[PATCH] ASoC: soc-core: fix use-after-free in snd_soc_unbind_card()

Matteo Cotifava posted 1 patch 1 month ago
sound/soc/soc-core.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
[PATCH] ASoC: soc-core: fix use-after-free in snd_soc_unbind_card()
Posted by Matteo Cotifava 1 month ago
When a sound card is unbound (e.g. on module removal) while a PCM
stream close is pending, a race between the close delayed work and
soc_cleanup_card_resources() can cause a use-after-free:

  BUG: KASAN: use-after-free in snd_soc_dapm_stream_event+0x39c/0x430
  Read of size 8 at addr ffff00000a29a918 by task kworker/u8:0/8

  CPU: 2 PID: 8 Comm: kworker/u8:0 Not tainted 5.15.71 #1
  Hardware name: NXP i.MX8MM
  Workqueue: events_power_efficient close_delayed_work
  Call trace:
   snd_soc_dapm_stream_event+0x39c/0x430
   snd_soc_close_delayed_work+0x208/0x2ac
   close_delayed_work+0x3c/0x54
   process_one_work+0x670/0xfd4
   worker_thread+0x84c/0xe74
   kthread+0x370/0x410
   ret_from_fork+0x10/0x20

  Allocated by task 8:
   snd_soc_dapm_new_control_unlocked+0x2c/0xbc4
   snd_soc_dapm_new_dai_widgets+0x12c/0x35c
   soc_probe_component+0x418/0xbb0
   snd_soc_bind_card+0xa38/0x2200

  Freed by task 488:
   snd_soc_dapm_free_widget+0x384/0x520
   snd_soc_dapm_free+0x11c/0x284
   soc_remove_component+0xd4/0x1b0
   soc_cleanup_card_resources+0x184/0x700
   snd_soc_unregister_card+0x1dc/0x240
   __device_release_driver+0x2b0/0x584
   driver_detach+0x190/0x290
   asoc_simple_card_exit+0x18/0x30 [snd_soc_simple_card]
   __arm64_sys_delete_module+0x2f0/0x4b0

The PCM close path schedules rtd->delayed_work with a timer delay
(pmdown_time). snd_soc_unbind_card() calls
snd_soc_flush_all_delayed_work(), but flush_delayed_work() does not
execute if the delayed work timer has not fired yet and the work item
has not been enqueued in the workqueue. Cleanup then frees DAPM
widgets, after which the timer fires and the work runs against freed
memory.

Replace snd_soc_flush_all_delayed_work() with a new
snd_soc_cancel_all_delayed_work() in the unbind path to guarantee
that any pending or running delayed work is cancelled or awaited
before card resources are released.

Also fix soc_free_pcm_runtime() to use cancel_delayed_work_sync()
unconditionally instead of the racy conditional flush. The original
check of delayed_work_pending() followed by flush_delayed_work() has
a time window where the work can become pending between the two
calls.

Fixes: e894efef9ac7 ("ASoC: core: add support to card rebind")
Fixes: 9c9b65203492 ("ASoC: core: only flush inited work during free")
Signed-off-by: Matteo Cotifava <cotifavamatteo@gmail.com>
---
 sound/soc/soc-core.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index d0fffef65daf..0f459679d459 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -462,8 +462,7 @@ static void soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd)
 
 	list_del(&rtd->list);
 
