diff options
author | Wei Wang <weiwan@google.com> | 2017-10-06 12:06:08 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2017-10-07 21:22:58 +0100 |
commit | 8d1040e808bb2a5aeb4f0791b32bc7356a01ab11 (patch) | |
tree | f4bf4d0471b7d5c75c21f5990fecac90fdb1f57d /net/ipv6/route.c | |
parent | bbd63f06d114a52be33f6982fc89ca2768cdeb62 (diff) | |
download | lwn-8d1040e808bb2a5aeb4f0791b32bc7356a01ab11.tar.gz lwn-8d1040e808bb2a5aeb4f0791b32bc7356a01ab11.zip |
ipv6: check fn->leaf before it is used
If rwlock is replaced with rcu and spinlock, it is possible that the
reader thread will see fn->leaf as NULL in the following scenarios:
1. fib6_add() is in progress and we have already inserted a new node but
not yet inserted the route.
2. fib6_del_route() is in progress and we have already set fn->leaf to
NULL but not yet freed the node because of rcu grace period.
This patch makes sure all the reader threads check fn->leaf first before
using it. And together with later patch to grab rcu_read_lock() and
rcu_dereference() fn->leaf, it makes sure reader threads are safe when
accessing fn->leaf.
Signed-off-by: Wei Wang <weiwan@google.com>
Signed-off-by: Martin KaFai Lau <kafai@fb.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv6/route.c')
-rw-r--r-- | net/ipv6/route.c | 20 |
1 files changed, 12 insertions, 8 deletions
diff --git a/net/ipv6/route.c b/net/ipv6/route.c index aeb349aea429..05dc450af441 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -712,6 +712,7 @@ out: } static struct rt6_info *find_rr_leaf(struct fib6_node *fn, + struct rt6_info *leaf, struct rt6_info *rr_head, u32 metric, int oif, int strict, bool *do_rr) @@ -730,7 +731,7 @@ static struct rt6_info *find_rr_leaf(struct fib6_node *fn, match = find_match(rt, oif, strict, &mpri, match, do_rr); } - for (rt = fn->leaf; rt && rt != rr_head; rt = rt->dst.rt6_next) { + for (rt = leaf; rt && rt != rr_head; rt = rt->dst.rt6_next) { if (rt->rt6i_metric != metric) { cont = rt; break; @@ -748,17 +749,21 @@ static struct rt6_info *find_rr_leaf(struct fib6_node *fn, return match; } -static struct rt6_info *rt6_select(struct fib6_node *fn, int oif, int strict) +static struct rt6_info *rt6_select(struct net *net, struct fib6_node *fn, + int oif, int strict) { + struct rt6_info *leaf = fn->leaf; struct rt6_info *match, *rt0; - struct net *net; bool do_rr = false; + if (!leaf) + return net->ipv6.ip6_null_entry; + rt0 = fn->rr_ptr; if (!rt0) - fn->rr_ptr = rt0 = fn->leaf; + fn->rr_ptr = rt0 = leaf; - match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict, + match = find_rr_leaf(fn, leaf, rt0, rt0->rt6i_metric, oif, strict, &do_rr); if (do_rr) { @@ -766,13 +771,12 @@ static struct rt6_info *rt6_select(struct fib6_node *fn, int oif, int strict) /* no entries matched; do round-robin */ if (!next || next->rt6i_metric != rt0->rt6i_metric) - next = fn->leaf; + next = leaf; if (next != rt0) fn->rr_ptr = next; } - net = dev_net(rt0->dst.dev); return match ? match : net->ipv6.ip6_null_entry; } @@ -1623,7 +1627,7 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, oif = 0; redo_rt6_select: - rt = rt6_select(fn, oif, strict); + rt = rt6_select(net, fn, oif, strict); if (rt->rt6i_nsiblings) rt = rt6_multipath_select(rt, fl6, oif, strict); if (rt == net->ipv6.ip6_null_entry) { |