Skip to content

Commit

Permalink
tcp: md5: protects md5sig_info with RCU
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Eric Dumazet authored and davem330 committed Feb 1, 2012
1 parent 41de8d4 commit a8afca0
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 15 deletions.
2 changes: 1 addition & 1 deletion include/linux/tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions include/net/tcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,7 @@ struct tcp_md5sig_key {
/* - sock block */
struct tcp_md5sig_info {
struct hlist_head head;
struct rcu_head rcu;
};

/* - pseudo header */
Expand Down
32 changes: 20 additions & 12 deletions net/ipv4/tcp_ipv4.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -932,15 +936,16 @@ 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)
return -ENOMEM;

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);
Expand All @@ -966,14 +971,17 @@ 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)
return -ENOENT;
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;
}
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions net/ipv6/tcp_ipv6.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit a8afca0

Please sign in to comment.