Skip to content

Commit f2c31e3

Browse files
Eric Dumazetdavem330
authored andcommitted
net: fix NULL dereferences in check_peer_redir()
Gergely Kalman reported crashes in check_peer_redir(). It appears commit f39925d (ipv4: Cache learned redirect information in inetpeer.) added a race, leading to possible NULL ptr dereference. Since we can now change dst neighbour, we should make sure a reader can safely use a neighbour. Add RCU protection to dst neighbour, and make sure check_peer_redir() can be called safely by different cpus in parallel. As neighbours are already freed after one RCU grace period, this patch should not add typical RCU penalty (cache cold effects) Many thanks to Gergely for providing a pretty report pointing to the bug. Reported-by: Gergely Kalman <[email protected]> Signed-off-by: Eric Dumazet <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 28f4881 commit f2c31e3

File tree

7 files changed

+67
-26
lines changed

7 files changed

+67
-26
lines changed

include/net/dst.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ struct dst_entry {
3737
unsigned long _metrics;
3838
unsigned long expires;
3939
struct dst_entry *path;
40-
struct neighbour *_neighbour;
40+
struct neighbour __rcu *_neighbour;
4141
#ifdef CONFIG_XFRM
4242
struct xfrm_state *xfrm;
4343
#else
@@ -88,12 +88,17 @@ struct dst_entry {
8888

8989
static inline struct neighbour *dst_get_neighbour(struct dst_entry *dst)
9090
{
91-
return dst->_neighbour;
91+
return rcu_dereference(dst->_neighbour);
92+
}
93+
94+
static inline struct neighbour *dst_get_neighbour_raw(struct dst_entry *dst)
95+
{
96+
return rcu_dereference_raw(dst->_neighbour);
9297
}
9398

9499
static inline void dst_set_neighbour(struct dst_entry *dst, struct neighbour *neigh)
95100
{
96-
dst->_neighbour = neigh;
101+
rcu_assign_pointer(dst->_neighbour, neigh);
97102
}
98103

99104
extern u32 *dst_cow_metrics_generic(struct dst_entry *dst, unsigned long old);
@@ -382,8 +387,12 @@ static inline void dst_rcu_free(struct rcu_head *head)
382387
static inline void dst_confirm(struct dst_entry *dst)
383388
{
384389
if (dst) {
385-
struct neighbour *n = dst_get_neighbour(dst);
390+
struct neighbour *n;
391+
392+
rcu_read_lock();
393+
n = dst_get_neighbour(dst);
386394
neigh_confirm(n);
395+
rcu_read_unlock();
387396
}
388397
}
389398

net/ipv4/ip_output.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,15 @@ static inline int ip_finish_output2(struct sk_buff *skb)
204204
skb = skb2;
205205
}
206206

207+
rcu_read_lock();
207208
neigh = dst_get_neighbour(dst);
208-
if (neigh)
209-
return neigh_output(neigh, skb);
209+
if (neigh) {
210+
int res = neigh_output(neigh, skb);
211+
212+
rcu_read_unlock();
213+
return res;
214+
}
215+
rcu_read_unlock();
210216

211217
if (net_ratelimit())
212218
printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");

net/ipv4/route.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,16 +1628,18 @@ static int check_peer_redir(struct dst_entry *dst, struct inet_peer *peer)
16281628
{
16291629
struct rtable *rt = (struct rtable *) dst;
16301630
__be32 orig_gw = rt->rt_gateway;
1631-
struct neighbour *n;
1631+
struct neighbour *n, *old_n;
16321632

16331633
dst_confirm(&rt->dst);
16341634

1635-
neigh_release(dst_get_neighbour(&rt->dst));
1636-
dst_set_neighbour(&rt->dst, NULL);
1637-
16381635
rt->rt_gateway = peer->redirect_learned.a4;
1639-
rt_bind_neighbour(rt);
1640-
n = dst_get_neighbour(&rt->dst);
1636+
1637+
n = ipv4_neigh_lookup(&rt->dst, &rt->rt_gateway);
1638+
if (IS_ERR(n))
1639+
return PTR_ERR(n);
1640+
old_n = xchg(&rt->dst._neighbour, n);
1641+
if (old_n)
1642+
neigh_release(old_n);
16411643
if (!n || !(n->nud_state & NUD_VALID)) {
16421644
if (n)
16431645
neigh_event_send(n, NULL);

net/ipv6/addrconf.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, int pfxlen,
656656
* layer address of our nexhop router
657657
*/
658658

659-
if (dst_get_neighbour(&rt->dst) == NULL)
659+
if (dst_get_neighbour_raw(&rt->dst) == NULL)
660660
ifa->flags &= ~IFA_F_OPTIMISTIC;
661661

662662
ifa->idev = idev;

net/ipv6/ip6_fib.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1455,7 +1455,7 @@ static int fib6_age(struct rt6_info *rt, void *arg)
14551455
RT6_TRACE("aging clone %p\n", rt);
14561456
return -1;
14571457
} else if ((rt->rt6i_flags & RTF_GATEWAY) &&
1458-
(!(dst_get_neighbour(&rt->dst)->flags & NTF_ROUTER))) {
1458+
(!(dst_get_neighbour_raw(&rt->dst)->flags & NTF_ROUTER))) {
14591459
RT6_TRACE("purging route %p via non-router but gateway\n",
14601460
rt);
14611461
return -1;

net/ipv6/ip6_output.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,15 @@ static int ip6_finish_output2(struct sk_buff *skb)
135135
skb->len);
136136
}
137137

138+
rcu_read_lock();
138139
neigh = dst_get_neighbour(dst);
139-
if (neigh)
140-
return neigh_output(neigh, skb);
140+
if (neigh) {
141+
int res = neigh_output(neigh, skb);
141142

143+
rcu_read_unlock();
144+
return res;
145+
}
146+
rcu_read_unlock();
142147
IP6_INC_STATS_BH(dev_net(dst->dev),
143148
ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES);
144149
kfree_skb(skb);
@@ -975,12 +980,14 @@ static int ip6_dst_lookup_tail(struct sock *sk,
975980
* dst entry and replace it instead with the
976981
* dst entry of the nexthop router
977982
*/
983+
rcu_read_lock();
978984
n = dst_get_neighbour(*dst);
979985
if (n && !(n->nud_state & NUD_VALID)) {
980986
struct inet6_ifaddr *ifp;
981987
struct flowi6 fl_gw6;
982988
int redirect;
983989

990+
rcu_read_unlock();
984991
ifp = ipv6_get_ifaddr(net, &fl6->saddr,
985992
(*dst)->dev, 1);
986993

@@ -1000,6 +1007,8 @@ static int ip6_dst_lookup_tail(struct sock *sk,
10001007
if ((err = (*dst)->error))
10011008
goto out_err_release;
10021009
}
1010+
} else {
1011+
rcu_read_unlock();
10031012
}
10041013
#endif
10051014

net/ipv6/route.c

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ static inline struct rt6_info *rt6_device_match(struct net *net,
364364
#ifdef CONFIG_IPV6_ROUTER_PREF
365365
static void rt6_probe(struct rt6_info *rt)
366366
{
367-
struct neighbour *neigh = rt ? dst_get_neighbour(&rt->dst) : NULL;
367+
struct neighbour *neigh;
368368
/*
369369
* Okay, this does not seem to be appropriate
370370
* for now, however, we need to check if it
@@ -373,8 +373,10 @@ static void rt6_probe(struct rt6_info *rt)
373373
* Router Reachability Probe MUST be rate-limited
374374
* to no more than one per minute.
375375
*/
376+
rcu_read_lock();
377+
neigh = rt ? dst_get_neighbour(&rt->dst) : NULL;
376378
if (!neigh || (neigh->nud_state & NUD_VALID))
377-
return;
379+
goto out;
378380
read_lock_bh(&neigh->lock);
379381
if (!(neigh->nud_state & NUD_VALID) &&
380382
time_after(jiffies, neigh->updated + rt->rt6i_idev->cnf.rtr_probe_interval)) {
@@ -387,8 +389,11 @@ static void rt6_probe(struct rt6_info *rt)
387389
target = (struct in6_addr *)&neigh->primary_key;
388390
addrconf_addr_solict_mult(target, &mcaddr);
389391
ndisc_send_ns(rt->rt6i_dev, NULL, target, &mcaddr, NULL);
390-
} else
392+
} else {
391393
read_unlock_bh(&neigh->lock);
394+
}
395+
out:
396+
rcu_read_unlock();
392397
}
393398
#else
394399
static inline void rt6_probe(struct rt6_info *rt)
@@ -412,8 +417,11 @@ static inline int rt6_check_dev(struct rt6_info *rt, int oif)
412417

413418
static inline int rt6_check_neigh(struct rt6_info *rt)
414419
{
415-
struct neighbour *neigh = dst_get_neighbour(&rt->dst);
420+
struct neighbour *neigh;
416421
int m;
422+
423+
rcu_read_lock();
424+
neigh = dst_get_neighbour(&rt->dst);
417425
if (rt->rt6i_flags & RTF_NONEXTHOP ||
418426
!(rt->rt6i_flags & RTF_GATEWAY))
419427
m = 1;
@@ -430,6 +438,7 @@ static inline int rt6_check_neigh(struct rt6_info *rt)
430438
read_unlock_bh(&neigh->lock);
431439
} else
432440
m = 0;
441+
rcu_read_unlock();
433442
return m;
434443
}
435444

@@ -769,7 +778,7 @@ static struct rt6_info *rt6_alloc_clone(struct rt6_info *ort,
769778
rt->rt6i_dst.plen = 128;
770779
rt->rt6i_flags |= RTF_CACHE;
771780
rt->dst.flags |= DST_HOST;
772-
dst_set_neighbour(&rt->dst, neigh_clone(dst_get_neighbour(&ort->dst)));
781+
dst_set_neighbour(&rt->dst, neigh_clone(dst_get_neighbour_raw(&ort->dst)));
773782
}
774783
return rt;
775784
}
@@ -803,7 +812,7 @@ static struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
803812
dst_hold(&rt->dst);
804813
read_unlock_bh(&table->tb6_lock);
805814

806-
if (!dst_get_neighbour(&rt->dst) && !(rt->rt6i_flags & RTF_NONEXTHOP))
815+
if (!dst_get_neighbour_raw(&rt->dst) && !(rt->rt6i_flags & RTF_NONEXTHOP))
807816
nrt = rt6_alloc_cow(rt, &fl6->daddr, &fl6->saddr);
808817
else if (!(rt->dst.flags & DST_HOST))
809818
nrt = rt6_alloc_clone(rt, &fl6->daddr);
@@ -1587,7 +1596,7 @@ void rt6_redirect(const struct in6_addr *dest, const struct in6_addr *src,
15871596
dst_confirm(&rt->dst);
15881597

15891598
/* Duplicate redirect: silently ignore. */
1590-
if (neigh == dst_get_neighbour(&rt->dst))
1599+
if (neigh == dst_get_neighbour_raw(&rt->dst))
15911600
goto out;
15921601

15931602
nrt = ip6_rt_copy(rt, dest);
@@ -1682,7 +1691,7 @@ static void rt6_do_pmtu_disc(const struct in6_addr *daddr, const struct in6_addr
16821691
1. It is connected route. Action: COW
16831692
2. It is gatewayed route or NONEXTHOP route. Action: clone it.
16841693
*/
1685-
if (!dst_get_neighbour(&rt->dst) && !(rt->rt6i_flags & RTF_NONEXTHOP))
1694+
if (!dst_get_neighbour_raw(&rt->dst) && !(rt->rt6i_flags & RTF_NONEXTHOP))
16861695
nrt = rt6_alloc_cow(rt, daddr, saddr);
16871696
else
16881697
nrt = rt6_alloc_clone(rt, daddr);
@@ -2326,6 +2335,7 @@ static int rt6_fill_node(struct net *net,
23262335
struct nlmsghdr *nlh;
23272336
long expires;
23282337
u32 table;
2338+
struct neighbour *n;
23292339

23302340
if (prefix) { /* user wants prefix routes only */
23312341
if (!(rt->rt6i_flags & RTF_PREFIX_RT)) {
@@ -2414,8 +2424,11 @@ static int rt6_fill_node(struct net *net,
24142424
if (rtnetlink_put_metrics(skb, dst_metrics_ptr(&rt->dst)) < 0)
24152425
goto nla_put_failure;
24162426

2417-
if (dst_get_neighbour(&rt->dst))
2418-
NLA_PUT(skb, RTA_GATEWAY, 16, &dst_get_neighbour(&rt->dst)->primary_key);
2427+
rcu_read_lock();
2428+
n = dst_get_neighbour(&rt->dst);
2429+
if (n)
2430+
NLA_PUT(skb, RTA_GATEWAY, 16, &n->primary_key);
2431+
rcu_read_unlock();
24192432

24202433
if (rt->dst.dev)
24212434
NLA_PUT_U32(skb, RTA_OIF, rt->rt6i_dev->ifindex);
@@ -2608,12 +2621,14 @@ static int rt6_info_route(struct rt6_info *rt, void *p_arg)
26082621
#else
26092622
seq_puts(m, "00000000000000000000000000000000 00 ");
26102623
#endif
2624+
rcu_read_lock();
26112625
n = dst_get_neighbour(&rt->dst);
26122626
if (n) {
26132627
seq_printf(m, "%pi6", n->primary_key);
26142628
} else {
26152629
seq_puts(m, "00000000000000000000000000000000");
26162630
}
2631+
rcu_read_unlock();
26172632
seq_printf(m, " %08x %08x %08x %08x %8s\n",
26182633
rt->rt6i_metric, atomic_read(&rt->dst.__refcnt),
26192634
rt->dst.__use, rt->rt6i_flags,

0 commit comments

Comments
 (0)