Skip to content

Commit a68886a

Browse files
dsaherndavem330
authored andcommitted
net/ipv6: Make from in rt6_info rcu protected
When a dst entry is created from a fib entry, the 'from' in rt6_info is set to the fib entry. The 'from' reference is used most notably for cookie checking - making sure stale dst entries are updated if the fib entry is changed. When a fib entry is deleted, the pcpu routes on it are walked releasing the fib6_info reference. This is needed for the fib6_info cleanup to happen and to make sure all device references are released in a timely manner. There is a race window when a FIB entry is deleted and the 'from' on the pcpu route is dropped and the pcpu route hits a cookie check. Handle this race using rcu on from. Signed-off-by: David Ahern <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 5bcaa41 commit a68886a

File tree

4 files changed

+88
-35
lines changed

4 files changed

+88
-35
lines changed

include/net/ip6_fib.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ struct fib6_info {
174174

175175
struct rt6_info {
176176
struct dst_entry dst;
177-
struct fib6_info *from;
177+
struct fib6_info __rcu *from;
178178

179179
struct rt6key rt6i_dst;
180180
struct rt6key rt6i_src;
@@ -248,13 +248,15 @@ static inline bool fib6_get_cookie_safe(const struct fib6_info *f6i,
248248

249249
static inline u32 rt6_get_cookie(const struct rt6_info *rt)
250250
{
251+
struct fib6_info *from;
251252
u32 cookie = 0;
252253

253254
rcu_read_lock();
254255

255-
if (rt->rt6i_flags & RTF_PCPU ||
256-
(unlikely(!list_empty(&rt->rt6i_uncached)) && rt->from))
257-
fib6_get_cookie_safe(rt->from, &cookie);
256+
from = rcu_dereference(rt->from);
257+
if (from && (rt->rt6i_flags & RTF_PCPU ||
258+
unlikely(!list_empty(&rt->rt6i_uncached))))
259+
fib6_get_cookie_safe(from, &cookie);
258260

259261
rcu_read_unlock();
260262

net/ipv6/ip6_fib.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -875,8 +875,12 @@ static void fib6_drop_pcpu_from(struct fib6_info *f6i,
875875
ppcpu_rt = per_cpu_ptr(f6i->rt6i_pcpu, cpu);
876876
pcpu_rt = *ppcpu_rt;
877877
if (pcpu_rt) {
878-
fib6_info_release(pcpu_rt->from);
879-
pcpu_rt->from = NULL;
878+
struct fib6_info *from;
879+
880+
from = rcu_dereference_protected(pcpu_rt->from,
881+
lockdep_is_held(&table->tb6_lock));
882+
rcu_assign_pointer(pcpu_rt->from, NULL);
883+
fib6_info_release(from);
880884
}
881885
}
882886
}

net/ipv6/ip6_output.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -962,16 +962,21 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
962962
* that's why we try it again later.
963963
*/
964964
if (ipv6_addr_any(&fl6->saddr) && (!*dst || !(*dst)->error)) {
965+
struct fib6_info *from;
965966
struct rt6_info *rt;
966967
bool had_dst = *dst != NULL;
967968

968969
if (!had_dst)
969970
*dst = ip6_route_output(net, sk, fl6);
970971
rt = (*dst)->error ? NULL : (struct rt6_info *)*dst;
971-
err = ip6_route_get_saddr(net, rt ? rt->from : NULL,
972-
&fl6->daddr,
972+
973+
rcu_read_lock();
974+
from = rt ? rcu_dereference(rt->from) : NULL;
975+
err = ip6_route_get_saddr(net, from, &fl6->daddr,
973976
sk ? inet6_sk(sk)->srcprefs : 0,
974977
&fl6->saddr);
978+
rcu_read_unlock();
979+
975980
if (err)
976981
goto out_err_release;
977982

net/ipv6/route.c

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ EXPORT_SYMBOL(ip6_dst_alloc);
359359
static void ip6_dst_destroy(struct dst_entry *dst)
360360
{
361361
struct rt6_info *rt = (struct rt6_info *)dst;
362-
struct fib6_info *from = rt->from;
362+
struct fib6_info *from;
363363
struct inet6_dev *idev;
364364

365365
dst_destroy_metrics_generic(dst);
@@ -371,8 +371,11 @@ static void ip6_dst_destroy(struct dst_entry *dst)
371371
in6_dev_put(idev);
372372
}
373373

374-
rt->from = NULL;
374+
rcu_read_lock();
375+
from = rcu_dereference(rt->from);
376+
rcu_assign_pointer(rt->from, NULL);
375377
fib6_info_release(from);
378+
rcu_read_unlock();
376379
}
377380

378381
static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
@@ -402,12 +405,16 @@ static bool __rt6_check_expired(const struct rt6_info *rt)
402405

403406
static bool rt6_check_expired(const struct rt6_info *rt)
404407
{
408+
struct fib6_info *from;
409+
410+
from = rcu_dereference(rt->from);
411+
405412
if (rt->rt6i_flags & RTF_EXPIRES) {
406413
if (time_after(jiffies, rt->dst.expires))
407414
return true;
408-
} else if (rt->from) {
415+
} else if (from) {
409416
return rt->dst.obsolete != DST_OBSOLETE_FORCE_CHK ||
410-
fib6_check_expired(rt->from);
417+
fib6_check_expired(from);
411418
}
412419
return false;
413420
}
@@ -963,7 +970,7 @@ static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from)
963970
{
964971
rt->rt6i_flags &= ~RTF_EXPIRES;
965972
fib6_info_hold(from);
966-
rt->from = from;
973+
rcu_assign_pointer(rt->from, from);
967974
dst_init_metrics(&rt->dst, from->fib6_metrics->metrics, true);
968975
if (from->fib6_metrics != &dst_default_metrics) {
969976
rt->dst._metrics |= DST_METRICS_REFCOUNTED;
@@ -2133,11 +2140,13 @@ static bool fib6_check(struct fib6_info *f6i, u32 cookie)
21332140
return true;
21342141
}
21352142

2136-
static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie)
2143+
static struct dst_entry *rt6_check(struct rt6_info *rt,
2144+
struct fib6_info *from,
2145+
u32 cookie)
21372146
{
21382147
u32 rt_cookie = 0;
21392148

2140-
if ((rt->from && !fib6_get_cookie_safe(rt->from, &rt_cookie)) ||
2149+
if ((from && !fib6_get_cookie_safe(from, &rt_cookie)) ||
21412150
rt_cookie != cookie)
21422151
return NULL;
21432152

@@ -2147,11 +2156,13 @@ static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie)
21472156
return &rt->dst;
21482157
}
21492158

2150-
static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt, u32 cookie)
2159+
static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt,
2160+
struct fib6_info *from,
2161+
u32 cookie)
21512162
{
21522163
if (!__rt6_check_expired(rt) &&
21532164
rt->dst.obsolete == DST_OBSOLETE_FORCE_CHK &&
2154-
fib6_check(rt->from, cookie))
2165+
fib6_check(from, cookie))
21552166
return &rt->dst;
21562167
else
21572168
return NULL;
@@ -2160,6 +2171,7 @@ static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt, u32 cookie)
21602171
static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie)
21612172
{
21622173
struct dst_entry *dst_ret;
2174+
struct fib6_info *from;
21632175
struct rt6_info *rt;
21642176

21652177
rt = container_of(dst, struct rt6_info, dst);
@@ -2171,11 +2183,13 @@ static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie)
21712183
* into this function always.
21722184
*/
21732185

2174-
if (rt->rt6i_flags & RTF_PCPU ||
2175-
(unlikely(!list_empty(&rt->rt6i_uncached)) && rt->from))
2176-
dst_ret = rt6_dst_from_check(rt, cookie);
2186+
from = rcu_dereference(rt->from);
2187+
2188+
if (from && (rt->rt6i_flags & RTF_PCPU ||
2189+
unlikely(!list_empty(&rt->rt6i_uncached))))
2190+
dst_ret = rt6_dst_from_check(rt, from, cookie);
21772191
else
2178-
dst_ret = rt6_check(rt, cookie);
2192+
dst_ret = rt6_check(rt, from, cookie);
21792193

21802194
rcu_read_unlock();
21812195

@@ -2211,22 +2225,33 @@ static void ip6_link_failure(struct sk_buff *skb)
22112225
if (rt->rt6i_flags & RTF_CACHE) {
22122226
if (dst_hold_safe(&rt->dst))
22132227
rt6_remove_exception_rt(rt);
2214-
} else if (rt->from) {
2228+
} else {
2229+
struct fib6_info *from;
22152230
struct fib6_node *fn;
22162231

22172232
rcu_read_lock();
2218-
fn = rcu_dereference(rt->from->fib6_node);
2219-
if (fn && (rt->rt6i_flags & RTF_DEFAULT))
2220-
fn->fn_sernum = -1;
2233+
from = rcu_dereference(rt->from);
2234+
if (from) {
2235+
fn = rcu_dereference(from->fib6_node);
2236+
if (fn && (rt->rt6i_flags & RTF_DEFAULT))
2237+
fn->fn_sernum = -1;
2238+
}
22212239
rcu_read_unlock();
22222240
}
22232241
}
22242242
}
22252243

