[PATCH] ALSA: pcm: Don't release the DMA buffer while it is mmapped

From: bo cao

Date: Tue Nov 03 2026 - 06:41:52 EST


From: Bo Cao <treebacker@xxxxxxxxxxx>

The hw_params state check refuses a re-configuration while the data
buffer is mmapped, but it carries an exemption for OSS emulation
streams. The OSS flag is kept on the shared substream, and a native
ALSA fd attaching to an already-open substream through an O_APPEND
open inherits that flag, so the exemption can be reached without ever
touching the OSS emulation layer itself.

When such a stream is re-configured with a larger buffer size,
snd_pcm_lib_malloc_pages() releases the old DMA buffer pages, and a
failed re-configuration releases them through the hw_params error
path. Neither path checks whether the buffer is still mmapped, so
the pages can be freed while the userspace mapping remains in place,
resulting in a use-after-free of the mapped pages, which is
exploitable for privilege escalation.

Refuse the buffer release while the buffer is still mmapped:

- snd_pcm_lib_malloc_pages() returns -EBADFD instead of freeing
the old buffer when the new size does not fit it and the buffer
is still mmapped (same-size re-configuration, which is what the
OSS emulation layer itself does while mmapped, keeps working),

- snd_pcm_lib_free_pages() refuses to release a buffer that is
still mmapped, which also covers the hw_params error path and
drivers freeing through it,

- a buffer retained this way by a failed hw_params is released at
the final close of the substream instead of leaking.

Fixes: 0df63e44c3e3 ("[ALSA] Add O_APPEND flag support to PCM")
Cc: stable@xxxxxxxxxxxxxxx
Reported-by: Bo Cao <treebacker@xxxxxxxxxxx>
Tested-by: Bo Cao <treebacker@xxxxxxxxxxx>
Signed-off-by: Bo Cao <treebacker@xxxxxxxxxxx>

---
Note for the list: this issue was originally reported privately to
security@xxxxxxxxxx; an accidental CC let this patch reach the stable
list early, so it is now submitted through the normal process at the
security team's request.

A word of caution on my part: I am not a professional kernel
developer -- the analysis comes from an independent security research
effort -- so please treat this as a starting point rather than a
finished fix. In particular, review of the snd_pcm_release_substream()
hunk (which ensures a buffer retained by a failed hw_params does not
leak) and of any OSS emulation behavior I may have missed would be
appreciated. I am happy to iterate on a v2 with whatever approach is
preferred.

Tested on linux 7.4-rc1 (arm64 QEMU, snd-intel8x0): a reproducer
fails cleanly after the fix -- the second hw_params returns -EBADFD
and the mmapped pages are not freed.

sound/core/pcm_memory.c | 8 ++++++++
sound/core/pcm_native.c | 6 ++++++
2 files changed, 14 insertions(+)

diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c
index cfe9f10e7359..ed3da74e2cf5 100644
--- a/sound/core/pcm_memory.c
+++ b/sound/core/pcm_memory.c
@@ -439,6 +439,11 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size)
runtime->dma_bytes = size;
return 0; /* ok, do not change */
}
+ /* the old buffer may still be mmapped: refuse instead of
+ * freeing it behind the userspace mapping
+ */
+ if (atomic_read(&substream->mmap_count))
+ return -EBADFD;
snd_pcm_lib_free_pages(substream);
}
if (substream->dma_buffer.area != NULL &&
@@ -488,6 +493,9 @@ int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream)
runtime = substream->runtime;
if (runtime->dma_area == NULL)
return 0;
+ /* the buffer may still be mmapped: refuse to release it */
+ if (atomic_read(&substream->mmap_count))
+ return -EBADFD;
if (runtime->dma_buffer_p != &substream->dma_buffer) {
struct snd_card *card = substream->pcm->card;

diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 5cbf88da7626..8e337983f2ec 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -2851,6 +2851,12 @@ void snd_pcm_release_substream(struct snd_pcm_substream *substream)
snd_refcount_sync(&substream->runtime->refcnt);
if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
do_hw_free(substream);
+ else if (substream->managed_buffer_alloc)
+ /* a failed hw_params may have left the buffer
+ * allocated (it was still mmapped at the time);
+ * release it now that the mapping is gone
+ */
+ snd_pcm_lib_free_pages(substream);
substream->ops->close(substream);
substream->hw_opened = 0;
}
--
2.39.5