All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net] mac802154: hold an interface reference across the scan worker
@ 2026-07-10 14:09 Ibrahim Hashimov
  0 siblings, 0 replies; only message 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] only message in thread

only message in thread, other threads:[~2026-07-10 14:09 UTC | newest]

Thread overview: (only message) (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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.