tcp: md5: protects md5sig_info with RCU
authorEric Dumazet <eric.dumazet@gmail.com>
Tue, 31 Jan 2012 18:45:40 +0000 (18:45 +0000)
committerDavid S. Miller <davem@davemloft.net>
Wed, 1 Feb 2012 07:11:47 +0000 (02:11 -0500)
This patch makes sure we use appropriate memory barriers before
publishing tp->md5sig_info, allowing tcp_md5_do_lookup() being used from
tcp_v4_send_reset() without holding socket lock (upcoming patch from
Shawn Lu)

Note we also need to respect rcu grace period before its freeing, since
we can free socket without this grace period thanks to
SLAB_DESTROY_BY_RCU

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Cc: Shawn Lu <shawn.lu@ericsson.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/tcp.h
include/net/tcp.h
net/ipv4/tcp_ipv4.c
net/ipv6/tcp_ipv6.c

index c2025f159641e218509438cbdee3ecf64737ab36..115389e9b9453f2d9a878bdaf0100f80524e9335 100644 (file)
@@ -463,7 +463,7 @@ struct tcp_sock {
        const struct tcp_sock_af_ops    *af_specific;
 
 /* TCP MD5 Signature Option information */
-       struct tcp_md5sig_info  *md5sig_info;
+       struct tcp_md5sig_info  __rcu *md5sig_info;
 #endif
 
        /* When the cookie options are generated and exchanged, then this
index 10ae4c7b6b4fc083d4abb16a29ab326b748cc129..78880ba0f5603109741efd9cd92237cb17236b09 100644 (file)
@@ -1150,6 +1150,7 @@ struct tcp_md5sig_key {
 /* - sock block */
 struct tcp_md5sig_info {
        struct hlist_head       head;
+       struct rcu_head         rcu;
 };
 
 /* - pseudo header */
index da5d3226771b78841740b47f2e469d89d250cd82..567cca9b30df4c76f24cc4d7dfe3478d65f38bec 100644 (file)
@@ -879,14 +879,18 @@ struct tcp_md5sig_key *tcp_md5_do_lookup(struct sock *sk,
        struct tcp_md5sig_key *key;
        struct hlist_node *pos;
        unsigned int size = sizeof(struct in_addr);
+       struct tcp_md5sig_info *md5sig;
 
-       if (!tp->md5sig_info)
+       /* caller either holds rcu_read_lock() or socket lock */
+       md5sig = rcu_dereference_check(tp->md5sig_info,
+                                      sock_owned_by_user(sk));
+       if (!md5sig)
                return NULL;
 #if IS_ENABLED(CONFIG_IPV6)
        if (family == AF_INET6)
                size = sizeof(struct in6_addr);
 #endif
-       hlist_for_each_entry_rcu(key, pos, &tp->md5sig_info->head, node) {
+       hlist_for_each_entry_rcu(key, pos, &md5sig->head, node) {
                if (key->family != family)
                        continue;
                if (!memcmp(&key->addr, addr, size))
@@ -932,7 +936,8 @@ int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
                return 0;
        }
 
-       md5sig = tp->md5sig_info;
+       md5sig = rcu_dereference_protected(tp->md5sig_info,
+                                          sock_owned_by_user(sk));
        if (!md5sig) {
                md5sig = kmalloc(sizeof(*md5sig), gfp);
                if (!md5sig)
@@ -940,7 +945,7 @@ int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
 
                sk_nocaps_add(sk, NETIF_F_GSO_MASK);
                INIT_HLIST_HEAD(&md5sig->head);
-               tp->md5sig_info = md5sig;
+               rcu_assign_pointer(tp->md5sig_info, md5sig);
        }
 
        key = sock_kmalloc(sk, sizeof(*key), gfp);
@@ -966,6 +971,7 @@ int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr, int family)
 {
        struct tcp_sock *tp = tcp_sk(sk);
        struct tcp_md5sig_key *key;
+       struct tcp_md5sig_info *md5sig;
 
        key = tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&addr, AF_INET);
        if (!key)
@@ -973,7 +979,9 @@ int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr, int family)
        hlist_del_rcu(&key->node);
        atomic_sub(sizeof(*key), &sk->sk_omem_alloc);
        kfree_rcu(key, rcu);
-       if (hlist_empty(&tp->md5sig_info->head))
+       md5sig = rcu_dereference_protected(tp->md5sig_info,
+                                          sock_owned_by_user(sk));
+       if (hlist_empty(&md5sig->head))
                tcp_free_md5sig_pool();
        return 0;
 }
@@ -984,10 +992,13 @@ void tcp_clear_md5_list(struct sock *sk)
        struct tcp_sock *tp = tcp_sk(sk);
        struct tcp_md5sig_key *key;
        struct hlist_node *pos, *n;
+       struct tcp_md5sig_info *md5sig;
 
-       if (!hlist_empty(&tp->md5sig_info->head))
+       md5sig = rcu_dereference_protected(tp->md5sig_info, 1);
+
+       if (!hlist_empty(&md5sig->head))
                tcp_free_md5sig_pool();
-       hlist_for_each_entry_safe(key, pos, n, &tp->md5sig_info->head, node) {
+       hlist_for_each_entry_safe(key, pos, n, &md5sig->head, node) {
                hlist_del_rcu(&key->node);
                atomic_sub(sizeof(*key), &sk->sk_omem_alloc);
                kfree_rcu(key, rcu);
@@ -1009,12 +1020,9 @@ static int tcp_v4_parse_md5_keys(struct sock *sk, char __user *optval,
        if (sin->sin_family != AF_INET)
                return -EINVAL;
 
-       if (!cmd.tcpm_key || !cmd.tcpm_keylen) {
-               if (!tcp_sk(sk)->md5sig_info)
-                       return -ENOENT;
+       if (!cmd.tcpm_key || !cmd.tcpm_keylen)
                return tcp_md5_do_del(sk, (union tcp_md5_addr *)&sin->sin_addr.s_addr,
                                      AF_INET);
-       }
 
        if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)
                return -EINVAL;
@@ -1896,7 +1904,7 @@ void tcp_v4_destroy_sock(struct sock *sk)
        /* Clean up the MD5 key list, if any */
        if (tp->md5sig_info) {
                tcp_clear_md5_list(sk);
-               kfree(tp->md5sig_info);
+               kfree_rcu(tp->md5sig_info, rcu);
                tp->md5sig_info = NULL;
        }
 #endif
index bec41f9a64131eb95d1d2371076d9a78f298c917..c25018106ef2659caa38fcdb61c99934fab31fc0 100644 (file)
@@ -571,8 +571,6 @@ static int tcp_v6_parse_md5_keys (struct sock *sk, char __user *optval,
                return -EINVAL;
 
        if (!cmd.tcpm_keylen) {
-               if (!tcp_sk(sk)->md5sig_info)
-                       return -ENOENT;
                if (ipv6_addr_v4mapped(&sin6->sin6_addr))
                        return tcp_md5_do_del(sk, (union tcp_md5_addr *)&sin6->sin6_addr.s6_addr32[3],
                                              AF_INET);