Skip to content

Commit 081efd1

Browse files
q2venPaolo Abeni
authored andcommitted
ipv6: Protect nh->f6i_list with spinlock and flag.
We will get rid of RTNL from RTM_NEWROUTE and SIOCADDRT. Then, we may be going to add a route tied to a dying nexthop. The nexthop itself is not freed during the RCU grace period, but if we link a route after __remove_nexthop_fib() is called for the nexthop, the route will be leaked. To avoid the race between IPv6 route addition under RCU vs nexthop deletion under RTNL, let's add a dead flag and protect it and nh->f6i_list with a spinlock. __remove_nexthop_fib() acquires the nexthop's spinlock and sets false to nh->dead, then calls ip6_del_rt() for the linked route one by one without the spinlock because fib6_purge_rt() acquires it later. While adding an IPv6 route, fib6_add() acquires the nexthop lock and checks the dead flag just before inserting the route. Signed-off-by: Kuniyuki Iwashima <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Paolo Abeni <[email protected]>
1 parent accb46b commit 081efd1

File tree

3 files changed

+51
-8
lines changed

3 files changed

+51
-8
lines changed

include/net/nexthop.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ struct nexthop {
152152
u8 protocol; /* app managing this nh */
153153
u8 nh_flags;
154154
bool is_group;
155+
bool dead;
156+
spinlock_t lock; /* protect dead and f6i_list */
155157

156158
refcount_t refcnt;
157159
struct rcu_head rcu;

net/ipv4/nexthop.c

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ static struct nexthop *nexthop_alloc(void)
541541
INIT_LIST_HEAD(&nh->f6i_list);
542542
INIT_LIST_HEAD(&nh->grp_list);
543543
INIT_LIST_HEAD(&nh->fdb_list);
544+
spin_lock_init(&nh->lock);
544545
}
545546
return nh;
546547
}
@@ -2118,7 +2119,7 @@ static void remove_nexthop_group(struct nexthop *nh, struct nl_info *nlinfo)
21182119
/* not called for nexthop replace */
21192120
static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
21202121
{
2121-
struct fib6_info *f6i, *tmp;
2122+
struct fib6_info *f6i;
21222123
bool do_flush = false;
21232124
struct fib_info *fi;
21242125

@@ -2129,13 +2130,24 @@ static void __remove_nexthop_fib(struct net *net, struct nexthop *nh)
21292130
if (do_flush)
21302131
fib_flush(net);
21312132

2132-
/* ip6_del_rt removes the entry from this list hence the _safe */
2133-
list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) {
2133+
spin_lock_bh(&nh->lock);
2134+
2135+
nh->dead = true;
2136+
2137+
while (!list_empty(&nh->f6i_list)) {
2138+
f6i = list_first_entry(&nh->f6i_list, typeof(*f6i), nh_list);
2139+
21342140
/* __ip6_del_rt does a release, so do a hold here */
21352141
fib6_info_hold(f6i);
2142+
2143+
spin_unlock_bh(&nh->lock);
21362144
ipv6_stub->ip6_del_rt(net, f6i,
21372145
!READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode));
2146+
2147+
spin_lock_bh(&nh->lock);
21382148
}
2149+
2150+
spin_unlock_bh(&nh->lock);
21392151
}
21402152

21412153
static void __remove_nexthop(struct net *net, struct nexthop *nh,

net/ipv6/ip6_fib.c

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,8 +1048,14 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
10481048
rt6_flush_exceptions(rt);
10491049
fib6_drop_pcpu_from(rt, table);
10501050

1051-
if (rt->nh && !list_empty(&rt->nh_list))
1052-
list_del_init(&rt->nh_list);
1051+
if (rt->nh) {
1052+
spin_lock(&rt->nh->lock);
1053+
1054+
if (!list_empty(&rt->nh_list))
1055+
list_del_init(&rt->nh_list);
1056+
1057+
spin_unlock(&rt->nh->lock);
1058+
}
10531059

10541060
if (refcount_read(&rt->fib6_ref) != 1) {
10551061
/* This route is used as dummy address holder in some split
@@ -1341,6 +1347,28 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt,
13411347
return 0;
13421348
}
13431349

1350+
static int fib6_add_rt2node_nh(struct fib6_node *fn, struct fib6_info *rt,
1351+
struct nl_info *info, struct netlink_ext_ack *extack,
1352+
struct list_head *purge_list)
1353+
{
1354+
int err;
1355+
1356+
spin_lock(&rt->nh->lock);
1357+
1358+
if (rt->nh->dead) {
1359+
NL_SET_ERR_MSG(extack, "Nexthop has been deleted");
1360+
err = -EINVAL;
1361+
} else {
1362+
err = fib6_add_rt2node(fn, rt, info, extack, purge_list);
1363+
if (!err)
1364+
list_add(&rt->nh_list, &rt->nh->f6i_list);
1365+
}
1366+
1367+
spin_unlock(&rt->nh->lock);
1368+
1369+
return err;
1370+
}
1371+
13441372
static void fib6_start_gc(struct net *net, struct fib6_info *rt)
13451373
{
13461374
if (!timer_pending(&net->ipv6.ip6_fib_timer) &&
@@ -1498,7 +1526,10 @@ int fib6_add(struct fib6_node *root, struct fib6_info *rt,
14981526
}
14991527
#endif
15001528

1501-
err = fib6_add_rt2node(fn, rt, info, extack, &purge_list);
1529+
if (rt->nh)
1530+
err = fib6_add_rt2node_nh(fn, rt, info, extack, &purge_list);
1531+
else
1532+
err = fib6_add_rt2node(fn, rt, info, extack, &purge_list);
15021533
if (!err) {
15031534
struct fib6_info *iter, *next;
15041535

@@ -1508,8 +1539,6 @@ int fib6_add(struct fib6_node *root, struct fib6_info *rt,
15081539
fib6_info_release(iter);
15091540
}
15101541

1511-
if (rt->nh)
1512-
list_add(&rt->nh_list, &rt->nh->f6i_list);
15131542
__fib6_update_sernum_upto_root(rt, fib6_new_sernum(info->nl_net));
15141543

15151544
if (rt->fib6_flags & RTF_EXPIRES)

0 commit comments

Comments
 (0)