net/tcp: Prevent TCP-MD5 with TCP-AO being set

Be as conservative as possible: if there is TCP-MD5 key for a given peer
regardless of L3 interface - don't allow setting TCP-AO key for the same
peer. According to RFC5925, TCP-AO is supposed to replace TCP-MD5 and
there can't be any switch between both on any connected tuple.
Later it can be relaxed, if there's a use, but in the beginning restrict
any intersection.

Note: it's still should be possible to set both TCP-MD5 and TCP-AO keys
on a listening socket for *different* peers.

Co-developed-by: Francesco Ruggeri <fruggeri@arista.com>
Signed-off-by: Francesco Ruggeri <fruggeri@arista.com>
Co-developed-by: Salam Noureddine <noureddine@arista.com>
Signed-off-by: Salam Noureddine <noureddine@arista.com>
Signed-off-by: Dmitry Safonov <dima@arista.com>
Acked-by: David Ahern <dsahern@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Dmitry Safonov 2023-10-23 20:21:56 +01:00 committed by David S. Miller
parent 4954f17dde
commit 0aadc73995
7 changed files with 198 additions and 9 deletions

View file

@ -1778,6 +1778,7 @@ int tcp_md5_key_copy(struct sock *sk, const union tcp_md5_addr *addr,
int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr, int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr,
int family, u8 prefixlen, int l3index, u8 flags); int family, u8 prefixlen, int l3index, u8 flags);
void tcp_clear_md5_list(struct sock *sk);
struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct sock *sk, struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct sock *sk,
const struct sock *addr_sk); const struct sock *addr_sk);
@ -1786,14 +1787,23 @@ struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct sock *sk,
extern struct static_key_false_deferred tcp_md5_needed; extern struct static_key_false_deferred tcp_md5_needed;
struct tcp_md5sig_key *__tcp_md5_do_lookup(const struct sock *sk, int l3index, struct tcp_md5sig_key *__tcp_md5_do_lookup(const struct sock *sk, int l3index,
const union tcp_md5_addr *addr, const union tcp_md5_addr *addr,
int family); int family, bool any_l3index);
static inline struct tcp_md5sig_key * static inline struct tcp_md5sig_key *
tcp_md5_do_lookup(const struct sock *sk, int l3index, tcp_md5_do_lookup(const struct sock *sk, int l3index,
const union tcp_md5_addr *addr, int family) const union tcp_md5_addr *addr, int family)
{ {
if (!static_branch_unlikely(&tcp_md5_needed.key)) if (!static_branch_unlikely(&tcp_md5_needed.key))
return NULL; return NULL;
return __tcp_md5_do_lookup(sk, l3index, addr, family); return __tcp_md5_do_lookup(sk, l3index, addr, family, false);
}
static inline struct tcp_md5sig_key *
tcp_md5_do_lookup_any_l3index(const struct sock *sk,
const union tcp_md5_addr *addr, int family)
{
if (!static_branch_unlikely(&tcp_md5_needed.key))
return NULL;
return __tcp_md5_do_lookup(sk, 0, addr, family, true);
} }
enum skb_drop_reason enum skb_drop_reason
@ -1811,6 +1821,13 @@ tcp_md5_do_lookup(const struct sock *sk, int l3index,
return NULL; return NULL;
} }
static inline struct tcp_md5sig_key *
tcp_md5_do_lookup_any_l3index(const struct sock *sk,
const union tcp_md5_addr *addr, int family)
{
return NULL;
}
static inline enum skb_drop_reason static inline enum skb_drop_reason
tcp_inbound_md5_hash(const struct sock *sk, const struct sk_buff *skb, tcp_inbound_md5_hash(const struct sock *sk, const struct sk_buff *skb,
const void *saddr, const void *daddr, const void *saddr, const void *daddr,
@ -2177,6 +2194,9 @@ struct tcp_sock_af_ops {
#endif #endif
#ifdef CONFIG_TCP_AO #ifdef CONFIG_TCP_AO
int (*ao_parse)(struct sock *sk, int optname, sockptr_t optval, int optlen); int (*ao_parse)(struct sock *sk, int optname, sockptr_t optval, int optlen);
struct tcp_ao_key *(*ao_lookup)(const struct sock *sk,
struct sock *addr_sk,
int sndid, int rcvid);
#endif #endif
}; };
@ -2588,4 +2608,23 @@ static inline u64 tcp_transmit_time(const struct sock *sk)
return 0; return 0;
} }
static inline bool tcp_ao_required(struct sock *sk, const void *saddr,
int family)
{
#ifdef CONFIG_TCP_AO
struct tcp_ao_info *ao_info;
struct tcp_ao_key *ao_key;
ao_info = rcu_dereference_check(tcp_sk(sk)->ao_info,
lockdep_sock_is_held(sk));
if (!ao_info)
return false;
ao_key = tcp_ao_do_lookup(sk, saddr, family, -1, -1);
if (ao_info->ao_required || ao_key)
return true;
#endif
return false;
}
#endif /* _TCP_H */ #endif /* _TCP_H */

