Netdev List
 help / color / mirror / Atom feed
From: Simon Horman <horms@kernel.org>
To: security@auditcode.ai
Cc: Simon Horman <horms@kernel.org>,
	alex.aring@gmail.com, stefan@datenfreihafen.org,
	miquel.raynal@bootlin.com, davem@davemloft.net,
	edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
	linux-wpan@vger.kernel.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, stable@vger.kernel.org
Subject: Re: [PATCH net] mac802154: flush rx_mac_cmd_list before freeing sdata
Date: Thu, 16 Jul 2026 13:49:27 +0100	[thread overview]
Message-ID: <20260716124926.304297-2-horms@kernel.org> (raw)
In-Reply-To: <20260710111353.12138-1-security@auditcode.ai>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
https://netdev-ai.bots.linux.dev/sashiko/
---
mac802154: flush rx_mac_cmd_list before freeing sdata

This patch adds mac802154_flush_queued_mac_cmds() and calls it from
ieee802154_if_remove() and ieee802154_unregister_hw() to drain
local->rx_mac_cmd_list before sdata is freed, aiming to close a KASAN
use-after-free in mac802154_rx_mac_cmd_worker().

> diff --git a/net/mac802154/iface.c b/net/mac802154/iface.c
> index b823720630e7..59a045cccfe9 100644
> --- a/net/mac802154/iface.c
> +++ b/net/mac802154/iface.c
> @@ -694,6 +694,16 @@ void ieee802154_if_remove(struct ieee802154_sub_if_data *sdata)
>  	mutex_unlock(&sdata->local->iflist_mtx);
>  
>  	synchronize_rcu();
> +
> +	/*
> +	 * Drop any rx_mac_cmd_list entry still pointing at this sdata
> +	 * before it is freed below: mac802154_rx_mac_cmd_worker() runs
> +	 * asynchronously on local->mac_wq and derefs mac_pkt->sdata with
> +	 * no liveness check of its own (see mac802154_flush_queued_mac_cmds()
> +	 * for details).
> +	 */
> +	mac802154_flush_queued_mac_cmds(sdata->local, sdata);
> +
>  	unregister_netdevice(sdata->dev);
>  }

[High]
When ieee802154_if_remove() runs to delete one interface, does anything
stop RX softirq on sibling interfaces of the same phy from concurrently
enqueueing into local->rx_mac_cmd_list while the flush is walking it?

The producer path is ieee802154_subif_frame(), reached from the RX
tasklet under rcu_read_lock only, and it unconditionally does:

    mac_pkt->sdata = sdata;
    list_add_tail(&mac_pkt->node, &sdata->local->rx_mac_cmd_list);
    queue_work(sdata->local->mac_wq, &sdata->local->rx_mac_cmd_work);

with no lock on rx_mac_cmd_list. list_del_rcu() + synchronize_rcu()
above only quiesces the sdata being removed; other sdata on the same
local remain in local->interfaces and can keep receiving MAC-command
frames on other CPUs throughout the flush.

> diff --git a/net/mac802154/main.c b/net/mac802154/main.c
> index ea1efef3572a..2f8c57e78db1 100644
> --- a/net/mac802154/main.c
> +++ b/net/mac802154/main.c
> @@ -277,6 +277,17 @@ void ieee802154_unregister_hw(struct ieee802154_hw *hw)
>  	tasklet_kill(&local->tasklet);
>  	flush_workqueue(local->workqueue);
>  
> +	/*
> +	 * tasklet_kill() above stops any further frame reaching
> +	 * ieee802154_subif_frame(), but mac802154_rx_mac_cmd_worker() may
> +	 * still be queued/running on local->mac_wq and derefs the sdata of
> +	 * every interface ieee802154_remove_interfaces() is about to free
> +	 * below. flush_workqueue(local->workqueue) does not cover it --
> +	 * that is the DATA workqueue, not local->mac_wq -- so drain it
> +	 * explicitly first.
> +	 */
> +	mac802154_flush_queued_mac_cmds(local, NULL);
> +
>  	rtnl_lock();
>  
>  	ieee802154_remove_interfaces(local);

[High]
Does tasklet_kill() actually stop any further frame from reaching
ieee802154_subif_frame() here?

tasklet_kill() clears TASKLET_STATE_SCHED and waits for a running
instance to finish, but it does not prevent a subsequent
tasklet_schedule() from a driver's ieee802154_rx_irqsafe() path. If a
driver interrupt fires between mac802154_flush_queued_mac_cmds(local,
NULL) and destroy_workqueue(local->mac_wq) further down, a new
mac_pkt would be added to rx_mac_cmd_list, the worker would be queued
on local->mac_wq, and it would then run against sdatas already freed
by ieee802154_remove_interfaces().

Note this call site also runs before rtnl_lock() a few lines below,
which is relevant to the kernel-doc comment quoted further down.

