net/tcp: Prevent TCP-MD5 with TCP-AO being set
authorDmitry Safonov <dima@arista.com>
Mon, 23 Oct 2023 19:21:56 +0000 (20:21 +0100)
committerDavid S. Miller <davem@davemloft.net>
Fri, 27 Oct 2023 09:35:44 +0000 (10:35 +0100)
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>
include/net/tcp.h
include/net/tcp_ao.h
net/ipv4/tcp_ao.c
net/ipv4/tcp_ipv4.c
net/ipv4/tcp_output.c
net/ipv6/tcp_ao.c
net/ipv6/tcp_ipv6.c

index ff20447..0272117 100644 (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 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 */
index a81e40f..3c7f576 100644 (file)
@@ -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)
 {
 }
index 3c2d005..ee23356 100644 (file)
@@ -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
index a4746c2..698e58a 100644 (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.  */
 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
 };
index ca4d759..1b90107 100644 (file)
@@ -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. */
 
index 049ddba..0640aca 100644 (file)
 #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)
 {
index 30bd17d..70a3842 100644 (file)
@@ -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);
+       }
+
+       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, (union tcp_md5_addr *)&sin6->sin6_addr,
-                             AF_INET6, prefixlen, l3index, flags,
+       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
 };