View file

@ -92,11 +92,24 @@ struct tcp_ao_info {
int tcp_parse_ao(struct sock *sk, int cmd, unsigned short int family, int tcp_parse_ao(struct sock *sk, int cmd, unsigned short int family,
sockptr_t optval, int optlen); sockptr_t optval, int optlen);
void tcp_ao_destroy_sock(struct sock *sk); void tcp_ao_destroy_sock(struct sock *sk);
struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk,
const union tcp_ao_addr *addr,
int family, int sndid, int rcvid);
/* ipv4 specific functions */ /* ipv4 specific functions */
int tcp_v4_parse_ao(struct sock *sk, int cmd, sockptr_t optval, int optlen); int tcp_v4_parse_ao(struct sock *sk, int cmd, sockptr_t optval, int optlen);
struct tcp_ao_key *tcp_v4_ao_lookup(const struct sock *sk, struct sock *addr_sk,
int sndid, int rcvid);
/* ipv6 specific functions */ /* ipv6 specific functions */
int tcp_v6_parse_ao(struct sock *sk, int cmd, sockptr_t optval, int optlen); int tcp_v6_parse_ao(struct sock *sk, int cmd, sockptr_t optval, int optlen);
struct tcp_ao_key *tcp_v6_ao_lookup(const struct sock *sk,
struct sock *addr_sk, int sndid, int rcvid);
#else #else
static inline struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk,
const union tcp_ao_addr *addr, int family, int sndid, int rcvid)
{
return NULL;
}
static inline void tcp_ao_destroy_sock(struct sock *sk) static inline void tcp_ao_destroy_sock(struct sock *sk)
{ {
} }

View file

