⬢ DragonFlyBSD Kernel Audit
DF-4026 / fix.diff
← back to finding ↓ download raw
Fix for DF-4026: mmap-vs-chn_resizebuf use-after-free.

dsp_mmap_single() maps bs->buf under the channel lock; chn_resizebuf()
checks CHN_F_MMAP only at entry and then drops the lock (parent-notify
window, SETBLOCKSIZE window, sndbuf_remalloc kmalloc window) before
freeing the old buffer.  A mapper that acquires the lock inside one of
those windows gets a mapping of a buffer freed moments later.

Two complementary guards: (1) chn_resizebuf() re-checks CHN_F_MMAP
after the last relock, right before sndbuf_remalloc(); (2)
sndbuf_remalloc() re-checks CHN_F_MMAP after its kmalloc-window relock
and keeps the old (possibly mapped) buffers if a mapper appeared.
--- a/sys/dev/sound/pcm/channel.c	2026-09-17 16:38:58.624936172 +0000
+++ b/sys/dev/sound/pcm/channel.c	2026-09-17 16:38:58.648935866 +0000
@@ -1905,6 +1905,15 @@
 
 	sblksz -= sblksz % sndbuf_getalign(bs);
 
+	/*
+	 * DF-4026: the lock was dropped above (parent notify and
+	 * SETBLOCKSIZE windows); a concurrent dsp_mmap_single() may
+	 * have mapped the current buffers in the meantime.  Never
+	 * free buffers that are mapped.
+	 */
+	if (c->flags & CHN_F_MMAP)
+		return (EINVAL);
+
 	if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz ||
 	    sndbuf_getsize(bs) != (sblkcnt * sblksz)) {
 		ret = sndbuf_remalloc(bs, sblkcnt, sblksz);
--- a/sys/dev/sound/pcm/buffer.c	2026-09-17 16:38:58.624936172 +0000
+++ b/sys/dev/sound/pcm/buffer.c	2026-09-17 16:38:58.660935713 +0000
@@ -226,6 +226,18 @@
 		tmpbuf = kmalloc(allocsize, M_DEVBUF, M_WAITOK);
 		shadbuf = kmalloc(allocsize, M_DEVBUF, M_WAITOK);
 		CHN_LOCK(b->channel);
+		/*
+		 * DF-4026: a concurrent dsp_mmap_single() may have
+		 * acquired the channel lock while we were allocating
+		 * and mapped the *current* (old) buffers.  Do not free
+		 * them in that case; discard the new allocations.
+		 */
+		if (b->channel->flags & CHN_F_MMAP) {
+			kfree(buf, M_DEVBUF);
+			kfree(tmpbuf, M_DEVBUF);
+			kfree(shadbuf, M_DEVBUF);
+			return (EINVAL);
+		}
 		if (b->buf != NULL)
 			kfree(b->buf, M_DEVBUF);
 		b->buf = buf;