[PATCH v4 5/5] ALSA: hda: Wait for the files of the codec PCMs before freeing them

From: Cen Zhang

Date: Sat Oct 31 2026 - 01:16:16 EST


The codec remove waits for codec->pcm_ref, which only counts the streams
opened through the HD-audio PCM ops. A file uses the PCM before that:
snd_pcm_open() takes pcm->open_mutex, and an OSS open walks the setup
list of the PCM, before a substream is opened. snd_pcm_release() also
still takes pcm->open_mutex after the close callback has dropped the
reference. When a codec is unbound through sysfs while the card stays
registered, the remove can free the PCMs under such a file:

PCM open codec unbind
-------- ------------
snd_lookup_minor_data()
snd_card_file_add()
snd_hda_codec_disconnect_pcms()
snd_refcount_sync(&codec->pcm_ref)
snd_hda_codec_cleanup_for_unbind()
snd_pcm_free()
mutex_lock(&pcm->open_mutex)

Opening the PCM while the codec is unbound and bound in a loop triggers
it:

BUG: KASAN: slab-use-after-free in mutex_spin_on_owner+0x94/0x2b0
Read of size 8 at addr ffff888005d52a58 by task hdatest/90
Call Trace:
__mutex_lock+0x16a4/0x24a0
snd_pcm_open+0x230/0x600
snd_pcm_playback_open+0x5e/0xb0
Freed by task 94:
kfree+0x2e5/0x510
snd_pcm_free+0x9d/0x150
__snd_device_free+0x121/0x370
snd_hda_codec_cleanup_for_unbind+0x219/0x920
hda_codec_driver_remove+0x1a7/0x2b0
unbind_store+0xd1/0xf0

An open can also get past the wait and call into the codec driver after
the remove has cleared its state, which ends in a NULL pointer
dereference in playback_pcm_open().

After disconnecting the PCMs, wait until the files opened on them are
closed. Do the same on the probe error path, which frees PCMs that a
late probe may already have registered.

Fixes: bcd96557bd0a ("ALSA: hda - Build PCMs and controls at codec driver probe")
Suggested-by: Takashi Iwai <tiwai@xxxxxxx>
Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
---
sound/hda/common/bind.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)

diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c
index de867f34f290..66949c4ff21f 100644
--- a/sound/hda/common/bind.c
+++ b/sound/hda/common/bind.c
@@ -84,6 +84,20 @@ int snd_hda_codec_set_name(struct hda_codec *codec, const char *name)
}
EXPORT_SYMBOL_GPL(snd_hda_codec_set_name);

+/*
+ * A file opened on a codec PCM can use the PCM until it is closed, also
+ * after the PCM was disconnected. Wait for those files.
+ */
+static void hda_codec_sync_pcm_files(struct hda_codec *codec)
+{
+ struct hda_pcm *cpcm;
+
+ list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
+ if (cpcm->pcm)
+ snd_card_file_sync(codec->card, cpcm->pcm);
+ }
+}
+
static int hda_codec_driver_probe(struct device *dev)
{
struct hda_codec *codec = dev_to_hda_codec(dev);
@@ -152,6 +166,8 @@ static int hda_codec_driver_probe(struct device *dev)
error_module:
snd_hdac_device_disable_unsol(&codec->core);
cancel_delayed_work_sync(&codec->jackpoll_work);
+ snd_hda_codec_disconnect_pcms(codec);
+ hda_codec_sync_pcm_files(codec);
if (probed && driver->ops->remove)
driver->ops->remove(codec);
error_module_put:
@@ -182,6 +198,7 @@ static int hda_codec_driver_remove(struct device *dev)
guard(mutex)(&codec->bus->core.codec_bind_lock);
snd_hda_codec_disconnect_pcms(codec);
snd_hda_jack_tbl_disconnect(codec);
+ hda_codec_sync_pcm_files(codec);
snd_refcount_sync(&codec->pcm_ref);
snd_power_sync_ref(codec->bus->card);