aboutsummaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2026-06-01 21:57:40 +0000
committerMark Johnston <markj@FreeBSD.org>2026-06-08 15:39:32 +0000
commitbda153dc04b4ddb667856874c083bd117fc0b9a1 (patch)
tree8791738b56a605948331a852400836756febe2de /sys/dev
parentc42ee04c521ed8268421173f961859233a321b17 (diff)
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
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/sound/pcm/buffer.c38
-rw-r--r--sys/dev/sound/pcm/buffer.h4
-rw-r--r--sys/dev/sound/pcm/dsp.c89
3 files changed, 115 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 <sys/refcount.h>
#include <dev/sound/pcm/sound.h>
#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..01d848180eec 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,81 @@ 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_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 void
+dsp_dev_pager_path(void *handle, char *path, size_t len)
+{
+ struct dsp_mmap_handle *h = handle;
+
+ dev_copyname(h->cdev, path, len);
+}
+
+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,
+ .cdev_pg_path = dsp_dev_pager_path,
+};
+
static int
-dsp_mmap_single(struct cdev *i_dev, vm_ooffset_t *offset,
+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 +2066,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);
}