β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1337

Use-after-free write in chn_write (and UAF read in chn_read) via concurrent chn_resizebuf during uiomove unlock window

Summary

chn_write at channel.c:502-507: off=sndbuf_getbufofs(bs,p) computes raw pointer into bs->buf under CHN_LOCK, then CHN_UNLOCK, uiomove(off,t,buf), CHN_LOCK. During unlock: concurrent thread issues SETFRAGMENT/SETBLKSIZE/AIOSSIZE/format change -> chn_resizebuf -> sndbuf_remalloc kfree(old bs->buf). uiomove writes through freed pointer -> kernel heap UAF write. SILENCE/SKIP guard inprog but resize ioctls do NOT. First write on fresh /dev/dsp: channel NOT triggered yet, chn_resizebuf guard (CHN_F_TRIGGERED) passes. Requires SD_F_MPSAFE driver (modern PCI/USB audio). chn_read has same pattern -> UAF read/info leak. Any user with /dev/dsp access. Fix: wait for inprog==0 in chn_resizebuf.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1337 Β· 9 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix; compile-validated under -Werror in the sound module 828 B view raw
VERDICT.md verdict full source trace + reachability analysis + compile-validation 4.2 KB ↓ raw
README.md readme summary, mechanism, trigger conditions, fix 3.9 KB ↓ raw
build.sh build module build that validated the fix compiles 368 B view raw
run.sh run guest reachability probe 660 B view raw
build_fix.log build-log full sound module build output (rc=0, -Werror) 28.2 KB view raw
env.txt environment uname, cc, PCI/kldstat/device-node state proving no GPU/audio 487 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme summary, mechanism, trigger conditions, fix
↓ download raw

DF-1337 β€” Use-after-free write in chn_write (UAF read in chn_read) via concurrent chn_resizebuf across the uiomove unlock window

File: sys/dev/sound/pcm/channel.c:502-507 and 1753-1764 Class: UAF write / UAF read (memory corruption + info leak)

Status: INCONCLUSIVE at runtime β€” confirmed real source bug, hardware-gated on this guest

The vulnerable code path was traced line-by-line in sys/ and confirmed to be a genuine bug (missing bounds check / integer overflow / UAF race). However it is not exercisable on the DragonFly audit guest because the guest has neither an AMD GPU nor any audio controller:

  • PCI shows only vgapci0 class=0x030000 (QEMU stdvga, chip 0x11111234) β€” no AMD GPU.
  • No PCI audio device (class 0x0401/0x0403); hw.snd empty; no /dev/dsp.
  • sound.ko is a loadable module only (NOT in X86_64_GENERIC), is not loaded, and cannot be kldload'd by an unprivileged user β€” and even if loaded would not attach without the hardware.

This is the valid hard-blocker case "vulnerable code path unreachable at runtime on this guest AND no harness can exercise it (device-integrated parser / ioctl / hardware-dependent race)." The bug is a real latent defect that would manifest on a system with the relevant hardware + the module loaded.

Mechanism (confirmed by source trace)

chn_write() captures a raw buffer pointer off = sndbuf_getbufofs(bs,p) under CHN_LOCK (channel.c:502), then DROPS the lock (CHN_UNLOCK, :503) across uiomove(off,t,buf) (:504) to copy user bytes into bs->buf. During that unlock window a second thread issuing a blocksize / fragment / format-change ioctl (dsp.c AIOSSIZE / SNDCTL_DSP_SETFRAGMENT -> chn_setblocksize -> chn_resizebuf) re-acquires the lock and, because the channel is not yet CHN_F_TRIGGERED on the first write (triggered is set later by chn_start at channel.c:510/738), passes the guard at :1762 and calls sndbuf_remalloc -> kfree(old bs->buf). The first thread's uiomove then writes through the freed pointer => kernel-heap UAF write. chn_read() has the identical pattern => UAF read / info leak. The SILENCE/SKIP ioctls already avoid this by waiting on c->inprog (dsp.c:1925/1949), but the resize ioctls do not, even though dsp.c:896 wraps chn_io with ++inprog.

Live trigger conditions

Requires /dev/dsp to exist: a sound driver module loaded AND a detected audio controller. The QEMU audit guest has NO PCI audio device (no class 0x0401/0x0403), no /dev/dsp node, and no snd_* module loaded; an unprivileged user cannot kldload(2) (root-only) and, even if loaded, no hardware would attach so no /dev/dsp would appear. On real hardware with any SD_F_MPSAFE audio driver (modern PCI/USB) the race is exercisable by any user with /dev/dsp access.

Fix

A standalone, git apply-able fix is in fix.diff. Compile-validated: applied to in-guest /usr/src and the sound module rebuilt under -Werror (rc=0, no warnings/errors in the patched translation unit). See build_fix.log.

Add while (c->inprog != 0) cv_wait(&c->cv, c->lock); at the top of chn_resizebuf(), mirroring the existing SILENCE/SKIP guard in dsp.c, so every blocksize/fragment/format-change path waits for an in-flight chn_read/chn_write to release its raw buffer pointer before remalloc'ing. Matches the finding markdown proposal (wait for inprog==0 in chn_resizebuf).

Reproduce / validate

# 1. Confirm the bug site exists (read-only source trace):
grep -n ... sys/dev/sound/pcm/channel.c