@ -116,6 +116,13 @@ static struct tcp_ao_key *__tcp_ao_do_lookup(const struct sock *sk,
return NULL; return NULL;
} }
struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk,
const union tcp_ao_addr *addr,
int family, int sndid, int rcvid)
{
return __tcp_ao_do_lookup(sk, addr, family, U8_MAX, sndid, rcvid);
}
static struct tcp_ao_info *tcp_ao_alloc_info(gfp_t flags) static struct tcp_ao_info *tcp_ao_alloc_info(gfp_t flags)
{ {
struct tcp_ao_info *ao; struct tcp_ao_info *ao;
@ -162,6 +169,14 @@ void tcp_ao_destroy_sock(struct sock *sk)
kfree_rcu(ao, rcu); kfree_rcu(ao, rcu);
} }
struct tcp_ao_key *tcp_v4_ao_lookup(const struct sock *sk, struct sock *addr_sk,
int sndid, int rcvid)
{
union tcp_ao_addr *addr = (union tcp_ao_addr *)&addr_sk->sk_daddr;
return tcp_ao_do_lookup(sk, addr, AF_INET, sndid, rcvid);
}
static bool tcp_ao_can_set_current_rnext(struct sock *sk) static bool tcp_ao_can_set_current_rnext(struct sock *sk)
{ {
/* There aren't current/rnext keys on TCP_LISTEN sockets */ /* There aren't current/rnext keys on TCP_LISTEN sockets */
@ -497,6 +512,10 @@ static int tcp_ao_add_cmd(struct sock *sk, unsigned short int family,
return -EINVAL; return -EINVAL;
} }
/* Don't allow keys for peers that have a matching TCP-MD5 key */
if (tcp_md5_do_lookup_any_l3index(sk, addr, family))
return -EKEYREJECTED;
ao_info = setsockopt_ao_info(sk); ao_info = setsockopt_ao_info(sk);
if (IS_ERR(ao_info)) if (IS_ERR(ao_info))
return PTR_ERR(ao_info); return PTR_ERR(ao_info);
@ -698,6 +717,31 @@ static int tcp_ao_del_cmd(struct sock *sk, unsigned short int family,
return -ENOENT; return -ENOENT;
} }
/* cmd.ao_required makes a socket TCP-AO only.
* Don't allow any md5 keys for any l3intf on the socket together with it.
* Restricting it early in setsockopt() removes a check for
* ao_info->ao_required on inbound tcp segment fast-path.
*/
static int tcp_ao_required_verify(struct sock *sk)
{
#ifdef CONFIG_TCP_MD5SIG
const struct tcp_md5sig_info *md5sig;
if (!static_branch_unlikely(&tcp_md5_needed.key))
return 0;
md5sig = rcu_dereference_check(tcp_sk(sk)->md5sig_info,
lockdep_sock_is_held(sk));
if (!md5sig)
return 0;
if (rcu_dereference_check(hlist_first_rcu(&md5sig->head),
lockdep_sock_is_held(sk)))
return 1;
#endif
return 0;
}
static int tcp_ao_info_cmd(struct sock *sk, unsigned short int family, static int tcp_ao_info_cmd(struct sock *sk, unsigned short int family,
sockptr_t optval, int optlen) sockptr_t optval, int optlen)
{ {
@ -732,6 +776,9 @@ static int tcp_ao_info_cmd(struct sock *sk, unsigned short int family,
first = true; first = true;
} }
if (cmd.ao_required && tcp_ao_required_verify(sk))
return -EKEYREJECTED;
/* For sockets in TCP_CLOSED it's possible set keys that aren't /* For sockets in TCP_CLOSED it's possible set keys that aren't
* matching the future peer (address/port/VRF/etc), * matching the future peer (address/port/VRF/etc),
* tcp_ao_connect_init() will choose a correct matching MKT * tcp_ao_connect_init() will choose a correct matching MKT

View file

@ -1082,7 +1082,7 @@ static bool better_md5_match(struct tcp_md5sig_key *old, struct tcp_md5sig_key *
/* Find the Key structure for an address. */ /* Find the Key structure for an address. */
struct tcp_md5sig_key *__tcp_md5_do_lookup(const struct sock *sk, int l3index, struct tcp_md5sig_key *__tcp_md5_do_lookup(const struct sock *sk, int l3index,
const union tcp_md5_addr *addr, const union tcp_md5_addr *addr,
int family) int family, bool any_l3index)
{ {
const struct tcp_sock *tp = tcp_sk(sk); const struct tcp_sock *tp = tcp_sk(sk);
struct tcp_md5sig_key *key; struct tcp_md5sig_key *key;
@ -1101,7 +1101,8 @@ struct tcp_md5sig_key *__tcp_md5_do_lookup(const struct sock *sk, int l3index,
lockdep_sock_is_held(sk)) { lockdep_sock_is_held(sk)) {
if (key->family != family) if (key->family != family)
continue; continue;
if (key->flags & TCP_MD5SIG_FLAG_IFINDEX && key->l3index != l3index) if (!any_l3index && key->flags & TCP_MD5SIG_FLAG_IFINDEX &&
key->l3index != l3index)
continue; continue;
if (family == AF_INET) { if (family == AF_INET) {
mask = inet_make_mask(key->prefixlen); mask = inet_make_mask(key->prefixlen);
@ -1313,7 +1314,7 @@ int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr, int family,
} }
EXPORT_SYMBOL(tcp_md5_do_del); EXPORT_SYMBOL(tcp_md5_do_del);
static void tcp_clear_md5_list(struct sock *sk) void tcp_clear_md5_list(struct sock *sk)
{ {
struct tcp_sock *tp = tcp_sk(sk); struct tcp_sock *tp = tcp_sk(sk);
struct tcp_md5sig_key *key; struct tcp_md5sig_key *key;
@ -1383,6 +1384,12 @@ static int tcp_v4_parse_md5_keys(struct sock *sk, int optname,
if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN) if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)
return -EINVAL; return -EINVAL;
/* Don't allow keys for peers that have a matching TCP-AO key.
* See the comment in tcp_ao_add_cmd()
*/
if (tcp_ao_required(sk, addr, AF_INET))
return -EKEYREJECTED;
return tcp_md5_do_add(sk, addr, AF_INET, prefixlen, l3index, flags, return tcp_md5_do_add(sk, addr, AF_INET, prefixlen, l3index, flags,
cmd.tcpm_key, cmd.tcpm_keylen); cmd.tcpm_key, cmd.tcpm_keylen);
} }
@ -2279,6 +2286,7 @@ static const struct tcp_sock_af_ops tcp_sock_ipv4_specific = {
.md5_parse = tcp_v4_parse_md5_keys, .md5_parse = tcp_v4_parse_md5_keys,
#endif #endif
#ifdef CONFIG_TCP_AO #ifdef CONFIG_TCP_AO
.ao_lookup = tcp_v4_ao_lookup,
.ao_parse = tcp_v4_parse_ao, .ao_parse = tcp_v4_parse_ao,
#endif #endif
}; };

View file

@ -3931,6 +3931,53 @@ int tcp_connect(struct sock *sk)
tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_CONNECT_CB, 0, NULL); tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_CONNECT_CB, 0, NULL);
#if defined(CONFIG_TCP_MD5SIG) && defined(CONFIG_TCP_AO)
/* Has to be checked late, after setting daddr/saddr/ops.
* Return error if the peer has both a md5 and a tcp-ao key
* configured as this is ambiguous.
*/
if (unlikely(rcu_dereference_protected(tp->md5sig_info,
lockdep_sock_is_held(sk)))) {
bool needs_ao = !!tp->af_specific->ao_lookup(sk, sk, -1, -1);
bool needs_md5 = !!tp->af_specific->md5_lookup(sk, sk);
struct tcp_ao_info *ao_info;
ao_info = rcu_dereference_check(tp->ao_info,
lockdep_sock_is_held(sk));
if (ao_info) {
/* This is an extra check: tcp_ao_required() in
* tcp_v{4,6}_parse_md5_keys() should prevent adding
* md5 keys on ao_required socket.
*/
needs_ao |= ao_info->ao_required;
WARN_ON_ONCE(ao_info->ao_required && needs_md5);
}
if (needs_md5 && needs_ao)
return -EKEYREJECTED;
/* If we have a matching md5 key and no matching tcp-ao key
* then free up ao_info if allocated.
*/
if (needs_md5) {
tcp_ao_destroy_sock(sk);
} else if (needs_ao) {
tcp_clear_md5_list(sk);
kfree(rcu_replace_pointer(tp->md5sig_info, NULL,
lockdep_sock_is_held(sk)));
}
}
#endif
#ifdef CONFIG_TCP_AO
if (unlikely(rcu_dereference_protected(tp->ao_info,
lockdep_sock_is_held(sk)))) {
/* Don't allow connecting if ao is configured but no
* matching key is found.
*/
if (!tp->af_specific->ao_lookup(sk, sk, -1, -1))
return -EKEYREJECTED;
}
#endif
if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk)) if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
return -EHOSTUNREACH; /* Routing failure or similar. */ return -EHOSTUNREACH; /* Routing failure or similar. */

