]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ipv6: Defer fib6_purge_rt() in fib6_add_rt2node() to fib6_add().
authorKuniyuki Iwashima <kuniyu@amazon.com>
Fri, 18 Apr 2025 00:03:54 +0000 (17:03 -0700)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 24 Apr 2025 07:29:56 +0000 (09:29 +0200)
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>
include/net/ip6_fib.h
net/ipv6/ip6_fib.c

index 7c87873ae211c5fa80d34e8f3b8df0e813976390..88b0dd4d8e094d90456fde5b22c086a1e4f5f408 100644 (file)
@@ -198,6 +198,7 @@ struct fib6_info {
                                        fib6_destroying:1,
                                        unused:4;
 
+       struct list_head                purge_link;
        struct rcu_head                 rcu;
        struct nexthop                  *nh;
        struct fib6_nh                  fib6_nh[];
index 79b672f3fc532e5621caec083aef4891bf01e5ee..9e9db5470bbf5d8ed86472ab0702f8a50912ec2b 100644 (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,
-                           struct nl_info *info,
-                           struct netlink_ext_ack *extack)
+                           struct nl_info *info, struct netlink_ext_ack *extack,
+                           struct list_head *purge_list)
 {
        struct fib6_info *leaf = rcu_dereference_protected(fn->leaf,
                                    lockdep_is_held(&rt->fib6_table->tb6_lock));
@@ -1308,10 +1308,9 @@ add:
                }
                nsiblings = iter->fib6_nsiblings;
                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)
                        fn->rr_ptr = NULL;
-               fib6_info_release(iter);
 
                if (nsiblings) {
                        /* Replacing an ECMP route, remove all siblings */
@@ -1324,10 +1323,9 @@ add:
                                if (rt6_qualify_for_ecmp(iter)) {
                                        *ins = iter->fib6_next;
                                        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)
                                                fn->rr_ptr = NULL;
-                                       fib6_info_release(iter);
                                        nsiblings--;
                                        info->nl_net->ipv6.rt6_stats->fib_rt_entries--;
                                } 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 fib6_table *table = rt->fib6_table;
+       LIST_HEAD(purge_list);
        struct fib6_node *fn;
 #ifdef CONFIG_IPV6_SUBTREES
        struct fib6_node *pn = NULL;
@@ -1499,8 +1498,16 @@ int fib6_add(struct fib6_node *root, struct fib6_info *rt,
        }
 #endif
 
-       err = fib6_add_rt2node(fn, rt, info, extack);
+       err = fib6_add_rt2node(fn, rt, info, extack, &purge_list);
        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)
                        list_add(&rt->nh_list, &rt->nh->f6i_list);
                __fib6_update_sernum_upto_root(rt, fib6_new_sernum(info->nl_net));