> diff --git a/net/mac802154/rx.c b/net/mac802154/rx.c
> index cd8f2a11920d..0b167f76cb23 100644
> --- a/net/mac802154/rx.c
> +++ b/net/mac802154/rx.c
> @@ -128,6 +128,48 @@ void mac802154_rx_mac_cmd_worker(struct work_struct *work)
>  	kfree(mac_pkt);
>  }
>  
> +/**
> + * mac802154_flush_queued_mac_cmds - drop pending rx_mac_cmd_list work
> + * @local: the mac802154 device the queue belongs to
> + * @sdata: interface being torn down, or %NULL to flush unconditionally
> + *
> + * Every queued &struct cfg802154_mac_pkt stashes a raw pointer to the
> + * interface it was received on (see ieee802154_subif_frame() below) which
> + * mac802154_rx_mac_cmd_worker() dereferences without ever checking whether
> + * that interface is still alive. Callers must invoke this before freeing
> + * @sdata -- or every interface on @local, when @sdata is %NULL -- so the
> + * worker can never run against freed memory:
> + *
> + *  - cancel_work_sync() waits out a run already in flight. That is still
> + *    safe to let finish because nothing has been freed yet, and it blocks
> + *    any new run from starting for as long as we hold the RTNL.

[Low]
Is this description of cancel_work_sync() accurate?

cancel_work_sync() only guarantees the work is not pending or executing
by the time it returns, and does not itself block future queue_work()
calls. RTNL is not held at the ieee802154_unregister_hw() call site
(the flush is invoked before rtnl_lock() there), and at the
ieee802154_if_remove() site RTNL is a plain mutex that does not disable
BH and does not serialize with the RX softirq path
ieee802154_tasklet_handler() -> ieee802154_rx() ->
__ieee802154_rx_handle_packet() -> ieee802154_subif_frame(), which is
what enqueues into rx_mac_cmd_list and re-arms rx_mac_cmd_work via
queue_work(local->mac_wq, ...).

Could this comment be reworded to describe an invariant the code
actually provides?

> + *  - every list entry pointing at @sdata (all of them, if @sdata is NULL)
> + *    is then dropped so no future run of the worker can see it.
> + *
> + * Mirrors mac802154_flush_queued_beacons() in scan.c, which does not need
> + * the cancel_work_sync() step because its worker never dereferences sdata.
> + */
> +void mac802154_flush_queued_mac_cmds(struct ieee802154_local *local,
> +				     struct ieee802154_sub_if_data *sdata)
> +{
> +	struct cfg802154_mac_pkt *mac_pkt, *tmp;
> +
> +	cancel_work_sync(&local->rx_mac_cmd_work);
> +
> +	list_for_each_entry_safe(mac_pkt, tmp, &local->rx_mac_cmd_list, node) {
> +		if (sdata && mac_pkt->sdata != sdata)
> +			continue;
> +
> +		list_del(&mac_pkt->node);
> +		kfree_skb(mac_pkt->skb);
> +		kfree(mac_pkt);
> +	}
> +
> +	/* Other interfaces on @local may still have entries pending. */
> +	if (!list_empty(&local->rx_mac_cmd_list))
> +		queue_work(local->mac_wq, &local->rx_mac_cmd_work);
> +}

[High]
Given that ieee802154_subif_frame() can still be entered from softirq on
sibling interfaces after cancel_work_sync() returns, what protects
rx_mac_cmd_list during this iteration?

The producer does:

    list_add_tail(&mac_pkt->node, &sdata->local->rx_mac_cmd_list);
    queue_work(sdata->local->mac_wq, &sdata->local->rx_mac_cmd_work);

with no lock. cancel_work_sync() does not block a subsequent
queue_work(), so mac802154_rx_mac_cmd_worker() can start on another CPU
while this function is still iterating. The worker does
list_first_entry_or_null() + list_del() on the same list, and the
iterator here pre-loads tmp = list_next_entry(pos) each round.

Under that shape, could a concurrent list_del() from the worker on the
head entry (a) target the same entry the flush is about to delete
(double kfree() of mac_pkt), (b) free the entry that tmp points at
(use-after-free inside the flush loop), or (c) race with a
list_add_tail() from the producer softirq and leave neighbour pointers
inconsistent?

The trailing:

    if (!list_empty(&local->rx_mac_cmd_list))
        queue_work(local->mac_wq, &local->rx_mac_cmd_work);

also reads list state and re-arms the worker without any lock, which is
a TOCTOU against a softirq list_add_tail() on the same list.

Would either a spin_lock_bh() around every rx_mac_cmd_list mutation
plus the queue_work() (matching the softirq producer), or preventing
new enqueues for the sdata being torn down in ieee802154_subif_frame()
before the flush runs, close this window?

Also, in the multi-interface case ieee802154_if_remove() only quiesces
the sdata being removed with synchronize_rcu(); does this fix actually
address the scenario the commit message describes, where MAC_CMD
traffic is arriving on a sibling interface of the same phy during
NL802154_CMD_DEL_INTERFACE?

      reply	other threads:[~2026-07-16 12:49 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-10 11:13 [PATCH net] mac802154: flush rx_mac_cmd_list before freeing sdata Ibrahim Hashimov
2026-07-16 12:49 ` Simon Horman [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260716124926.304297-2-horms@kernel.org \
    --to=horms@kernel.org \
    --cc=alex.aring@gmail.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-wpan@vger.kernel.org \
    --cc=miquel.raynal@bootlin.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=security@auditcode.ai \
    --cc=stable@vger.kernel.org \
    --cc=stefan@datenfreihafen.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox