The underlying wpan device must remain alive while users of the lowpan
netdevice can invoke its neighbour discovery callbacks. lowpan_newlink()
retains wdev, but lowpan_dellink() drops that reference immediately
after unregistering ldev, before ldev's outstanding references have
drained.
With router reachability probing enabled, rt6_probe_deferred() holds
ldev while sending a neighbour solicitation. If the worker selects a
valid link-local source before physical wpan unregister, it can continue
into option sizing after address teardown. The following order is
possible:
1. The RT6 worker enters ndisc_send_ns() with its reference to ldev.
2. ipv6_get_lladdr() copies the source and releases its lock and RCU
read section. The worker is preempted before ndisc_ns_create().
3. Physical wpan unregister calls lowpan_device_event(), which calls
lowpan_dellink(). It unregisters ldev and drops the wdev reference.
4. netdev_run_todo() skips the still-referenced ldev and frees wdev.
5. The worker resumes into lowpan_ndisc_opt_addr_space(), dereferencing
the freed wdev through ldev's stored pointer.
RTNL and synchronize_net() do not join this workqueue execution after
source selection. Out-of-order netdevice finalization therefore permits
a use-after-free read of wdev->ieee802154_ptr and its embedded wpan_dev.
Release the existing wdev reference from ldev's private destructor so it
covers the worker's entire use of ldev. Install the destructor only
after successful registration, preserving the existing reference release
on registration failure. Once ldev's users finish, its destructor drops
the wdev reference and physical device finalization can proceed.
KASAN report as below:
BUG: KASAN: slab-use-after-free in lowpan_ndisc_opt_addr_space+0x324/0x380
Read of size 8 at addr ffff8881077d44c8 by task kworker/0:1/11
CPU: 0 UID: 0 PID: 11 Comm: kworker/0:1 Not tainted
7.2.0-rc5-pmb-bt-functional-v1+ #1 PREEMPT(lazy)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix,
1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: events rt6_probe_deferred
Call Trace:
<TASK>
dump_stack_lvl+0x93/0xd0
print_report+0xce/0x630
? lowpan_ndisc_opt_addr_space+0x324/0x380
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x20d/0x410
? lowpan_ndisc_opt_addr_space+0x324/0x380
kasan_report+0xe0/0x110
? lowpan_ndisc_opt_addr_space+0x324/0x380
? __pfx_lowpan_ndisc_opt_addr_space+0x10/0x10
lowpan_ndisc_opt_addr_space+0x324/0x380
ndisc_ns_create+0x5c9/0x760
? srso_alias_return_thunk+0x5/0xfbef5
? pmbd_probe_hit_cookie+0xee/0x1c0
? __pfx_ndisc_ns_create+0x10/0x10
? __pfx_pmbd_probe_hit_cookie+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? ipv6_get_lladdr+0x287/0x470
ndisc_send_ns+0xae/0x1a0
? __pfx_ndisc_send_ns+0x10/0x10
? __pfx_pmbd_probe_hit_cookie+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? debug_object_deactivate+0x2f8/0x3c0
rt6_probe_deferred+0xf4/0x290
? process_one_work+0x895/0x19c0
? __pfx_rt6_probe_deferred+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
process_one_work+0x908/0x19c0
? __pfx_process_one_work+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? lock_is_held_type+0x8f/0x100
? srso_alias_return_thunk+0x5/0xfbef5
worker_thread+0x65c/0xe40
? srso_alias_return_thunk+0x5/0xfbef5
? __kthread_parkme+0x175/0x220
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_worker_thread+0x10/0x10
kthread+0x34f/0x460
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_kthread+0x10/0x10
ret_from_fork+0x659/0x940
? __pfx_ret_from_fork+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __switch_to+0x74f/0xf80
? __pfx_kthread+0x10/0x10
ret_from_fork_asm+0x1a/0x30
</TASK>
Allocated by task 1:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kvmalloc_node_noprof+0x358/0x900
alloc_netdev_mqs+0xa0/0x1250
ieee802154_if_add+0xdb/0x10e0
ieee802154_register_hw+0x6ef/0xa10
hwsim_add_one+0x699/0x12e0
hwsim_probe+0x2c/0x130
platform_probe+0xbe/0x1d0
really_probe+0x21f/0xa90
__driver_probe_device+0x1ec/0x490
driver_probe_device+0x4f/0x130
__driver_attach+0x248/0x580
bus_for_each_dev+0x143/0x1d0
bus_add_driver+0x2ea/0x660
driver_register+0x155/0x490
hwsim_init_module+0x11f/0x170
do_one_initcall+0xf3/0x4e0
kernel_init_freeable+0x59d/0xae0
kernel_init+0x24/0x280
ret_from_fork+0x659/0x940
ret_from_fork_asm+0x1a/0x30
Freed by task 535:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x5f/0x80
kfree+0x236/0x5a0
device_release+0xd7/0x280
kobject_put+0x1ad/0x610
netdev_run_todo+0x931/0x1460
ieee802154_unregister_hw+0x5f/0x100
hwsim_del+0x304/0x580
hwsim_del_radio_nl+0x158/0x1f0
genl_family_rcv_msg_doit+0x1f6/0x2e0
genl_rcv_msg+0x48b/0x700
netlink_rcv_skb+0x133/0x390
genl_rcv+0x2d/0x40
netlink_unicast+0x504/0x840
netlink_sendmsg+0x7f7/0xcf0
__sys_sendto+0x425/0x470
__x64_sys_sendto+0xe5/0x1c0
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff8881077d4000
which belongs to the cache dyn-kmalloc-8k of size 8192
The buggy address is located 1224 bytes inside of
freed 8192-byte region [ffff8881077d4000, ffff8881077d6000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1077d0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x200000000000040(head|node=0|zone=2)
page_type: f5(slab)
raw: 0200000000000040 ffff888100048280 dead000000000122 0000000000000000
raw: 0000000000000000 0000000000020002 00000000f5000000 0000000000000000
head: 0200000000000040 ffff888100048280 dead000000000122 0000000000000000
head: 0000000000000000 0000000000020002 00000000f5000000 0000000000000000
head: 0200000000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff8881077d4380: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881077d4400: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff8881077d4480: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff8881077d4500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8881077d4580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Fixes: faab39f63c1f ("net: allow out-of-order netdev unregistration")
Signed-off-by: Cen Zhang <[email protected]>
Assisted-by: LLM
---
diff --git a/net/ieee802154/6lowpan/core.c b/net/ieee802154/6lowpan/core.c
index 6a8d6852cb93..42ae2df8b9a4 100644
--- a/net/ieee802154/6lowpan/core.c
+++ b/net/ieee802154/6lowpan/core.c
@@ -67,6 +67,11 @@ static int lowpan_dev_init(struct net_device *ldev)
return 0;
}
+static void lowpan_dev_free(struct net_device *ldev)
+{
+ dev_put(lowpan_802154_dev(ldev)->wdev);
+}
+
static int lowpan_open(struct net_device *dev)
{
if (!open_count)
@@ -181,6 +186,7 @@ static int lowpan_newlink(struct net_device *ldev,
return ret;
}
+ ldev->priv_destructor = lowpan_dev_free;
wdev->ieee802154_ptr->lowpan_dev = ldev;
return 0;
}
@@ -193,7 +199,6 @@ static void lowpan_dellink(struct net_device *ldev, struct
list_head *head)
wdev->ieee802154_ptr->lowpan_dev = NULL;
lowpan_unregister_netdevice(ldev);
- dev_put(wdev);
}
static struct rtnl_link_ops lowpan_link_ops __read_mostly = {