mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
ipv6: Flush rt->from in rt6_uncached_list_flush_dev().
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: 93531c6743 ("net/ipv6: separate handling of FIB entries from dst based routes")
Reported-by: Jason Zhang <jdz@google.com>
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/20261101064418.2055158-3-kuniyu@google.com
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
parent
9401b428b5
commit
a0fe1a3f39
|
|
@ -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 &&
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user