sound/soc/soc-core.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-)
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
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.
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
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
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.
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
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
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
© 2016 - 2026 Red Hat, Inc.