Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ipv6: Defer fib6_purge_rt() in fib6_add_rt2node() to fib6_add().

The next patch adds per-nexthop spinlock which protects nh->f6i_list.

When rt->nh is not NULL, fib6_add_rt2node() will be called under the lock.
fib6_add_rt2node() could call fib6_purge_rt() for another route, which
could holds another nexthop lock.

Then, deadlock could happen between two nexthops.

Let's defer fib6_purge_rt() after fib6_add_rt2node().

Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
Link: https://patch.msgid.link/20250418000443.43734-14-kuniyu@amazon.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>

authored by

Kuniyuki Iwashima and committed by
Paolo Abeni
accb46b5 834d9784

+15 -7
+1
include/net/ip6_fib.h
··· 198 198 fib6_destroying:1, 199 199 unused:4; 200 200 201 + struct list_head purge_link; 201 202 struct rcu_head rcu; 202 203 struct nexthop *nh; 203 204 struct fib6_nh fib6_nh[];
+14 -7
net/ipv6/ip6_fib.c
··· 1083 1083 */ 1084 1084 1085 1085 static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt, 1086 - struct nl_info *info, 1087 - struct netlink_ext_ack *extack) 1086 + struct nl_info *info, struct netlink_ext_ack *extack, 1087 + struct list_head *purge_list) 1088 1088 { 1089 1089 struct fib6_info *leaf = rcu_dereference_protected(fn->leaf, 1090 1090 lockdep_is_held(&rt->fib6_table->tb6_lock)); ··· 1308 1308 } 1309 1309 nsiblings = iter->fib6_nsiblings; 1310 1310 iter->fib6_node = NULL; 1311 - fib6_purge_rt(iter, fn, info->nl_net); 1311 + list_add(&iter->purge_link, purge_list); 1312 1312 if (rcu_access_pointer(fn->rr_ptr) == iter) 1313 1313 fn->rr_ptr = NULL; 1314 - fib6_info_release(iter); 1315 1314 1316 1315 if (nsiblings) { 1317 1316 /* Replacing an ECMP route, remove all siblings */ ··· 1323 1324 if (rt6_qualify_for_ecmp(iter)) { 1324 1325 *ins = iter->fib6_next; 1325 1326 iter->fib6_node = NULL; 1326 - fib6_purge_rt(iter, fn, info->nl_net); 1327 + list_add(&iter->purge_link, purge_list); 1327 1328 if (rcu_access_pointer(fn->rr_ptr) == iter) 1328 1329 fn->rr_ptr = NULL; 1329 - fib6_info_release(iter); 1330 1330 nsiblings--; 1331 1331 info->nl_net->ipv6.rt6_stats->fib_rt_entries--; 1332 1332 } else { ··· 1395 1397 struct nl_info *info, struct netlink_ext_ack *extack) 1396 1398 { 1397 1399 struct fib6_table *table = rt->fib6_table; 1400 + LIST_HEAD(purge_list); 1398 1401 struct fib6_node *fn; 1399 1402 #ifdef CONFIG_IPV6_SUBTREES 1400 1403 struct fib6_node *pn = NULL; ··· 1498 1499 } 1499 1500 #endif 1500 1501 1501 - err = fib6_add_rt2node(fn, rt, info, extack); 1502 + err = fib6_add_rt2node(fn, rt, info, extack, &purge_list); 1502 1503 if (!err) { 1504 + struct fib6_info *iter, *next; 1505 + 1506 + list_for_each_entry_safe(iter, next, &purge_list, purge_link) { 1507 + list_del(&iter->purge_link); 1508 + fib6_purge_rt(iter, fn, info->nl_net); 1509 + fib6_info_release(iter); 1510 + } 1511 + 1503 1512 if (rt->nh) 1504 1513 list_add(&rt->nh_list, &rt->nh->f6i_list); 1505 1514 __fib6_update_sernum_upto_root(rt, fib6_new_sernum(info->nl_net));