View file

@ -12,6 +12,23 @@
#include <net/tcp.h> #include <net/tcp.h>
#include <net/ipv6.h> #include <net/ipv6.h>
static struct tcp_ao_key *tcp_v6_ao_do_lookup(const struct sock *sk,
const struct in6_addr *addr,
int sndid, int rcvid)
{
return tcp_ao_do_lookup(sk, (union tcp_ao_addr *)addr, AF_INET6,
sndid, rcvid);
}
struct tcp_ao_key *tcp_v6_ao_lookup(const struct sock *sk,
struct sock *addr_sk,
int sndid, int rcvid)
{
struct in6_addr *addr = &addr_sk->sk_v6_daddr;
return tcp_v6_ao_do_lookup(sk, addr, sndid, rcvid);
}
int tcp_v6_parse_ao(struct sock *sk, int cmd, int tcp_v6_parse_ao(struct sock *sk, int cmd,
sockptr_t optval, int optlen) sockptr_t optval, int optlen)
{ {

View file

@ -600,6 +600,7 @@ static int tcp_v6_parse_md5_keys(struct sock *sk, int optname,
{ {
struct tcp_md5sig cmd; struct tcp_md5sig cmd;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&cmd.tcpm_addr; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&cmd.tcpm_addr;
union tcp_ao_addr *addr;
int l3index = 0; int l3index = 0;
u8 prefixlen; u8 prefixlen;
u8 flags; u8 flags;
@ -654,13 +655,28 @@ static int tcp_v6_parse_md5_keys(struct sock *sk, int optname,
if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN) if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)
return -EINVAL; return -EINVAL;
if (ipv6_addr_v4mapped(&sin6->sin6_addr)) if (ipv6_addr_v4mapped(&sin6->sin6_addr)) {
return tcp_md5_do_add(sk, (union tcp_md5_addr *)&sin6->sin6_addr.s6_addr32[3], addr = (union tcp_md5_addr *)&sin6->sin6_addr.s6_addr32[3];
/* Don't allow keys for peers that have a matching TCP-AO key.
* See the comment in tcp_ao_add_cmd()
*/
if (tcp_ao_required(sk, addr, AF_INET))
return -EKEYREJECTED;
return tcp_md5_do_add(sk, addr,
AF_INET, prefixlen, l3index, flags, AF_INET, prefixlen, l3index, flags,
cmd.tcpm_key, cmd.tcpm_keylen); cmd.tcpm_key, cmd.tcpm_keylen);
}
return tcp_md5_do_add(sk, (union tcp_md5_addr *)&sin6->sin6_addr, addr = (union tcp_md5_addr *)&sin6->sin6_addr;
AF_INET6, prefixlen, l3index, flags,
/* Don't allow keys for peers that have a matching TCP-AO key.
* See the comment in tcp_ao_add_cmd()
*/
if (tcp_ao_required(sk, addr, AF_INET6))
return -EKEYREJECTED;
return tcp_md5_do_add(sk, addr, AF_INET6, prefixlen, l3index, flags,
cmd.tcpm_key, cmd.tcpm_keylen); cmd.tcpm_key, cmd.tcpm_keylen);
} }
@ -1903,6 +1919,7 @@ static const struct tcp_sock_af_ops tcp_sock_ipv6_specific = {
.md5_parse = tcp_v6_parse_md5_keys, .md5_parse = tcp_v6_parse_md5_keys,
#endif #endif
#ifdef CONFIG_TCP_AO #ifdef CONFIG_TCP_AO
.ao_lookup = tcp_v6_ao_lookup,
.ao_parse = tcp_v6_parse_ao, .ao_parse = tcp_v6_parse_ao,
#endif #endif
}; };
@ -1934,6 +1951,7 @@ static const struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific = {
.md5_parse = tcp_v6_parse_md5_keys, .md5_parse = tcp_v6_parse_md5_keys,
#endif #endif
#ifdef CONFIG_TCP_AO #ifdef CONFIG_TCP_AO
.ao_lookup = tcp_v6_ao_lookup,
.ao_parse = tcp_v6_parse_ao, .ao_parse = tcp_v6_parse_ao,
#endif #endif
}; };