22262244
static void rt6_update_expires(struct rt6_info *rt0, int timeout)
22272245
{
2228-
if (!(rt0->rt6i_flags & RTF_EXPIRES) && rt0->from)
2229-
rt0->dst.expires = rt0->from->expires;
2246+
if (!(rt0->rt6i_flags & RTF_EXPIRES)) {
2247+
struct fib6_info *from;
2248+
2249+
rcu_read_lock();
2250+
from = rcu_dereference(rt0->from);
2251+
if (from)
2252+
rt0->dst.expires = from->expires;
2253+
rcu_read_unlock();
2254+
}
22302255

22312256
dst_set_expires(&rt0->dst, timeout);
22322257
rt0->rt6i_flags |= RTF_EXPIRES;
@@ -2243,8 +2268,14 @@ static void rt6_do_update_pmtu(struct rt6_info *rt, u32 mtu)
22432268

22442269
static bool rt6_cache_allowed_for_pmtu(const struct rt6_info *rt)
22452270
{
2271+
bool from_set;
2272+
2273+
rcu_read_lock();
2274+
from_set = !!rcu_dereference(rt->from);
2275+
rcu_read_unlock();
2276+
22462277
return !(rt->rt6i_flags & RTF_CACHE) &&
2247-
(rt->rt6i_flags & RTF_PCPU || rt->from);
2278+
(rt->rt6i_flags & RTF_PCPU || from_set);
22482279
}
22492280

22502281
static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
@@ -2280,16 +2311,18 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
22802311
if (rt6->rt6i_flags & RTF_CACHE)
22812312
rt6_update_exception_stamp_rt(rt6);
22822313
} else if (daddr) {
2314+
struct fib6_info *from;
22832315
struct rt6_info *nrt6;
22842316

22852317
rcu_read_lock();
2286-
nrt6 = ip6_rt_cache_alloc(rt6->from, daddr, saddr);
2287-
rcu_read_unlock();
2318+
from = rcu_dereference(rt6->from);
2319+
nrt6 = ip6_rt_cache_alloc(from, daddr, saddr);
22882320
if (nrt6) {
22892321
rt6_do_update_pmtu(nrt6, mtu);
2290-
if (rt6_insert_exception(nrt6, rt6->from))
2322+
if (rt6_insert_exception(nrt6, from))
22912323
dst_release_immediate(&nrt6->dst);
22922324
}
2325+
rcu_read_unlock();
22932326
}
22942327
}
22952328

@@ -3222,6 +3255,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
32223255
struct ndisc_options ndopts;
32233256
struct inet6_dev *in6_dev;
32243257
struct neighbour *neigh;
3258+
struct fib6_info *from;
32253259
struct rd_msg *msg;
32263260
int optlen, on_link;
32273261
u8 *lladdr;
@@ -3304,7 +3338,8 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
33043338
NDISC_REDIRECT, &ndopts);
33053339

33063340
rcu_read_lock();
3307-
nrt = ip6_rt_cache_alloc(rt->from, &msg->dest, NULL);
3341+
from = rcu_dereference(rt->from);
3342+
nrt = ip6_rt_cache_alloc(from, &msg->dest, NULL);
33083343
rcu_read_unlock();
33093344
if (!nrt)
33103345
goto out;
@@ -4687,6 +4722,7 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
46874722
struct net *net = sock_net(in_skb->sk);
46884723
struct nlattr *tb[RTA_MAX+1];
46894724
int err, iif = 0, oif = 0;
4725+
struct fib6_info *from;
46904726
struct dst_entry *dst;
46914727
struct rt6_info *rt;
46924728
struct sk_buff *skb;
@@ -4783,15 +4819,21 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
47834819
}
47844820

47854821
skb_dst_set(skb, &rt->dst);
4822+
4823+
rcu_read_lock();
4824+
from = rcu_dereference(rt->from);
4825+
47864826
if (fibmatch)
4787-
err = rt6_fill_node(net, skb, rt->from, NULL, NULL, NULL, iif,
4827+
err = rt6_fill_node(net, skb, from, NULL, NULL, NULL, iif,
47884828
RTM_NEWROUTE, NETLINK_CB(in_skb).portid,
47894829
nlh->nlmsg_seq, 0);
47904830
else
4791-
err = rt6_fill_node(net, skb, rt->from, dst,
4792-
&fl6.daddr, &fl6.saddr, iif, RTM_NEWROUTE,
4831+
err = rt6_fill_node(net, skb, from, dst, &fl6.daddr,
4832+
&fl6.saddr, iif, RTM_NEWROUTE,
47934833
NETLINK_CB(in_skb).portid, nlh->nlmsg_seq,
47944834
0);
4835+
rcu_read_unlock();
4836+
47954837
if (err < 0) {
47964838
kfree_skb(skb);
47974839
goto errout;

0 commit comments

Comments
 (0)