56; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1781032747; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=9xrjMpke3SWdY61aKvkSaa3s5X+JTnyPTMGRQchlp6E=; b=baaYyCcLoRF/rFH4/TAQb3bUMAiNcr6VADvhkjNUOXSVQojeeXQKiVlQ0FpBXVnMZz+I1i v48LApXbIMHGqdUAtadu11Vd2K1KeG6Xn3hX8qeuArKlL6kF59cNaHwfmN+bXiSXlHtHBo LvEA4aZEmEtL6rx99/J7hbA7K3QMG7xyPtHbTDnDGqnm38ifuhx1oJ/SjZwp5TNL8WpvFq JNHII6/pGr/F+iOZ2ylFAUPTfodVYP9Ql9xis/3hO4uMGxWBOePe2CUzwQtuD2LaJ2IVzi WmYtc491VVO6ydLQsQ2JxlNlEV5X41dEk11kmu/SIuplQbnwIJrpN3P4myhYHQ== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1781032747; a=rsa-sha256; cv=none; b=BZj+t/FTkwBAy+8vdb5aOwpnpPusne58XlBODeKRSfl4ooq24WWTLQyck8Ly4rn451vQ04 c7ZLXmxyUTFEe9zud5z1UfuaJmf6zXmIhUw4vA+JZUhVzRSnMxFILVnSSfu2mdZvs4+t4C 4bUTdUPZTLDv8lnpI+YhSGYrVwUfs1AIpwGkVCRb0AveqzmEFEFAtxRGG4R9xAGFXZSzNl LZHfYFKUSs8KtnbAtXB9ci0G8nIQU9aTt0ooWxMdxlTZPfEBo/x2FKwFZnbwz0kmOmlesD m3Dk/LppOFtLCz7jxnQ2Be4Tje9PmPn6C39xRzEO/hOpfhfqHLK1FMu0UQKqmw== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1781032747; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=9xrjMpke3SWdY61aKvkSaa3s5X+JTnyPTMGRQchlp6E=; b=jBExgbcNcquKDaZ1P/IlOFXikhClA4aEN/lXMgxgxlRT9bNt4lJ4AFDj2z7L5KzbB6Nb6p 1wpKNFiCRYp+IKmdp1LfUE/7GZpj5u+lzM2dQvGPex4XPVVQhUQsZIJz8ZFrMzu5xVBO7A bs1vQbkEGEOO2ftUJy1P4shJk+lNc/pQWiSdIab0+C/HWXRBgtPl4fYtXXRF73AmJjrENV /04s8L5T9DqUttzr+b4rontLHF3Rjcy1Qr6cIuCheinL+Exsj4RZbfMgtHYlvfGeRwWXKO bgFIbHgXaramPcOxhn4cYsvZoB3yDjMxz1BFY06KndFj4v/LlooOn/DKb+mKDA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gZdxl5ghsznrc for ; Tue, 09 Jun 2026 19:19:07 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3c5fb by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 09 Jun 2026 19:19:07 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Mark Johnston Subject: git: 0e8cc8d8a49f - releng/14.4 - sound: Fix software buffer lifetime issues List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: markj X-Git-Repository: src X-Git-Refname: refs/heads/releng/14.4 X-Git-Reftype: branch X-Git-Commit: 0e8cc8d8a49f2393fcbc836171f0a85a0e4bd92c Auto-Submitted: auto-generated Date: Tue, 09 Jun 2026 19:19:07 +0000 Message-Id: <6a28672b.3c5fb.387624f6@gitrepo.freebsd.org> The branch releng/14.4 has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=0e8cc8d8a49f2393fcbc836171f0a85a0e4bd92c commit 0e8cc8d8a49f2393fcbc836171f0a85a0e4bd92c Author: Mark Johnston AuthorDate: 2026-06-01 21:57:40 +0000 Commit: Mark Johnston CommitDate: 2026-06-08 13:21:29 +0000 sound: Fix software buffer lifetime issues The channel buffer mapped by dsp_mmap_single() may be freed when the device handle is closed, but the mapping persists beyond that, allowing userspace to read or write memory owned by a different consumer. Fix the problem by adding a reference counter to the sound buffer. Define pager ops for the VM object returned by dsp_mmap_single() and use them to manage the extra reference. Add a regression test. Approved by: so Security: FreeBSD-SA-26:27.sound Security: CVE-2026-49417 Reported by: Lexpl0it, 75Acol, Liyw979, Rob1n Reviewed by kib Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D57393 --- sys/dev/sound/pcm/buffer.c | 38 ++++++++++++++++++++-- sys/dev/sound/pcm/buffer.h | 4 +++ sys/dev/sound/pcm/dsp.c | 80 ++++++++++++++++++++++++++++++++++++++-------- tests/sys/sound/mmap.c | 60 ++++++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 16 deletions(-) diff --git a/sys/dev/sound/pcm/buffer.c b/sys/dev/sound/pcm/buffer.c index de535ec2dcba..ddcf14fbc1c2 100644 --- a/sys/dev/sound/pcm/buffer.c +++ b/sys/dev/sound/pcm/buffer.c @@ -32,6 +32,7 @@ #include "opt_snd.h" #endif +#include #include #include "feeder_if.h" @@ -46,6 +47,7 @@ sndbuf_create(device_t dev, char *drv, char *desc, struct pcm_channel *channel) struct snd_dbuf *b; b = malloc(sizeof(*b), M_DEVBUF, M_WAITOK | M_ZERO); + refcount_init(&b->refcount, 1); snprintf(b->name, SNDBUF_NAMELEN, "%s:%s", drv, desc); b->dev = dev; b->channel = channel; @@ -56,8 +58,30 @@ sndbuf_create(device_t dev, char *drv, char *desc, struct pcm_channel *channel) void sndbuf_destroy(struct snd_dbuf *b) { - sndbuf_free(b); - free(b, M_DEVBUF); + b->flags |= SNDBUF_F_DETACHED; + sndbuf_rele(b); +} + +void +sndbuf_ref(struct snd_dbuf *b) +{ + unsigned int count __diagused; + + CHN_LOCK(b->channel); + count = refcount_acquire(&b->refcount); + KASSERT(count > 0, ("sndbuf %p refcount 0", b)); + CHN_UNLOCK(b->channel); +} + +void +sndbuf_rele(struct snd_dbuf *b) +{ + if (refcount_release(&b->refcount)) { + sndbuf_free(b); + KASSERT(refcount_load(&b->refcount) == 0, + ("sndbuf %p still referenced", b)); + free(b, M_DEVBUF); + } } bus_addr_t @@ -183,6 +207,11 @@ sndbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) if (bufsize > b->allocsize || bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { + if (refcount_load(&b->refcount) > 1 || + (b->flags & SNDBUF_F_DETACHED) != 0) { + CHN_UNLOCK(b->channel); + return (EBUSY); + } allocsize = round_page(bufsize); CHN_UNLOCK(b->channel); tmpbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); @@ -218,10 +247,15 @@ sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) if (blkcnt < 2 || blksz < 16) return EINVAL; + CHN_LOCKASSERT(b->channel); + bufsize = blksz * blkcnt; if (bufsize > b->allocsize || bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { + if (refcount_load(&b->refcount) > 1 || + (b->flags & SNDBUF_F_DETACHED) != 0) + return (EBUSY); allocsize = round_page(bufsize); CHN_UNLOCK(b->channel); buf = malloc(allocsize, M_DEVBUF, M_WAITOK); diff --git a/sys/dev/sound/pcm/buffer.h b/sys/dev/sound/pcm/buffer.h index ddf4083ec19f..99bc6c0611d2 100644 --- a/sys/dev/sound/pcm/buffer.h +++ b/sys/dev/sound/pcm/buffer.h @@ -27,6 +27,7 @@ */ #define SNDBUF_F_MANAGED 0x00000008 +#define SNDBUF_F_DETACHED 0x00000010 #define SNDBUF_NAMELEN 48 @@ -50,6 +51,7 @@ struct snd_dbuf { bus_dma_tag_t dmatag; bus_addr_t buf_addr; int dmaflags; + unsigned int refcount; struct selinfo sel; struct pcm_channel *channel; char name[SNDBUF_NAMELEN]; @@ -57,6 +59,8 @@ struct snd_dbuf { struct snd_dbuf *sndbuf_create(device_t dev, char *drv, char *desc, struct pcm_channel *channel); void sndbuf_destroy(struct snd_dbuf *b); +void sndbuf_ref(struct snd_dbuf *b); +void sndbuf_rele(struct snd_dbuf *b); void sndbuf_dump(struct snd_dbuf *b, char *s, u_int32_t what); diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c index 72bde9c1066f..bf76345827f5 100644 --- a/sys/dev/sound/pcm/dsp.c +++ b/sys/dev/sound/pcm/dsp.c @@ -79,7 +79,6 @@ static d_read_t dsp_read; static d_write_t dsp_write; static d_ioctl_t dsp_ioctl; static d_poll_t dsp_poll; -static d_mmap_t dsp_mmap; static d_mmap_single_t dsp_mmap_single; struct cdevsw dsp_cdevsw = { @@ -89,7 +88,6 @@ struct cdevsw dsp_cdevsw = { .d_write = dsp_write, .d_ioctl = dsp_ioctl, .d_poll = dsp_poll, - .d_mmap = dsp_mmap, .d_mmap_single = dsp_mmap_single, .d_name = "dsp", }; @@ -1931,23 +1929,72 @@ dsp_poll(struct cdev *i_dev, int events, struct thread *td) return (ret); } +struct dsp_mmap_handle { + struct cdev *cdev; + struct snd_dbuf *buf; +}; + static int -dsp_mmap(struct cdev *i_dev, vm_ooffset_t offset, vm_paddr_t *paddr, - int nprot, vm_memattr_t *memattr) +dsp_dev_pager_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot, + vm_ooffset_t foff, struct ucred *cred, u_short *color) { + struct dsp_mmap_handle *h = handle; - /* - * offset is in range due to checks in dsp_mmap_single(). - * XXX memattr is not honored. - */ - *paddr = vtophys(offset); + dev_ref(h->cdev); + sndbuf_ref(h->buf); return (0); } +static void +dsp_dev_pager_dtor(void *handle) +{ + struct dsp_mmap_handle *h = handle; + + sndbuf_rele(h->buf); + dev_rel(h->cdev); + free(h, M_DEVBUF); +} + static int -dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, +dsp_dev_pager_fault(vm_object_t object, vm_ooffset_t offset, int prot, + vm_page_t *mres) +{ + struct dsp_mmap_handle *h = object->handle; + vm_page_t m; + uintptr_t addr; + vm_paddr_t paddr; + + addr = (uintptr_t)offset; + if (addr < (uintptr_t)h->buf->buf || + addr >= (uintptr_t)h->buf->buf + h->buf->allocsize) + return (VM_PAGER_ERROR); + paddr = vtophys((void *)addr); + + if (((*mres)->flags & PG_FICTITIOUS) != 0) { + m = *mres; + vm_page_updatefake(m, paddr, object->memattr); + } else { + VM_OBJECT_WUNLOCK(object); + m = vm_page_getfake(paddr, object->memattr); + VM_OBJECT_WLOCK(object); + vm_page_replace(m, object, (*mres)->pindex, *mres); + *mres = m; + } + m->valid = VM_PAGE_BITS_ALL; + return (VM_PAGER_OK); +} + +static const struct cdev_pager_ops dsp_dev_pager_ops = { + .cdev_pg_ctor = dsp_dev_pager_ctor, + .cdev_pg_dtor = dsp_dev_pager_dtor, + .cdev_pg_fault = dsp_dev_pager_fault, +}; + +static int +dsp_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, vm_size_t size, struct vm_object **object, int nprot) { + struct dsp_mmap_handle *handle; struct dsp_cdevpriv *priv; struct snddev_info *d; struct pcm_channel *wrch, *rdch, *c; @@ -2010,13 +2057,18 @@ dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset, *offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset); dsp_unlock_chans(priv, FREAD | FWRITE); - *object = vm_pager_allocate(OBJT_DEVICE, i_dev, - size, nprot, *offset, curthread->td_ucred); + handle = malloc(sizeof(*handle), M_DEVBUF, M_WAITOK); + handle->cdev = cdev; + handle->buf = c->bufsoft; + *object = cdev_pager_allocate(handle, OBJT_DEVICE, &dsp_dev_pager_ops, + size, nprot, *offset, curthread->td_ucred); PCM_GIANT_LEAVE(d); + if (*object == NULL) { + free(handle, M_DEVBUF); + return (EINVAL); + } - if (*object == NULL) - return (EINVAL); return (0); } diff --git a/tests/sys/sound/mmap.c b/tests/sys/sound/mmap.c index ab203a39194c..f775c0f8da8a 100644 --- a/tests/sys/sound/mmap.c +++ b/tests/sys/sound/mmap.c @@ -4,12 +4,14 @@ * Copyright (c) 2026 The FreeBSD Foundation */ +#include #include #include #include #include #include +#include #include #define FMT_ERR(s) s ": %s", strerror(errno) @@ -43,9 +45,67 @@ ATF_TC_BODY(mmap_offset_overflow, tc) close(fd); } +/* + * Verify that a MAP_SHARED mapping of a DSP device's software buffer remains + * valid after the file descriptor is closed. + */ +ATF_TC(mmap_buffer_lifetime); +ATF_TC_HEAD(mmap_buffer_lifetime, tc) +{ + atf_tc_set_md_var(tc, "descr", "mmap data survives close()"); + atf_tc_set_md_var(tc, "require.kmods", "snd_dummy"); +} +ATF_TC_BODY(mmap_buffer_lifetime, tc) +{ + audio_buf_info abi; + uint8_t *buf; + size_t len; + int fd, arg; + + fd = open("/dev/dsp0", O_RDWR); + ATF_REQUIRE_MSG(fd >= 0, FMT_ERR("open")); + + arg = (2 << 16) | 14; /* 2*16KB */ + ATF_REQUIRE_MSG(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) == 0, + FMT_ERR("SNDCTL_DSP_SETFRAGMENT")); + ATF_REQUIRE_MSG(ioctl(fd, SNDCTL_DSP_GETOSPACE, &abi) == 0, + FMT_ERR("SNDCTL_DSP_GETOSPACE")); + + len = abi.bytes; + ATF_REQUIRE_MSG(len >= PAGE_SIZE, "buffer too small: %zu", len); + + buf = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ATF_REQUIRE_MSG(buf != MAP_FAILED, FMT_ERR("mmap")); + + for (size_t i = 0; i < len; i++) { + ATF_REQUIRE_MSG(buf[i] == 0, + "mmap data corrupted at offset %zu: want 0 got 0x%02x", + i, buf[i]); + } + + memset(buf, 0xa5, len); + for (size_t i = 0; i < len; i++) { + ATF_REQUIRE_MSG(buf[i] == 0xa5, + "mmap data corrupted at offset %zu: want 0xa5 got 0x%02x", + i, buf[i]); + } + + ATF_REQUIRE(close(fd) == 0); + + /* Closing the device causes the buffer to be reset. */ + for (size_t i = 0; i < len; i++) { + ATF_REQUIRE_MSG(buf[i] == 0 || buf[i] == 0xa5, + "mmap data corrupted at offset %zu: got 0x%02x", i, buf[i]); + } + memset(buf, 0xa5, len); + + ATF_REQUIRE(munmap(buf, len) == 0); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, mmap_offset_overflow); + ATF_TP_ADD_TC(tp, mmap_buffer_lifetime); return (atf_no_error()); }