Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tcp: md5: protects md5sig_info with RCU

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>

authored by

Eric Dumazet and committed by
David S. Miller
a8afca03 41de8d4c

+22 -15
+1 -1
include/linux/tcp.h
··· 463 463 const struct tcp_sock_af_ops *af_specific; 464 464 465 465 /* TCP MD5 Signature Option information */ 466 - struct tcp_md5sig_info *md5sig_info; 466 + struct tcp_md5sig_info __rcu *md5sig_info; 467 467 #endif 468 468 469 469 /* When the cookie options are generated and exchanged, then this
+1
include/net/tcp.h
··· 1150 1150 /* - sock block */ 1151 1151 struct tcp_md5sig_info { 1152 1152 struct hlist_head head; 1153 + struct rcu_head rcu; 1153 1154 }; 1154 1155 1155 1156 /* - pseudo header */
+20 -12
net/ipv4/tcp_ipv4.c
··· 879 879 struct tcp_md5sig_key *key; 880 880 struct hlist_node *pos; 881 881 unsigned int size = sizeof(struct in_addr); 882 + struct tcp_md5sig_info *md5sig; 882 883 883 - if (!tp->md5sig_info) 884 + /* caller either holds rcu_read_lock() or socket lock */ 885 + md5sig = rcu_dereference_check(tp->md5sig_info, 886 + sock_owned_by_user(sk)); 887 + if (!md5sig) 884 888 return NULL; 885 889 #if IS_ENABLED(CONFIG_IPV6) 886 890 if (family == AF_INET6) 887 891 size = sizeof(struct in6_addr); 888 892 #endif 889 - hlist_for_each_entry_rcu(key, pos, &tp->md5sig_info->head, node) { 893 + hlist_for_each_entry_rcu(key, pos, &md5sig->head, node) { 890 894 if (key->family != family) 891 895 continue; 892 896 if (!memcmp(&key->addr, addr, size)) ··· 936 932 return 0; 937 933 } 938 934 939 - md5sig = tp->md5sig_info; 935 + md5sig = rcu_dereference_protected(tp->md5sig_info, 936 + sock_owned_by_user(sk)); 940 937 if (!md5sig) { 941 938 md5sig = kmalloc(sizeof(*md5sig), gfp); 942 939 if (!md5sig) ··· 945 940 946 941 sk_nocaps_add(sk, NETIF_F_GSO_MASK); 947 942 INIT_HLIST_HEAD(&md5sig->head); 948 - tp->md5sig_info = md5sig; 943 + rcu_assign_pointer(tp->md5sig_info, md5sig); 949 944 } 950 945 951 946 key = sock_kmalloc(sk, sizeof(*key), gfp); ··· 971 966 { 972 967 struct tcp_sock *tp = tcp_sk(sk); 973 968 struct tcp_md5sig_key *key; 969 + struct tcp_md5sig_info *md5sig; 974 970 975 971 key = tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&addr, AF_INET); 976 972 if (!key) ··· 979 973 hlist_del_rcu(&key->node); 980 974 atomic_sub(sizeof(*key), &sk->sk_omem_alloc); 981 975 kfree_rcu(key, rcu); 982 - if (hlist_empty(&tp->md5sig_info->head)) 976 + md5sig = rcu_dereference_protected(tp->md5sig_info, 977 + sock_owned_by_user(sk)); 978 + if (hlist_empty(&md5sig->head)) 983 979 tcp_free_md5sig_pool(); 984 980 return 0; 985 981 } ··· 992 984 struct tcp_sock *tp = tcp_sk(sk); 993 985 struct tcp_md5sig_key *key; 994 986 struct hlist_node *pos, *n; 987 + struct tcp_md5sig_info *md5sig; 995 988 996 - if (!hlist_empty(&tp->md5sig_info->head)) 989 + md5sig = rcu_dereference_protected(tp->md5sig_info, 1); 990 + 991 + if (!hlist_empty(&md5sig->head)) 997 992 tcp_free_md5sig_pool(); 998 - hlist_for_each_entry_safe(key, pos, n, &tp->md5sig_info->head, node) { 993 + hlist_for_each_entry_safe(key, pos, n, &md5sig->head, node) { 999 994 hlist_del_rcu(&key->node); 1000 995 atomic_sub(sizeof(*key), &sk->sk_omem_alloc); 1001 996 kfree_rcu(key, rcu); ··· 1020 1009 if (sin->sin_family != AF_INET) 1021 1010 return -EINVAL; 1022 1011 1023 - if (!cmd.tcpm_key || !cmd.tcpm_keylen) { 1024 - if (!tcp_sk(sk)->md5sig_info) 1025 - return -ENOENT; 1012 + if (!cmd.tcpm_key || !cmd.tcpm_keylen) 1026 1013 return tcp_md5_do_del(sk, (union tcp_md5_addr *)&sin->sin_addr.s_addr, 1027 1014 AF_INET); 1028 - } 1029 1015 1030 1016 if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN) 1031 1017 return -EINVAL; ··· 1904 1896 /* Clean up the MD5 key list, if any */ 1905 1897 if (tp->md5sig_info) { 1906 1898 tcp_clear_md5_list(sk); 1907 - kfree(tp->md5sig_info); 1899 + kfree_rcu(tp->md5sig_info, rcu); 1908 1900 tp->md5sig_info = NULL; 1909 1901 } 1910 1902 #endif
-2
net/ipv6/tcp_ipv6.c
··· 571 571 return -EINVAL; 572 572 573 573 if (!cmd.tcpm_keylen) { 574 - if (!tcp_sk(sk)->md5sig_info) 575 - return -ENOENT; 576 574 if (ipv6_addr_v4mapped(&sin6->sin6_addr)) 577 575 return tcp_md5_do_del(sk, (union tcp_md5_addr *)&sin6->sin6_addr.s6_addr32[3], 578 576 AF_INET);