* [PATCH net] mac802154: hold an interface reference across the scan worker
@ 2026-07-10 14:09 Ibrahim Hashimov
2026-07-17 8:57 ` Miquel Raynal
2026-07-17 10:58 ` [PATCH v2] " Ibrahim Hashimov
0 siblings, 2 replies; 4+ messages in thread
From: Ibrahim Hashimov @ 2026-07-10 14:09 UTC (permalink / raw)
To: Alexander Aring, Stefan Schmidt, Miquel Raynal, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, linux-wpan, netdev, linux-kernel, stable
mac802154_scan_worker() captures the scanning sub-interface once under
RCU:
sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(scan_req->wpan_dev);
and then, after rcu_read_unlock() and outside the rtnl, keeps
dereferencing sdata->dev: in the channel-change and restart failure
traces, in mac802154_transmit_beacon_req() (skb->dev = sdata->dev) for
active scans, in the final dev_dbg(), and in the end_scan
mac802154_scan_cleanup_locked() path. Nothing keeps that netdev alive
for the duration of the worker iteration.
A concurrent teardown of the scanning interface -- userspace issuing
NL802154_CMD_DEL_INTERFACE (ieee802154_if_remove() ->
unregister_netdevice()), or a full PHY removal via
ieee802154_unregister_hw() -> ieee802154_remove_interfaces() -- can run
as soon as the worker drops the rtnl between its two short
drv_set_channel()/drv_start() sections. The netdev is not freed
synchronously by unregister_netdevice(): it is queued to net_todo_list
and freed later from netdev_run_todo(), which drops the rtnl mutex
(__rtnl_unlock()) *before* netdev_wait_allrefs_any()/free_netdev(). The
freeing therefore runs with the rtnl not held, on whichever task next
drains net_todo_list. Holding the rtnl in the worker does not prevent
it, and the per-PHY IEEE802154_IS_SCANNING flag does not identify the
specific interface: a subsequent NEW_INTERFACE + TRIGGER_SCAN re-arms
the flag, so a stale worker iteration sails past an is-scanning recheck
and dereferences the already-freed netdev.
Triggering the race requires CAP_NET_ADMIN: both
NL802154_CMD_TRIGGER_SCAN and NL802154_CMD_DEL_INTERFACE are
GENL_ADMIN_PERM, reachable only from the initial user namespace, so
the attacker is a locally privileged (CAP_NET_ADMIN) user, not an
unprivileged local user or a remote peer.
KASAN slab-use-after-free, kworker reading the freed
net_device/ieee802154_sub_if_data (kmalloc-cg-4k) allocated and freed by
the racing NEW_INTERFACE/DEL_INTERFACE task:
BUG: KASAN: slab-use-after-free in mac802154_scan_worker+0x... [mac802154]
Read of size 8 ... by task kworker/u8:N
Workqueue: phy0-mac-cmds mac802154_scan_worker [mac802154]
mac802154_scan_worker
process_one_work
(also hit via ieee802154_mlme_tx_locked() from the beacon-request path
and via _dev_err()/__dev_printk() formatting sdata->dev->dev)
Fix it by taking a reference on the interface while the RCU read lock is
still held -- so the netdev cannot be freed before the refcount is
raised -- and releasing it at every exit of the worker past that point.
This keeps sdata->dev valid for the whole iteration.
The reference does not defer the free indefinitely and does not
deadlock the single-threaded mac_wq. Each worker iteration drops the
reference before it requeues itself, and a teardown started while the
reference is held simply blocks in netdev_run_todo() until the current
iteration returns. It cannot be the worker's own rtnl_unlock() that
blocks on the reference either: the unregistering task removes the
netdev from net_todo_list under the rtnl (list_replace_init() in
netdev_run_todo() runs before __rtnl_unlock()), so it always owns the
todo entry, and the worker acquires the rtnl only afterwards -- the
blocking netdev_wait_allrefs_any() therefore always runs on the
teardown task, never on the worker.
Verified on a v6.19 KASAN build: racing DEL_INTERFACE against an
in-flight TRIGGER_SCAN reliably tripped a slab-use-after-free KASAN
report inside mac802154_scan_worker() before this patch, and the
same reproducer no longer triggers it with the fix applied.
Fixes: 57588c71177f ("mac802154: Handle passive scanning")
Cc: stable@vger.kernel.org
Assisted-by: AuditCode-AI:2026.07
Signed-off-by: Ibrahim Hashimov <security@auditcode.ai>
---
net/mac802154/scan.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c
index 0a31ac8d8415..48e661031cc0 100644
--- a/net/mac802154/scan.c
+++ b/net/mac802154/scan.c
@@ -209,6 +209,26 @@ void mac802154_scan_worker(struct work_struct *work)
return;
}
+ /* From here on sdata->dev is dereferenced after rcu_read_unlock() and
+ * outside the rtnl: in the dev_err()/dev_dbg() traces below, in
+ * mac802154_transmit_beacon_req() (skb->dev = sdata->dev) and in the
+ * end_scan mac802154_scan_cleanup_locked() call. A concurrent teardown
+ * of that interface (NL802154_CMD_DEL_INTERFACE ->
+ * ieee802154_if_remove(), or a full PHY removal via
+ * ieee802154_unregister_hw()) can unregister the netdev; the actual
+ * free then runs asynchronously from netdev_run_todo() with the rtnl
+ * already dropped, so neither holding the rtnl nor the per-PHY
+ * IEEE802154_IS_SCANNING flag keeps sdata->dev alive here. Pin it with
+ * a reference taken while we still hold the RCU read lock (so the
+ * netdev cannot be freed before we bump the refcount) and drop it at
+ * every exit below. This blocks the teardown's netdev_run_todo() until
+ * this worker iteration is done; it cannot self-deadlock because the
+ * unregistering task claims the net_todo_list entry under the rtnl, so
+ * the blocking netdev_wait_allrefs_any() always runs on that task, not
+ * on this single-threaded worker.
+ */
+ dev_hold(sdata->dev);
+
wpan_phy = scan_req->wpan_phy;
scan_req_type = scan_req->type;
scan_req_duration = scan_req->duration;
@@ -262,12 +282,14 @@ void mac802154_scan_worker(struct work_struct *work)
"Scan page %u channel %u for %ums\n",
page, channel, jiffies_to_msecs(scan_duration));
queue_delayed_work(local->mac_wq, &local->scan_work, scan_duration);
+ dev_put(sdata->dev);
return;
end_scan:
rtnl_lock();
mac802154_scan_cleanup_locked(local, sdata, false);
rtnl_unlock();
+ dev_put(sdata->dev);
}
int mac802154_trigger_scan_locked(struct ieee802154_sub_if_data *sdata,
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH net] mac802154: hold an interface reference across the scan worker
2026-07-10 14:09 [PATCH net] mac802154: hold an interface reference across the scan worker Ibrahim Hashimov
@ 2026-07-17 8:57 ` Miquel Raynal
2026-07-17 10:58 ` [PATCH v2] " Ibrahim Hashimov
1 sibling, 0 replies; 4+ messages in thread
From: Miquel Raynal @ 2026-07-17 8:57 UTC (permalink / raw)
To: Ibrahim Hashimov
Cc: Alexander Aring, Stefan Schmidt, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-wpan, netdev,
linux-kernel, stable
Hi Ibrahim,
> + /* From here on sdata->dev is dereferenced after rcu_read_unlock() and
> + * outside the rtnl: in the dev_err()/dev_dbg() traces below, in
> + * mac802154_transmit_beacon_req() (skb->dev = sdata->dev) and in the
> + * end_scan mac802154_scan_cleanup_locked() call. A concurrent teardown
> + * of that interface (NL802154_CMD_DEL_INTERFACE ->
> + * ieee802154_if_remove(), or a full PHY removal via
> + * ieee802154_unregister_hw()) can unregister the netdev; the actual
> + * free then runs asynchronously from netdev_run_todo() with the rtnl
> + * already dropped, so neither holding the rtnl nor the per-PHY
> + * IEEE802154_IS_SCANNING flag keeps sdata->dev alive here. Pin it with
> + * a reference taken while we still hold the RCU read lock (so the
> + * netdev cannot be freed before we bump the refcount) and drop it at
> + * every exit below. This blocks the teardown's netdev_run_todo() until
> + * this worker iteration is done; it cannot self-deadlock because the
> + * unregistering task claims the net_todo_list entry under the rtnl, so
> + * the blocking netdev_wait_allrefs_any() always runs on that task, not
> + * on this single-threaded worker.
> + */
The patch seems correct, the fix as well, but can we trim down this huge
comment please?
Thanks,
Miquèl
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2] mac802154: hold an interface reference across the scan worker
2026-07-10 14:09 [PATCH net] mac802154: hold an interface reference across the scan worker Ibrahim Hashimov
2026-07-17 8:57 ` Miquel Raynal
@ 2026-07-17 10:58 ` Ibrahim Hashimov
2026-07-17 11:37 ` Miquel Raynal
1 sibling, 1 reply; 4+ messages in thread
From: Ibrahim Hashimov @ 2026-07-17 10:58 UTC (permalink / raw)
To: alex.aring, miquel.raynal, stefan
Cc: linux-wpan, netdev, linux-kernel, stable
mac802154_scan_worker() captures the scanning sub-interface once under
RCU:
sdata = IEEE802154_WPAN_DEV_TO_SUB_IF(scan_req->wpan_dev);
and then, after rcu_read_unlock() and outside the rtnl, keeps
dereferencing sdata->dev: in the channel-change and restart failure
traces, in mac802154_transmit_beacon_req() (skb->dev = sdata->dev) for
active scans, in the final dev_dbg(), and in the end_scan
mac802154_scan_cleanup_locked() path. Nothing keeps that netdev alive
for the duration of the worker iteration.
A concurrent teardown of the scanning interface -- userspace issuing
NL802154_CMD_DEL_INTERFACE (ieee802154_if_remove() ->
unregister_netdevice()), or a full PHY removal via
ieee802154_unregister_hw() -> ieee802154_remove_interfaces() -- can run
as soon as the worker drops the rtnl between its two short
drv_set_channel()/drv_start() sections. The netdev is not freed
synchronously by unregister_netdevice(): it is queued to net_todo_list
and freed later from netdev_run_todo(), which drops the rtnl mutex
(__rtnl_unlock()) *before* netdev_wait_allrefs_any()/free_netdev(). The
freeing therefore runs with the rtnl not held, on whichever task next
drains net_todo_list. Holding the rtnl in the worker does not prevent
it, and the per-PHY IEEE802154_IS_SCANNING flag does not identify the
specific interface: a subsequent NEW_INTERFACE + TRIGGER_SCAN re-arms
the flag, so a stale worker iteration sails past an is-scanning recheck
and dereferences the already-freed netdev.
Triggering the race requires CAP_NET_ADMIN: both
NL802154_CMD_TRIGGER_SCAN and NL802154_CMD_DEL_INTERFACE are
GENL_ADMIN_PERM, reachable only from the initial user namespace, so
the attacker is a locally privileged (CAP_NET_ADMIN) user, not an
unprivileged local user or a remote peer.
KASAN slab-use-after-free, kworker reading the freed
net_device/ieee802154_sub_if_data (kmalloc-cg-4k) allocated and freed by
the racing NEW_INTERFACE/DEL_INTERFACE task:
BUG: KASAN: slab-use-after-free in mac802154_scan_worker+0x... [mac802154]
Read of size 8 ... by task kworker/u8:N
Workqueue: phy0-mac-cmds mac802154_scan_worker [mac802154]
mac802154_scan_worker
process_one_work
Fix it by taking a reference on the interface while the RCU read lock is
still held -- so the netdev cannot be freed before the refcount is
raised -- and releasing it at every exit of the worker past that point.
This keeps sdata->dev valid for the whole iteration. The reference does
not defer the free indefinitely: a teardown started while it is held
simply blocks in netdev_run_todo() until the current iteration returns,
and it cannot self-deadlock the single-threaded mac_wq because the
unregistering task claims the net_todo_list entry under the rtnl, so the
blocking netdev_wait_allrefs_any() always runs on that task, not on the
worker.
Verified on a v6.19 KASAN build: racing DEL_INTERFACE against an
in-flight TRIGGER_SCAN reliably tripped a slab-use-after-free KASAN
report inside mac802154_scan_worker() before this patch, and the
same reproducer no longer triggers it with the fix applied.
Fixes: 57588c71177f ("mac802154: Handle passive scanning")
Cc: stable@vger.kernel.org
Signed-off-by: Ibrahim Hashimov <security@auditcode.ai>
Assisted-by: AuditCode-AI:2026.07
---
v2: trim the in-worker comment down to the essentials, as requested by
Miquel Raynal. No functional change.
net/mac802154/scan.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/net/mac802154/scan.c b/net/mac802154/scan.c
index 300d4584533e..5b4ea2a895cc 100644
--- a/net/mac802154/scan.c
+++ b/net/mac802154/scan.c
@@ -209,6 +209,14 @@ void mac802154_scan_worker(struct work_struct *work)
return;
}
+ /*
+ * sdata->dev is dereferenced below after rcu_read_unlock() and outside
+ * the rtnl, and a concurrent DEL_INTERFACE / PHY teardown can free it
+ * asynchronously from netdev_run_todo(). Pin it with a reference taken
+ * while the RCU read lock is still held, and drop it at every exit.
+ */
+ dev_hold(sdata->dev);
+
wpan_phy = scan_req->wpan_phy;
scan_req_type = scan_req->type;
scan_req_duration = scan_req->duration;
@@ -262,12 +270,14 @@ void mac802154_scan_worker(struct work_struct *work)
"Scan page %u channel %u for %ums\n",
page, channel, jiffies_to_msecs(scan_duration));
queue_delayed_work(local->mac_wq, &local->scan_work, scan_duration);
+ dev_put(sdata->dev);
return;
end_scan:
rtnl_lock();
mac802154_scan_cleanup_locked(local, sdata, false);
rtnl_unlock();
+ dev_put(sdata->dev);
}
int mac802154_trigger_scan_locked(struct ieee802154_sub_if_data *sdata,
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v2] mac802154: hold an interface reference across the scan worker
2026-07-17 10:58 ` [PATCH v2] " Ibrahim Hashimov
@ 2026-07-17 11:37 ` Miquel Raynal
0 siblings, 0 replies; 4+ messages in thread
From: Miquel Raynal @ 2026-07-17 11:37 UTC (permalink / raw)
To: Ibrahim Hashimov
Cc: alex.aring, stefan, linux-wpan, netdev, linux-kernel, stable
Hello,
> Fixes: 57588c71177f ("mac802154: Handle passive scanning")
> Cc: stable@vger.kernel.org
> Signed-off-by: Ibrahim Hashimov <security@auditcode.ai>
> Assisted-by: AuditCode-AI:2026.07
Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
Thanks,
Miquèl
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-17 11:37 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 14:09 [PATCH net] mac802154: hold an interface reference across the scan worker Ibrahim Hashimov
2026-07-17 8:57 ` Miquel Raynal
2026-07-17 10:58 ` [PATCH v2] " Ibrahim Hashimov
2026-07-17 11:37 ` Miquel Raynal
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox