diff options
author | Eric Dumazet <edumazet@google.com> | 2015-11-29 19:37:57 -0800 |
---|---|---|
committer | Ben Hutchings <ben@decadent.org.uk> | 2015-12-30 02:26:02 +0000 |
commit | 5bf369b4470d3618af67b572a82d76b92ce1abd1 (patch) | |
tree | 2ebafe230e8da33e591ef32b9749963ef09cee87 /include | |
parent | 3f276bbbe575e06eafcde349f9d92332690e4d2d (diff) | |
download | lwn-5bf369b4470d3618af67b572a82d76b92ce1abd1.tar.gz lwn-5bf369b4470d3618af67b572a82d76b92ce1abd1.zip |
ipv6: add complete rcu protection around np->opt
[ Upstream commit 45f6fad84cc305103b28d73482b344d7f5b76f39 ]
This patch addresses multiple problems :
UDP/RAW sendmsg() need to get a stable struct ipv6_txoptions
while socket is not locked : Other threads can change np->opt
concurrently. Dmitry posted a syzkaller
(http://github.com/google/syzkaller) program desmonstrating
use-after-free.
Starting with TCP/DCCP lockless listeners, tcp_v6_syn_recv_sock()
and dccp_v6_request_recv_sock() also need to use RCU protection
to dereference np->opt once (before calling ipv6_dup_options())
This patch adds full RCU protection to np->opt
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
[bwh: Backported to 3.2:
- Drop changes to l2tp
- Fix an additional use of np->opt in tcp_v6_send_synack()
- Fold in commit 43264e0bd963 ("ipv6: remove unnecessary codes in tcp_ipv6.c")
- Adjust context]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Diffstat (limited to 'include')
-rw-r--r-- | include/linux/ipv6.h | 2 | ||||
-rw-r--r-- | include/net/ipv6.h | 21 |
2 files changed, 21 insertions, 2 deletions
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h index 84b1447481b5..00ef00d0f315 100644 --- a/include/linux/ipv6.h +++ b/include/linux/ipv6.h @@ -369,7 +369,7 @@ struct ipv6_pinfo { struct ipv6_ac_socklist *ipv6_ac_list; struct ipv6_fl_socklist *ipv6_fl_list; - struct ipv6_txoptions *opt; + struct ipv6_txoptions __rcu *opt; struct sk_buff *pktoptions; struct sk_buff *rxpmtu; struct { diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 77763c69e0a6..25f04918640e 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -191,6 +191,7 @@ extern rwlock_t ip6_ra_lock; */ struct ipv6_txoptions { + atomic_t refcnt; /* Length of this structure */ int tot_len; @@ -203,7 +204,7 @@ struct ipv6_txoptions { struct ipv6_opt_hdr *dst0opt; struct ipv6_rt_hdr *srcrt; /* Routing Header */ struct ipv6_opt_hdr *dst1opt; - + struct rcu_head rcu; /* Option buffer, as read by IPV6_PKTOPTIONS, starts here. */ }; @@ -229,6 +230,24 @@ struct ipv6_fl_socklist { struct ip6_flowlabel *fl; }; +static inline struct ipv6_txoptions *txopt_get(const struct ipv6_pinfo *np) +{ + struct ipv6_txoptions *opt; + + rcu_read_lock(); + opt = rcu_dereference(np->opt); + if (opt && !atomic_inc_not_zero(&opt->refcnt)) + opt = NULL; + rcu_read_unlock(); + return opt; +} + +static inline void txopt_put(struct ipv6_txoptions *opt) +{ + if (opt && atomic_dec_and_test(&opt->refcnt)) + kfree_rcu(opt, rcu); +} + extern struct ip6_flowlabel *fl6_sock_lookup(struct sock *sk, __be32 label); extern struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions * opt_space, struct ip6_flowlabel * fl, |