-	if (delayed_work_pending(&rtd->delayed_work))
-		flush_delayed_work(&rtd->delayed_work);
+	cancel_delayed_work_sync(&rtd->delayed_work);
 	snd_soc_pcm_component_free(rtd);
 
 	/*
@@ -616,6 +615,14 @@ static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card)
 		flush_delayed_work(&rtd->delayed_work);
 }
 
+static void snd_soc_cancel_all_delayed_work(struct snd_soc_card *card)
+{
+	struct snd_soc_pcm_runtime *rtd;
+
+	for_each_card_rtds(card, rtd)
+		cancel_delayed_work_sync(&rtd->delayed_work);
+}
+
 #ifdef CONFIG_PM_SLEEP
 static void soc_playback_digital_mute(struct snd_soc_card *card, int mute)
 {
@@ -2149,7 +2156,7 @@ static void snd_soc_unbind_card(struct snd_soc_card *card)
 {
 	if (snd_soc_card_is_instantiated(card)) {
 		card->instantiated = false;
-		snd_soc_flush_all_delayed_work(card);
+		snd_soc_cancel_all_delayed_work(card);
 
 		soc_cleanup_card_resources(card);
 	}
-- 
2.39.5
Re: [PATCH] ASoC: soc-core: fix use-after-free in snd_soc_unbind_card()
Posted by Mark Brown 1 month ago
On Sun, Mar 08, 2026 at 12:09:31PM +0100, Matteo Cotifava wrote:

> snd_soc_flush_all_delayed_work(), but flush_delayed_work() does not
> execute if the delayed work timer has not fired yet and the work item
> has not been enqueued in the workqueue. Cleanup then frees DAPM

That's exactly what flush_delayed_work() is supposed to do?  Are you
sure whatever you're seeing isn't that something is managing to schedule
new work after the cancellations?

> Replace snd_soc_flush_all_delayed_work() with a new
> snd_soc_cancel_all_delayed_work() in the unbind path to guarantee
> that any pending or running delayed work is cancelled or awaited
> before card resources are released.

> Also fix soc_free_pcm_runtime() to use cancel_delayed_work_sync()
> unconditionally instead of the racy conditional flush. The original
> check of delayed_work_pending() followed by flush_delayed_work() has
> a time window where the work can become pending between the two
> calls.

These are two separate changes which should be in two separate commits.

> -	if (delayed_work_pending(&rtd->delayed_work))
> -		flush_delayed_work(&rtd->delayed_work);
> +	cancel_delayed_work_sync(&rtd->delayed_work);

This now guarantees that we don't execute any queued work, presumably
something was expecting it to do something...  We should probably drop
the check for pending work since flush ought to be safe if nothing was
scheduled, but it's not clear why you're jumping to cancellation.
[PATCH v2 0/2] ASoC: soc-core: fix use-after-free in close_delayed_work
Posted by matteo.cotifava 1 month ago
Fix a use-after-free in snd_soc_dapm_stream_event() triggered when a
sound card is unbound while a PCM close delayed work is pending.

As Mark pointed out in v1 review, flush_delayed_work() does handle
pending timers correctly. The actual issue appears to be new work
getting scheduled after the flush: snd_card_disconnect_sync() inside
soc_cleanup_card_resources() can trigger PCM closes which call
snd_soc_dapm_stream_stop(), scheduling new delayed work after the
flush in snd_soc_unbind_card() has already completed. If the timer
fires after soc_remove_link_components() frees the DAPM widgets,
the work accesses freed memory.

v1 -> v2:
- Split into two patches as requested
- Dropped cancel_delayed_work_sync() approach, kept flush as suggested
- Added a flush in soc_cleanup_card_resources() after disconnect_sync
  (so no new work can be scheduled) and before DAIs/widgets are freed

matteo.cotifava (2):
  ASoC: soc-core: drop delayed_work_pending() check before flush
  ASoC: soc-core: flush delayed work before removing DAIs and widgets

 sound/soc/soc-core.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

-- 
2.39.5
Re: [PATCH v2 0/2] ASoC: soc-core: fix use-after-free in close_delayed_work
Posted by Mark Brown 1 month ago
On Mon, 09 Mar 2026 22:54:10 +0100, matteo.cotifava wrote:
> Fix a use-after-free in snd_soc_dapm_stream_event() triggered when a
> sound card is unbound while a PCM close delayed work is pending.
> 
> As Mark pointed out in v1 review, flush_delayed_work() does handle
> pending timers correctly. The actual issue appears to be new work
> getting scheduled after the flush: snd_card_disconnect_sync() inside
> soc_cleanup_card_resources() can trigger PCM closes which call
> snd_soc_dapm_stream_stop(), scheduling new delayed work after the
> flush in snd_soc_unbind_card() has already completed. If the timer
> fires after soc_remove_link_components() frees the DAPM widgets,
> the work accesses freed memory.
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next

Thanks!

[1/2] ASoC: soc-core: drop delayed_work_pending() check before flush
      commit: 3c99c9f0ed60582c1c9852b685d78d5d3a50de63
[2/2] ASoC: soc-core: flush delayed work before removing DAIs and widgets
      commit: 95bc5c225513fc3c4ce169563fb5e3929fbb938b

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark
Re: [PATCH v2 0/2] ASoC: soc-core: fix use-after-free in close_delayed_work
Posted by Mark Brown 1 month ago
On Mon, Mar 09, 2026 at 10:54:10PM +0100, matteo.cotifava wrote:
> Fix a use-after-free in snd_soc_dapm_stream_event() triggered when a
> sound card is unbound while a PCM close delayed work is pending.

Please don't send new patches in reply to old patches or serieses, this
makes it harder for both people and tools to understand what is going
on - it can bury things in mailboxes and make it difficult to keep track
of what current patches are, both for the new patches and the old ones.
[PATCH v2 1/2] ASoC: soc-core: drop delayed_work_pending() check before flush
Posted by matteo.cotifava 1 month ago
The delayed_work_pending() check before flush_delayed_work() in
soc_free_pcm_runtime() is unnecessary and racy. flush_delayed_work()
is safe to call unconditionally - it is a no-op when no work is
pending. Remove the check.

The original check was added by commit 9c9b65203492 ("ASoC: core:
only flush inited work during free") but delayed_work_pending()
followed by flush_delayed_work() has a time-of-check/time-of-use
window where work can become pending between the two calls.

Fixes: 9c9b65203492 ("ASoC: core: only flush inited work during free")
Signed-off-by: Matteo Cotifava <cotifavamatteo@gmail.com>
---
 sound/soc/soc-core.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index d0fffef65daf..e5ac8ae1665d 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -462,8 +462,7 @@ static void soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd)
 
 	list_del(&rtd->list);
 
-	if (delayed_work_pending(&rtd->delayed_work))
-		flush_delayed_work(&rtd->delayed_work);
+	flush_delayed_work(&rtd->delayed_work);
 	snd_soc_pcm_component_free(rtd);
 
 	/*
-- 
2.39.5
[PATCH v2 2/2] ASoC: soc-core: flush delayed work before removing DAIs and widgets
Posted by matteo.cotifava 1 month ago
When a sound card is unbound while a PCM stream is open, a
use-after-free can occur in snd_soc_dapm_stream_event(), called from
the close_delayed_work workqueue handler.

During unbind, snd_soc_unbind_card() flushes delayed work and then
calls soc_cleanup_card_resources(). Inside cleanup,
snd_card_disconnect_sync() releases all PCM file descriptors, and
the resulting PCM close path can call snd_soc_dapm_stream_stop()
which schedules new delayed work with a pmdown_time timer delay.
Since this happens after the flush in snd_soc_unbind_card(), the
new work is not caught. soc_remove_link_components() then frees
DAPM widgets before this work fires, leading to the use-after-free.

The existing flush in soc_free_pcm_runtime() also cannot help as it
runs after soc_remove_link_components() has already freed the widgets.

Add a flush in soc_cleanup_card_resources() after
snd_card_disconnect_sync() (after which no new PCM closes can
schedule further delayed work) and before soc_remove_link_dais()
and soc_remove_link_components() (which tear down the structures the
delayed work accesses).

Fixes: e894efef9ac7 ("ASoC: core: add support to card rebind")
Signed-off-by: Matteo Cotifava <cotifavamatteo@gmail.com>
---
 sound/soc/soc-core.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index e5ac8ae1665d..cf826c2a8b59 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -2121,6 +2121,9 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card)
 	for_each_card_rtds(card, rtd)
 		if (rtd->initialized)
 			snd_soc_link_exit(rtd);
+	/* flush delayed work before removing DAIs and DAPM widgets */
+	snd_soc_flush_all_delayed_work(card);
+
 	/* remove and free each DAI */
 	soc_remove_link_dais(card);
 	soc_remove_link_components(card);
-- 
2.39.5
Re: [PATCH] ASoC: soc-core: fix use-after-free in snd_soc_unbind_card()
Posted by matteo.cotifava 1 month ago
On Mon, Mar 09, 2026 at 03:01:40PM +0000, Mark Brown wrote:
> That's exactly what flush_delayed_work() is supposed to do?  Are you
> sure whatever you're seeing isn't that something is managing to schedule
> new work after the cancellations?

You're right, I was wrong about flush_delayed_work() in v1.

Looking at it more carefully, I believe the issue is exactly what you
suggested: new work gets scheduled after the flush. Specifically,
snd_card_disconnect_sync() inside soc_cleanup_card_resources() can
trigger PCM closes which call snd_soc_dapm_stream_stop(), scheduling
new delayed work after the flush in snd_soc_unbind_card() has already
completed.

> These are two separate changes which should be in two separate commits.

Agreed, split in v2.

> This now guarantees that we don't execute any queued work, presumably
> something was expecting it to do something...

Dropped the cancel approach entirely. v2 keeps flush and adds a second
one in soc_cleanup_card_resources() after snd_card_disconnect_sync()
(so no new work can be scheduled) and before DAIs/widgets are freed.

v2 incoming.

Thanks,
Matteo