[PATCH v1 net 2/3] ipv6: Flush rt->from in rt6_uncached_list_flush_dev().
flat view
HOTtoday
From: Kuniyuki Iwashima <kuniyu@google.com>
Date: 2026-11-01 06:44:22
Subsystem:
networking [general], networking [ipv4/ipv6], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds
When a device is unregistered, rt6_uncached_list_flush_dev() iterates over uncached routes and switches the dying dev with blackhole_netdev. However, rt->from could be cached via sk->sk_rx_dst and hold the device reference. Then, unregister_netdevice() gets stuck with the splat below. [0] Let's release rt->from in rt6_uncached_list_flush_dev(). Note that ip6_rt_copy_init() could set rt->from even if in6_dev_get() returns NULL (e.g. mtu < IPV6_MIN_MTU is set concurrently). Also, rt->from->nh could be replaced with another device than rt_dev and rt_idev->dev, and in such a case, nexthop_flush_dev() sets fib6_destroying before rt6_uncached_list_flush_dev(). [0]: unregister_netdevice: waiting for dummy2 to become free. Usage count = 2 ref_tracker: netdev@000000006fb9da8c has 1/1 users at netdev_get_by_index+0x14c/0x170 fib6_nh_init+0x1ae/0x1950 ip6_route_info_create_nh+0x15d/0x780 ip6_route_add+0x55/0x150 inet6_rtm_newroute+0x15d/0x1360 rtnetlink_rcv_msg+0x606/0x710 netlink_rcv_skb+0x14f/0x2f0 netlink_unicast+0x71b/0x940 netlink_sendmsg+0x595/0x840 __sock_sendmsg+0x119/0x150 ____sys_sendmsg+0x3e9/0x5c0 ___sys_sendmsg+0x157/0x1a0 __x64_sys_sendmsg+0x119/0x170 do_syscall_64+0x109/0x330 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: 93531c674315 ("net/ipv6: separate handling of FIB entries from dst based routes") Reported-by: Jason Zhang <redacted> Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com> --- net/ipv6/route.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index fae698c5522b..2eb884558c24 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c@@ -179,12 +179,20 @@ void rt6_uncached_list_flush_dev(struct net_device *dev) list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) { struct inet6_dev *rt_idev = rt->rt6i_idev; struct net_device *rt_dev = rt->dst.dev; + struct fib6_info *from; + bool release_fib; + + rcu_read_lock(); + from = rcu_dereference(rt->from); + release_fib = from && from->fib6_destroying; + rcu_read_unlock(); if (rt_idev && (dev ? rt_idev->dev == dev : READ_ONCE(rt_idev->dev->reg_state) == NETREG_UNREGISTERED)) { rt->rt6i_idev = in6_dev_get(blackhole_netdev); in6_dev_put(rt_idev); + release_fib = true; } if (dev ? rt_dev == dev :
@@ -193,6 +201,12 @@ void rt6_uncached_list_flush_dev(struct net_device *dev) netdev_ref_replace(rt_dev, blackhole_netdev, &rt->dst.dev_tracker, GFP_ATOMIC); + release_fib = true; + } + + if (release_fib) { + from = unrcu_pointer(xchg(&rt->from, NULL)); + fib6_info_release(from); } if (rt->dst.dev == blackhole_netdev &&
--
2.56.0.317.g454f8302e1-goog