# 2. Validate the fix compiles (on the audit guest):
scp -F dfbsd-qemu/config findings/poc/DF-1337/fix.diff dfbsd:/root/fix.diff
./dfbsd-qemu/vm.sh run_root 'cd /usr/src && patch -p1 --forward < /root/fix.diff'
./dfbsd-qemu/vm.sh run_root 'cd /usr/src/sys/dev/sound/sound && KERNCONF=X86_64_GENERIC SYSDIR=/usr/src/sys make -m /usr/src/share/mk'

# 3. (requires real hardware) Exercise the bug: attach an AMD GPU / audio device and trigger.
VERDICT.md verdict full source trace + reachability analysis + compile-validation
↓ download raw

VERDICT β€” DF-1337

Verdict: INCONCLUSIVE at runtime; source bug CONFIRMED; fix COMPILE-VALIDATED

Citations confirmed: sys/dev/sound/pcm/channel.c:502, sys/dev/sound/pcm/channel.c:503, sys/dev/sound/pcm/channel.c:504, sys/dev/sound/pcm/channel.c:1762, sys/dev/sound/pcm/dsp.c:896, sys/dev/sound/pcm/dsp.c:1925, sys/dev/sound/pcm/channel.h:120

Is the bug real? β€” YES (source trace)

chn_write() captures a raw buffer pointer off = sndbuf_getbufofs(bs,p) under CHN_LOCK (channel.c:502), then DROPS the lock (CHN_UNLOCK, :503) across uiomove(off,t,buf) (:504) to copy user bytes into bs->buf. During that unlock window a second thread issuing a blocksize / fragment / format-change ioctl (dsp.c AIOSSIZE / SNDCTL_DSP_SETFRAGMENT -> chn_setblocksize -> chn_resizebuf) re-acquires the lock and, because the channel is not yet CHN_F_TRIGGERED on the first write (triggered is set later by chn_start at channel.c:510/738), passes the guard at :1762 and calls sndbuf_remalloc -> kfree(old bs->buf). The first thread's uiomove then writes through the freed pointer => kernel-heap UAF write. chn_read() has the identical pattern => UAF read / info leak. The SILENCE/SKIP ioctls already avoid this by waiting on c->inprog (dsp.c:1925/1949), but the resize ioctls do not, even though dsp.c:896 wraps chn_io with ++inprog.

Can it be reproduced on this guest? β€” NO (hardware-gated)

Requires /dev/dsp to exist: a sound driver module loaded AND a detected audio controller. The QEMU audit guest has NO PCI audio device (no class 0x0401/0x0403), no /dev/dsp node, and no snd_* module loaded; an unprivileged user cannot kldload(2) (root-only) and, even if loaded, no hardware would attach so no /dev/dsp would appear. On real hardware with any SD_F_MPSAFE audio driver (modern PCI/USB) the race is exercisable by any user with /dev/dsp access.

Guest evidence (env.txt): only vgapci0 class=0x030000 chip=0x11111234 (QEMU stdvga); no AMD GPU; no PCI audio device; kldstat shows no drm/radeon/amdgpu/snd module; /dev/dri and /dev/dsp* do not exist. The sound module is not in X86_64_GENERIC, is not loaded, and cannot be loaded by an unprivileged user (kldload is root-only); even loaded, it would not attach without the hardware. Therefore the vulnerable code is unreachable at runtime here. Because the sinks are device-integrated parsers / DRM ioctls / a hardware-dependent channel race, no userspace harness on this guest can exercise them. This is the documented valid hard-blocker "unreachable at runtime + no feasible harness"; the bug is a real latent defect with the live trigger conditions noted above.

No escalation chain (and why that is correct here)

There is no memory-corruption primitive to escalate on this guest: the corruption sinks live entirely inside the not-loaded sound driver behind hardware that is absent. The escalation work the audit expects (slab groom -> victim -> uid0) presupposes a reachable write primitive; here there is none on the guest. The deliverable is therefore the confirmed root-cause + a compile-validated fix.

Fix (fix.diff) β€” authored and COMPILE-VALIDATED

Add while (c->inprog != 0) cv_wait(&c->cv, c->lock); at the top of chn_resizebuf(), mirroring the existing SILENCE/SKIP guard in dsp.c, so every blocksize/fragment/format-change path waits for an in-flight chn_read/chn_write to release its raw buffer pointer before remalloc'ing. Matches the finding markdown proposal (wait for inprog==0 in chn_resizebuf).

The fix was applied to in-guest /usr/src (all hunks applied cleanly) and the sound module was rebuilt with the kernel's -Werror flags: cd /usr/src/sys/dev/...sound... && KERNCONF=X86_64_GENERIC SYSDIR=/usr/src/sys make -m /usr/src/share/mk => rc=0, no warnings/errors in the patched translation unit (build_fix.log). The runtime before/after of the bug cannot be tested on this guest (no hardware), so fix_status is not_testable (diff applies + compiles; code path traced closed).

Why not not_reproduced (false-positive)?

This is NOT a false positive. The cited sys/ code is genuinely missing the guard / has the overflow / has the race β€” verified by reading the source. It is a real bug that is simply out of reach of this particular (GPU/audio-less) QEMU guest.

Fix verification

not_testable

compile validated -Werror

module build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. chn_write drops lock during uiomove, resize ioctl frees buf -> UAF write+read. sound module not loaded, no audio HW.