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>
This commit is contained in:
Kuniyuki Iwashima 2025-04-17 17:03:54 -07:00 committed by Paolo Abeni
parent 834d97843e
commit accb46b56b
2 changed files with 15 additions and 7 deletions

View File

@ -198,6 +198,7 @@ struct fib6_info {
fib6_destroying:1, fib6_destroying:1,
unused:4; unused:4;
struct list_head purge_link;
struct rcu_head rcu; struct rcu_head rcu;
struct nexthop *nh; struct nexthop *nh;
struct fib6_nh fib6_nh[]; struct fib6_nh fib6_nh[];

View File

@ -1083,8 +1083,8 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
*/ */
static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt, static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt,
struct nl_info *info, struct nl_info *info, struct netlink_ext_ack *extack,
struct netlink_ext_ack *extack) struct list_head *purge_list)
{ {
struct fib6_info *leaf = rcu_dereference_protected(fn->leaf, struct fib6_info *leaf = rcu_dereference_protected(fn->leaf,
lockdep_is_held(&rt->fib6_table->tb6_lock)); lockdep_is_held(&rt->fib6_table->tb6_lock));
@ -1308,10 +1308,9 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt,
} }
nsiblings = iter->fib6_nsiblings; nsiblings = iter->fib6_nsiblings;
iter->fib6_node = NULL; iter->fib6_node = NULL;
fib6_purge_rt(iter, fn, info->nl_net); list_add(&iter->purge_link, purge_list);
if (rcu_access_pointer(fn->rr_ptr) == iter) if (rcu_access_pointer(fn->rr_ptr) == iter)
fn->rr_ptr = NULL; fn->rr_ptr = NULL;
fib6_info_release(iter);
if (nsiblings) { if (nsiblings) {
/* Replacing an ECMP route, remove all siblings */ /* Replacing an ECMP route, remove all siblings */
@ -1324,10 +1323,9 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt,
if (rt6_qualify_for_ecmp(iter)) { if (rt6_qualify_for_ecmp(iter)) {
*ins = iter->fib6_next; *ins = iter->fib6_next;
iter->fib6_node = NULL; iter->fib6_node = NULL;
fib6_purge_rt(iter, fn, info->nl_net); list_add(&iter->purge_link, purge_list);
if (rcu_access_pointer(fn->rr_ptr) == iter) if (rcu_access_pointer(fn->rr_ptr) == iter)
fn->rr_ptr = NULL; fn->rr_ptr = NULL;
fib6_info_release(iter);
nsiblings--; nsiblings--;
info->nl_net->ipv6.rt6_stats->fib_rt_entries--; info->nl_net->ipv6.rt6_stats->fib_rt_entries--;
} else { } else {
@ -1397,6 +1395,7 @@ int fib6_add(struct fib6_node *root, struct fib6_info *rt,
struct nl_info *info, struct netlink_ext_ack *extack) struct nl_info *info, struct netlink_ext_ack *extack)
{ {
struct fib6_table *table = rt->fib6_table; struct fib6_table *table = rt->fib6_table;
LIST_HEAD(purge_list);
struct fib6_node *fn; struct fib6_node *fn;
#ifdef CONFIG_IPV6_SUBTREES #ifdef CONFIG_IPV6_SUBTREES
struct fib6_node *pn = NULL; struct fib6_node *pn = NULL;
@ -1499,8 +1498,16 @@ int fib6_add(struct fib6_node *root, struct fib6_info *rt,
} }
#endif #endif
err = fib6_add_rt2node(fn, rt, info, extack); err = fib6_add_rt2node(fn, rt, info, extack, &purge_list);
if (!err) { if (!err) {
struct fib6_info *iter, *next;
list_for_each_entry_safe(iter, next, &purge_list, purge_link) {
list_del(&iter->purge_link);
fib6_purge_rt(iter, fn, info->nl_net);
fib6_info_release(iter);
}
if (rt->nh) if (rt->nh)
list_add(&rt->nh_list, &rt->nh->f6i_list); list_add(&rt->nh_list, &rt->nh->f6i_list);
__fib6_update_sernum_upto_root(rt, fib6_new_sernum(info->nl_net)); __fib6_update_sernum_upto_root(rt, fib6_new_sernum(info->nl_net));