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:
parent
4954f17dde
commit
0aadc73995
@ -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 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,
|
||||
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;
|
||||
struct tcp_md5sig_key *__tcp_md5_do_lookup(const struct sock *sk, int l3index,
|
||||
const union tcp_md5_addr *addr,
|
||||
int family);
|
||||
int family, bool any_l3index);
|
||||
static inline struct tcp_md5sig_key *
|
||||
tcp_md5_do_lookup(const struct sock *sk, int l3index,
|
||||
const union tcp_md5_addr *addr, int family)
|
||||
{
|
||||
if (!static_branch_unlikely(&tcp_md5_needed.key))
|
||||
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
|
||||
@ -1811,6 +1821,13 @@ tcp_md5_do_lookup(const struct sock *sk, int l3index,
|
||||
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
|
||||
tcp_inbound_md5_hash(const struct sock *sk, const struct sk_buff *skb,
|
||||
const void *saddr, const void *daddr,
|
||||
@ -2177,6 +2194,9 @@ struct tcp_sock_af_ops {
|
||||
#endif
|
||||
#ifdef CONFIG_TCP_AO
|
||||
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
|
||||
};
|
||||
|
||||
@ -2588,4 +2608,23 @@ static inline u64 tcp_transmit_time(const struct sock *sk)
|
||||
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 */
|
||||
|
@ -92,11 +92,24 @@ struct tcp_ao_info {
|
||||
int tcp_parse_ao(struct sock *sk, int cmd, unsigned short int family,
|
||||
sockptr_t optval, int optlen);
|
||||
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 */
|
||||
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 */
|
||||
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
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
@ -116,6 +116,13 @@ static struct tcp_ao_key *__tcp_ao_do_lookup(const struct sock *sk,
|
||||
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)
|
||||
{
|
||||
struct tcp_ao_info *ao;
|
||||
@ -162,6 +169,14 @@ void tcp_ao_destroy_sock(struct sock *sk)
|
||||
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)
|
||||
{
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
if (IS_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;
|
||||
}
|
||||
|
||||
/* 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,
|
||||
sockptr_t optval, int optlen)
|
||||
{
|
||||
@ -732,6 +776,9 @@ static int tcp_ao_info_cmd(struct sock *sk, unsigned short int family,
|
||||
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
|
||||
* matching the future peer (address/port/VRF/etc),
|
||||
* tcp_ao_connect_init() will choose a correct matching MKT
|
||||
|
@ -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. */
|
||||
struct tcp_md5sig_key *__tcp_md5_do_lookup(const struct sock *sk, int l3index,
|
||||
const union tcp_md5_addr *addr,
|
||||
int family)
|
||||
int family, bool any_l3index)
|
||||
{
|
||||
const struct tcp_sock *tp = tcp_sk(sk);
|
||||
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)) {
|
||||
if (key->family != family)
|
||||
continue;
|
||||
if (key->flags & TCP_MD5SIG_FLAG_IFINDEX && key->l3index != l3index)
|
||||
if (!any_l3index && key->flags & TCP_MD5SIG_FLAG_IFINDEX &&
|
||||
key->l3index != l3index)
|
||||
continue;
|
||||
if (family == AF_INET) {
|
||||
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);
|
||||
|
||||
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_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)
|
||||
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,
|
||||
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,
|
||||
#endif
|
||||
#ifdef CONFIG_TCP_AO
|
||||
.ao_lookup = tcp_v4_ao_lookup,
|
||||
.ao_parse = tcp_v4_parse_ao,
|
||||
#endif
|
||||
};
|
||||
|
@ -3931,6 +3931,53 @@ int tcp_connect(struct sock *sk)
|
||||
|
||||
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))
|
||||
return -EHOSTUNREACH; /* Routing failure or similar. */
|
||||
|
||||
|
@ -12,6 +12,23 @@
|
||||
#include <net/tcp.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,
|
||||
sockptr_t optval, int optlen)
|
||||
{
|
||||
|
@ -600,6 +600,7 @@ static int tcp_v6_parse_md5_keys(struct sock *sk, int optname,
|
||||
{
|
||||
struct tcp_md5sig cmd;
|
||||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&cmd.tcpm_addr;
|
||||
union tcp_ao_addr *addr;
|
||||
int l3index = 0;
|
||||
u8 prefixlen;
|
||||
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)
|
||||
return -EINVAL;
|
||||
|
||||
if (ipv6_addr_v4mapped(&sin6->sin6_addr))
|
||||
return tcp_md5_do_add(sk, (union tcp_md5_addr *)&sin6->sin6_addr.s6_addr32[3],
|
||||
if (ipv6_addr_v4mapped(&sin6->sin6_addr)) {
|
||||
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,
|
||||
cmd.tcpm_key, cmd.tcpm_keylen);
|
||||
}
|
||||
|
||||
return tcp_md5_do_add(sk, (union tcp_md5_addr *)&sin6->sin6_addr,
|
||||
AF_INET6, prefixlen, l3index, flags,
|
||||
addr = (union tcp_md5_addr *)&sin6->sin6_addr;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
@ -1903,6 +1919,7 @@ static const struct tcp_sock_af_ops tcp_sock_ipv6_specific = {
|
||||
.md5_parse = tcp_v6_parse_md5_keys,
|
||||
#endif
|
||||
#ifdef CONFIG_TCP_AO
|
||||
.ao_lookup = tcp_v6_ao_lookup,
|
||||
.ao_parse = tcp_v6_parse_ao,
|
||||
#endif
|
||||
};
|
||||
@ -1934,6 +1951,7 @@ static const struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific = {
|
||||
.md5_parse = tcp_v6_parse_md5_keys,
|
||||
#endif
|
||||
#ifdef CONFIG_TCP_AO
|
||||
.ao_lookup = tcp_v6_ao_lookup,
|
||||
.ao_parse = tcp_v6_parse_ao,
|
||||
#endif
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user