[PATCH] ASoC: soc-core: fix use-after-free in snd_soc_unbind_card()
From: Matteo Cotifava
Date: Sun Mar 08 2026 - 07:09:51 EST
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@xxxxxxxxx>
---
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