dsp_mmap_single 64-bit offset-overflow bounds bypass + 32-bit truncation in sndbuf_getbufofs maps arbitrary kernel memory (b->buf + attacker-chosen ofs) read/write into user PTEs
Summary
The only bounds check in dsp_mmap_single is '(*offset + size) > sndbuf_getsize(c->bufsoft)' where BOTH u64 operands are user-chosen (kern_mmap does no VCHR offset validation). *offset = 2^64 - size + x wraps the sum to <= bufsize and passes; the wrapped offset is truncated to 'unsigned int ofs' by sndbuf_getbufofs (KASSERT-only bound) yielding an attacker-chosen wild kernel pointer b->buf + ofs (ofs in [0,4GB)). That pointer becomes the device-pager offset; the ctor 'validation' is a no-op because dsp_mmap always returns atop(vtophys(offset)); on every page fault the pager resolves the physical frame of whatever kernel memory sits at that address and enters it RW in the user's mapping. /dev/dsp* are 0666. Any unprivileged local user on a sound-equipped DragonFly system: on non-INVARIANTS kernels page-granular ARBITRARY KERNEL-MEMORY READ/WRITE within 4GB of the sound buffer (heap spray -> cred/function-pointer overwrite -> uid=0); on INVARIANTS kernels (X86_64_GENERIC) a deterministic KASSERT panic. Also proves DF-1262's d_mmap vtophys sink is LIVE as the pager fault callback, not dead code. VERIFIED: selection arithmetic reproduced on the guest ('CHECK BYPASSED, wild ofs controlled'); the guest has no PCM device so the kernel path could not execute - source chain proven end-to-end (kern_mmap -> vm_mmap -> kern_device -> dsp.c -> device_pager -> pmap_kextract). Overflow-safe-check fix. UPGRADED: kernel path made executable via QEMU emulated intel-hda/hda-duplex (snd_hda binds, pcm0 created, /dev/dsp 0666); REPRODUCED as unprivileged uid=1001 twice - KASSERT panic with attacker-chosen ofs AND an INVARIANTS-free Fatal trap 12 in pmap_kextract (production-relevant). Mechanism correction: vm_ooffset_t is SIGNED - the bypass is an undetected negative mmap offset. v1 fix proven insufficient (built+re-panicked); v2 unsigned-compare fix kernel-validated (both attacks EINVAL, legit mmap RW preserved).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3879 Β· 22 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.2 KB | β raw | |
| VERDICT.md | β | 11.0 KB | β raw | |
| mmap_ofs_overflow.c | β | 5.6 KB | view raw | |
| mmap_alias_rw.c | β | 5.1 KB | view raw | |
| mmap_onepage.c | β | 2.9 KB | view raw | |
| kmemprobe.c | β | 1.4 KB | view raw | |
| sanity_mmap.c | β | 1.3 KB | view raw | |
| build.sh | β | 116 B | view raw | |
| run.sh | β | 136 B | view raw | |
| build.log | β | 329 B | view raw | |
| run.log | β | 3.0 KB | view raw | |
| panic.txt | β | 590 B | view raw | |
| panic.pmap.txt | β | 768 B | view raw | |
| env.audio.txt | β | 764 B | view raw | |
| env.txt | β | 692 B | view raw | |
| fix.diff | β | 890 B | view raw | |
| fix_env.txt | β | 203 B | view raw | |
| fix_build.log | β | 2.7 KB | view raw | |
| fix_run_variantB.log | β | 628 B | view raw | |
| fix_run_variantA_sanity.log | β | 729 B | view raw | |
| manifest.json | β | 2.7 KB | view raw | |
| verdict.json | β | 9.4 KB | view raw |
DF-3879 β dsp_mmap_single() negative-offset bounds bypass + u32 truncation
File: sys/dev/sound/pcm/dsp.c:2296-2364 β pass 2 (GLM 5.3), kernel-path verified 2026-09-16 (GLM 5.3 verify pass).
THE BUG (mechanism corrected during verification): vm_ooffset_t is SIGNED (sys/cpu/x86_64/include/types.h:44), and kern_mmap() performs no sign/range validation of the mmap offset for VCHR devices (vm_mmap.c:195-197). dsp_mmap_single()'s only bound
(*offset + size) > sndbuf_getsize(c->bufsoft) dsp.c:2342
therefore passes for a NEGATIVE offset (off = t - size, t in [0,bufsize)):
the sum lands in [0, bufsize]. The offset is then truncated to
unsigned int ofs by sndbuf_getbufofs() (dsp.c:2355, buffer.c:424-429,
KASSERT-only bound) and the resulting wild kernel pointer
b->buf + ofs becomes the device-pager offset (dsp.c:2357).
MAKING THE GUEST ATTACKABLE (QEMU audio attach) The audit guest boots headless with no sound hardware. Boot it with:
-audiodev none,id=snd0 \
-device intel-hda -device hda-duplex,audiodev=snd0
(see the run narrative in run.log; dfbsd-qemu/vm.sh was NOT modified β
a temporary launcher added the three flags). In the guest:
kldload snd_hda # binds QEMU ICH6 HDA (8086:2668)
dmesg then shows hdac0/hdacc0/hdaa0/pcm0 and /dev/dsp + /dev/dsp0
appear 0666 root:wheel (env.audio.txt).
BUILD (on the guest) cc -O2 -Wall -o /tmp/mmap_ofs /tmp/mmap_ofs_overflow.c cc -O2 -Wall -o /tmp/mmap_alias_rw /tmp/mmap_alias_rw.c cc -O2 -Wall -o /tmp/sanity_mmap /tmp/sanity_mmap.c
RUN (unprivileged, e.g. vm.sh run_user) /tmp/mmap_ofs; echo exit=$?
EXPECTED β stock INVARIANTS kernel (e.g. X86_64_GENERIC) * variant B (/tmp/mmap_ofs, off=0xffffffff10000000 size=0xf0000000): the ssh session dies mid-run and the serial console shows panic: sndbuf_getbufofs: ofs invalid 268435456 with backtrace kern_mmap -> vm_mmap -> dev_dmmap_single -> dsp_mmap_single -> sndbuf_getbufofs (panic.txt). 268435456 = 0x10000000 = the attacker-chosen ofs. * variant A (/tmp/mmap_alias_rw, off=0xffffffff00000000 size=4GiB): Fatal trap 12 in pmap_kextract during the mmap syscall (panic.pmap.txt) β the device-pager ctor walk over the wild range; contains NO INVARIANTS code, so this fires on production builds too. On a NON-INVARIANTS kernel variant B proceeds into the pager: with a heap-layout-dependent mapped victim page in [b->buf+4GB-S, b->buf+4GB) the mapping succeeds and aliases that kernel page RW (uid=0 ceiling; see VERDICT.md "attack geometry" β not demonstrated on this guest).
EXPECTED β kernel with fix.diff (v2) applied variant B: mmap: Invalid argument (errno=22), exit=0, guest up variant A: wild mmap: Invalid argument, guest up sanity: legit mmap(off=0, size=bufsize=65536) ok, RW verified
FIX VALIDATION (kernel-level) cd /usr/src && git apply fix.diff make -j6 nativekernel KERNCONF=X86_64_GENERIC (~30 min) install the REBUILT sound.ko module (dsp.c lives in the module, not the kernel binary!) from /usr/obj/usr/src/sys/X86_64_GENERIC/usr/src/sys/dev/sound/sound/sound.ko to /boot/kernel/sound.ko (+ matching snd_hda.ko from .../driver/hda/), sync, reboot, re-run the matrix above. NOTE: the originally-filed v1 fix (signed compares) was built and PROVEN INSUFFICIENT β variant B still panicked; only the v2 unsigned-compare form blocks the attack.
ARTIFACTS mmap_ofs_overflow.c the filed PoC (panic trigger, variant B) mmap_alias_rw.c negative-offset pager proof (variant A/A2) mmap_onepage.c small-S attack shape (RW-alias follow-up) kmemprobe.c root-side /dev/kmem mapped-ness oracle sanity_mmap.c legit-feature regression check panic.txt KASSERT panic + backtrace (stock kernel) panic.pmap.txt pmap_kextract fatal trap (stock kernel) run.log narrative of all runs env.audio.txt audio-enabled guest environment fix_*.log/txt fix validation evidence fix.diff v2 fix (authoritative; kernel-validated) verdict.json / manifest.json / VERDICT.md
DF-3879 VERDICT β dsp_mmap_single offset-overflow β wild kernel page mapping
Status: REPRODUCED on the live kernel (impact: panic β deterministic, unprivileged; twice, with two distinct signatures). Fix validated (v2).
2026-09-16 verification update (GLM 5.3 verify pass): the kernel path was made executable by attaching QEMU emulated audio (
-device intel-hda -device hda-duplexwith-audiodev none) to the audit guest; DragonFly'ssnd_hda.kobinds the QEMU ICH6 HDA controller (8086:2668) and the hda-duplex codec (0x1af40022), creatingpcm0 (play/rec)and/dev/dsp,/dev/dsp0β both 0666 root:wheel (dsp.c:2506-2509, sound.c:1420-1425). Every hardware precondition of the finding is now met on this guest; see env.audio.txt.
Verification results (all runs as unprivileged uid=1001 via /dev/dsp)
-
Variant B β the filed PoC (
mmap_ofs_overflow), off=0xffffffff10000000 size=0xf0000000: deterministic panic during the mmap(2) syscall, serial console (panic.txt):panic: sndbuf_getbufofs: ofs invalid 268435456 sndbuf_getbufofs() at sndbuf_getbufofs+0x2b dsp_mmap_single() at dsp_mmap_single+0x149 dev_dmmap_single() at dev_dmmap_single+0x5e vm_mmap() at vm_mmap+0x6d8 kern_mmap() at kern_mmap+0x378
268435456 = 0x10000000 = exactly the attacker-chosen ofs β the complete chain of the finding executed on the running kernel.
-
Variant A (
mmap_alias_rw), off=0xffffffff00000000 (= β2^32) size=0x100000000: the check at dsp.c:2342 passes (sum = 0 β€ bufsize), the wild offset is installed in the device pager, and the kernel panics inside the mmap syscall inpmap_kextract(panic.pmap.txt):Fatal user address access from kernel mode from mmap_alias_rw Fatal trap 12: page fault while in kernel mode fault virtual address = 0xffff80403e005000 (DMAP read of a garbage PDE target β phys 275GB) Stopped at pmap_kextract.part.23+0x16: movq (%rax,%rdx,1),%rdx
Mechanism (source-proven, device_pager.c old_dev_pager_ctor): the
old-style cdev pager ctor validates EVERY page of the mapping upfront
(for (off = foff; npages--; off += PAGE_SIZE) dev_dmmap(...)), and
dsp_mmap never returns -1, so a mapping whose range leaves mapped KVA
makes vtophys() walk garbage page-table entries β an unrecoverable
kernel page fault. This path contains zero INVARIANTS-dependent
code β it fires identically on production (non-INVARIANTS) builds.
Deterministic across boots (same fault VA twice: phys page 0 content is
boot-identical).
CORRECTION to the filed mechanism (important)
The filed framing "u64 wrap" is imprecise: vm_ooffset_t is SIGNED
(typedef __int64_t vm_ooffset_t, sys/cpu/x86_64/include/types.h:44).
The attack offset 0xffffffff10000000 IS -0xF0000000 as int64 β the user
simply passes a negative mmap offset (t - size), which kern_mmap
never range-checks for VCHR (vm_mmap.c:195-197 page-aligns only), and the
signed sum *offset + size = t β [0, bufsize] passes dsp.c:2342 without
any 2^64 modular wrap. All PoC numbers, both panics, and the u32
truncation sink ((unsigned int)(-0xF0000000) = 0x10000000) are
unchanged; only the type analysis is corrected.
Attack geometry (refined): with mmap size S, a true negative offset
requires t β [0, min(bufsize, S)) and yields
ofs = (u32)(t - S) = 2^32 - S + t β wild = b->buf + 2^32 - S + t,
i.e. the aliasable window is [b->buf + 4GB - S, b->buf + 4GB) and the
pager ctor must validate every one of the S/PAGE pages. Small S (1 page)
gives a one-page alias target at b->buf + 0xFFFFF000+t; whether that
page is mapped KVA is heap-layout dependent (kernel_map band
0xfffff800_00000000+, kstacks observed at 0xfffff801_1xxxxxxx). Misses
panic deterministically (the RUN-1/2 primitive); hits give the RW alias.
A full RW-alias demonstration on this guest would need a root-side
/dev/kmem mapped-ness oracle to pre-select t (kmemprobe.c in this
pack) β left as follow-up; the panic reproduction above already proves
the check bypass, the wild offset reaching the pager, and the
vtophys/pmap_kextract sink live.
Fix validation β v1 INSUFFICIENT, v2 VALIDATED
- v1 (the originally-filed
*offset > sndbuf_getsize(...) || size > sndbuf_getsize(...) - *offset) compiles but does NOT block the attack: because vm_ooffset_t is signed,*offset > bufsizeis a signed compare (jgin the objdump) β a negative offset is "not greater" β andsize > bufsize - *offsetcomputes0xF0000000 > 0xF0010000= false. Verified empirically: the v1 module was built, installed, and variant B still panicked* with the same KASSERT signature. This is a textbook example of why fix validation must be executed, not reviewed. -
v2 (validated): compare unsigned β
(uint64_t)offset > (uint64_t)sndbuf_getsize(c->bufsoft) || size > (uint64_t)sndbuf_getsize(c->bufsoft) - (uint64_t)offset ||
objdump of the built sound.ko shows ja/jb (unsigned) at both
compares. Full matrix on kernel #1 + fixed sound.ko (fix_env.txt,
fix_run_variantB.log, fix_run_variantA_sanity.log):
| test | stock kernel | fix-v2 kernel |
|---|---|---|
| variant B mmap_ofs (ofs=0x10000000) | panic: sndbuf_getbufofs: ofs invalid 268435456 | mmap: Invalid argument (errno=22), guest up |
| variant A2 mmap_alias_rw (β4GiB, 4GiB) | Fatal trap 12 in pmap_kextract during mmap | mmap: Invalid argument (errno=22), guest up |
| legit mmap(off=0, size=bufsize=65536) | works | works, RW verified β feature preserved |
Build: make nativekernel KERNCONF=X86_64_GENERIC (kernel #1, 19:37),
fixed sound.ko (md5 6f5280a1ded4e7b516549eefdc8f1735) installed to
/boot/kernel (fix_build.log). Note dsp.c lives in the sound.ko
module, not the kernel binary β validation requires the rebuilt
module, not just a kernel rebuild.
Root cause (as filed, line-verified)
-
Unvalidated offset in.
kern_mmap()page-aligns the user's mmap offset but performs NO range/sign validation ofposfor VCHR mappings (sys/vm/vm_mmap.c:195-197), andvm_mmap()hands it straight todev_dmmap_single()(vm_mmap.c:1377, kern_device.c:286-310) β any 64-bit page-aligned value, negative included, reachesdsp_mmap_single(). -
Wrapping bounds check. The only bound is
(*offset + size) > sndbuf_getsize(c->bufsoft)β dsp.c:2342: choosing*offset = t - size(t β [0, bufsize]) makes the sum t β€ bufsize and the check PASSES. Reproduced live (both variants). -
32-bit truncation.
*offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset)β dsp.c:2355; the callee takesunsigned int ofs(buffer.c:424-429) and its ONLY bound isKASSERT(ofs < b->bufsize)β compiled out on production kernels, and on INVARIANTS kernels a panic primitive (proven: panic.txt). With the check bypassed, ofs β [bufsize, 4GB) is fully attacker-chosen. -
The wild pointer becomes the pager offset.
dev_pager_alloc(i_dev, size, nprot, *offset)β dsp.c:2357 creates an old-style cdev pager (device_pager.c:174-177). Its ctor validates each page by callingdev_dmmap()(device_pager.c:326-333) which lands indsp_mmap()(dsp.c:2287-2294) β that function ALWAYS returns 0 witha_result = atop(vtophys(offset)), so ctor validation is a no-op even for wild addresses β proven live: the ctor walk itself panics in pmap_kextract (panic.pmap.txt). The vm_map entry is created with offset = wild (vm_mmap.c:1466-1471), and every page fault funnelsold_dev_pager_fault(device_pager.c:351-363) βdev_dmmap(wild + n*PAGE)βdsp_mmapβvtophys(wild + n*PAGE)=pmap_kextract(pmap.c:1865-1890). -
Physical page entered into user PTEs.
pmap_kextracton a MAPPED wild kernel address returns its true physical frame β whichdev_pager_getfake/pmap_phys_addressenter into the user's mapping read/write (device_pager.c:355-390). On a NON-INVARIANTS kernel this is unprivileged arbitrary kernel-memory read/write within 4GB of the sound soft buffer (kernel heap spray β victim object β uid=0); reachability of a mapped victim page is heap-layout dependent (see "Attack geometry"). On an UNMAPPED wild addresspmap_kextractwalks garbage PTEs and the kernel takes an unrecoverable page fault (deterministic, reproduced β panic.pmap.txt; INVARIANTS-independent).
Reachability
- /dev/dsp and friends are created 0666 root:wheel*
(dsp.c:2506-2509
make_only_dev(... 0666 ...)), so the surface is every local unprivileged user on any machine with a PCM device β including QEMU/KVM virtual machines with emulated HDA (this verification). - Gates that pass trivially: channel must not be RUNNING/DEAD β
CHN_F_MMAP_INVALID == (CHN_F_DEAD | CHN_F_RUNNING)(channel.h:402), so a freshly-opened /dev/dsp (before any write/trigger) is mmap-valid. PROT_READ alone selects rdch; PROT_WRITE selects wrch (dsp.c:2329-2340); open O_RDWR gives both. mmap must be MAP_SHARED (vm_mmap.c:309-313 rejects MAP_PRIVATE for VCHR) β matches the exploit requirement. - bufsoft is allocated by the OSS setup ioctls
(SETFMT/CHANNELS/SPEED β chn_setparam β chn_resizebuf β
sndbuf_remalloc, channel.c:1954/1910) β bufsize 65536 observed; at
fresh open
bufsoft->bufis NULL (chn_init: sndbuf_setup(bs, NULL, 0), channel.c:1247) which merely changes the panic variant.
Severity note
Critical on non-INVARIANTS production builds with sound hardware (arbitrary kernel R/W within 4GB of the sound soft buffer β uid=0, gated on heap layout); deterministic local DoS by unprivileged users on INVARIANTS builds (KASSERT panic) AND on production builds (pmap_kextract page fault β INVARIANTS-independent, reproduced). /dev/dsp is 0666. CVSS reflects the worst case.
Fix
Reject the negative/wrapped offset with UNSIGNED comparisons β fix.diff (v2, git-apply clean, kernel-build-validated on the guest). v1's signed form was proven insufficient and superseded.
if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) ||
- (*offset + size) > sndbuf_getsize(c->bufsoft) ||
+ /* DF-3879: vm_ooffset_t is SIGNED (__int64_t): a negative
+ * *offset made the old (*offset + size) sum land within
+ * [0, bufsize]; sndbuf_getbufofs() then truncated it to
+ * unsigned int β wild kernel pointer into the device pager.
+ * Compare unsigned so negative offsets are rejected. */
+ (uint64_t)*offset > (uint64_t)sndbuf_getsize(c->bufsoft) ||
+ size > (uint64_t)sndbuf_getsize(c->bufsoft) - (uint64_t)*offset ||
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedv2 fix validated at kernel level on the guest: baseline (stock X86_64_GENERIC INVARIANTS + QEMU HDA pcm0) reproduces both panic signatures from uid=1001; patched (kernel #1 + rebuilt sound.ko md5 6f5280a1ded4e7b516549eefdc8f1735 with the unsigned-compare check, ja/jb confirmed by objdump) rejects both attack variants with EINVAL and preserves the legitimate mmap(off=0, size=bufsize) feature (RW verified), guest stable across all runs. IMPORTANT: the originally-filed v1 fix was built and empirically proven INSUFFICIENT (signed compare lets the negative offset through; variant B still panicked) β v2 supersedes it.
fix.diff (v2); fix_run_variantB.log; fix_run_variantA_sanity.log; fix_env.txt; fix_build.log; VERDICT.md (objdump jg-vs-ja analysis of v1 vs v2)
Confirmed kernel references
- sys/dev/sound/pcm/dsp.c:2342
- sys/dev/sound/pcm/dsp.c:2355
- sys/dev/sound/pcm/dsp.c:2357
- sys/dev/sound/pcm/dsp.c:2287
- sys/dev/sound/pcm/dsp.c:2506
- sys/dev/sound/pcm/buffer.c:424
- sys/dev/sound/pcm/channel.c:1247
- sys/dev/sound/pcm/channel.h:402
- sys/cpu/x86_64/include/types.h:44
- sys/vm/vm_mmap.c:195
- sys/vm/vm_mmap.c:1377
- sys/kern/kern_device.c:286
- sys/vm/device_pager.c:174
- sys/vm/device_pager.c:326
- sys/vm/device_pager.c:351
- sys/platform/pc64/x86_64/pmap.c:1865
- sys/dev/sound/pcm/sound.c:1420
Detail
Exploit chain
unprivileged user -> open /dev/dsp O_RDWR (0666) -> OSS SETFMT/CHANNELS/SPEED (allocates bufsoft, bufsize 65536 observed) -> mmap(NULL, size=S, PROT_READ|PROT_WRITE, MAP_SHARED, fd, off = t-S [NEGATIVE, t<S<=bufsize]) -> kern_mmap (no VCHR offset sign/range check, vm_mmap.c:195-197) -> dev_dmmap_single -> dsp_mmap_single: signed sum (offset+size)=t <= bufsize so dsp.c:2342 PASSES -> sndbuf_getbufofs(c->bufsoft, offset) truncates to u32 ofs = 2^32-S+t (buffer.c:424, KASSERT-only bound) -> wild = b->buf + ofs -> dev_pager_alloc old-style pager (dsp.c:2357; ctor validates every page via dev_dmmap -> dsp_mmap -> vtophys and never rejects because dsp_mmap always returns 0, dsp.c:2287-2294). Outcome attacker-shaped by S/t: (a) any page of [wild, wild+S) unmapped -> pmap_kextract walks a garbage PDE -> unrecoverable kernel page fault DURING mmap (INVARIANTS-independent, reproduced twice, deterministic) β local DoS; (b) INVARIANTS kernels with ofs >= bufsize -> KASSERT panic 'sndbuf_getbufofs: ofs invalid' (reproduced); (c) all pages mapped (heap-layout dependent, window [b->buf+4GB-S, b->buf+4GB)) -> mmap succeeds and old_dev_pager_fault enters pmap_kextract(wild+n*PAGE) frames read/write into user PTEs -> arbitrary kernel-memory R/W within 4GB of b->buf -> spray/groom a victim object (e.g. cred-bearing structure) -> uid=0. (c) not demonstrated on this guest; (a)+(b) proven.
Evidence (decisive lines)
panic.txt (KASSERT panic + full backtrace from unprivileged run); panic.pmap.txt (Fatal trap 12 in pmap_kextract reached through the pager during mmap, INVARIANTS-independent); run.log (narrative of all baseline runs); env.audio.txt (pcm0 attached via QEMU intel-hda + hda-duplex, /dev/dsp 0666); fix_run_variantB.log + fix_run_variantA_sanity.log (fix-v2 matrix: both attack variants -> EINVAL, legit mmap still RW, guest up); fix_env.txt + fix_build.log (kernel #1 + fixed sound.ko md5 6f5280a1ded4e7b516549eefdc8f1735); fix.diff (v2 unsigned-compare fix, git-apply clean); VERDICT.md (full story incl. signed-type correction, v1-insufficiency proof with objdump jg-vs-ja, attack geometry, /dev/kmem oracle plan)
PoC changes
mmap_ofs_overflow.c unchanged from prior pass (its userspace replica + live tail; the live tail now actually executes on the audio-attached guest). ADDED: mmap_alias_rw.c (wrapped -2^32 offset + OSS setup + cross-view alias proof attempt; on this guest its two baseline runs proved the pmap_kextract ctor-walk panic instead), mmap_onepage.c (small-S attack shape for follow-up RW-alias work), kmemprobe.c (root-side /dev/kmem mapped-ness oracle), sanity_mmap.c (legit-feature regression check for fix validation). The QEMU guest was booted with -audiodev none,id=snd0 -device intel-hda -device hda-duplex,audiodev=snd0 via a temporary launcher (dfbsd-qemu/vm.sh untouched); snd_hda kldload'ed as root; all attack runs as uid=1001.
Verified recommended fix
Unsigned bounds check in dsp_mmap_single (dsp.c:2342): '(uint64_t)offset > (uint64_t)sndbuf_getsize(c->bufsoft) || size > (uint64_t)sndbuf_getsize(c->bufsoft) - (uint64_t)offset' β rejects negative/wrapped offsets; kernel-build-validated (v1 signed form proven insufficient); optionally also bounds-check inside sndbuf_getbufofs without relying on KASSERT
Verdict
REPRODUCED on the live kernel after making the path executable by attaching QEMU emulated Intel-HDA audio (-device intel-hda -device hda-duplex) to the audit guest: DragonFly's snd_hda.ko binds the QEMU ICH6 controller (8086:2668) + hda-duplex codec (0x1af40022), creating pcm0 and /dev/dsp, /dev/dsp0 both 0666 root:wheel. As unprivileged uid=1001: (1) the filed PoC (mmap off=0xffffffff10000000 size=0xf0000000) deterministically PANICS the running INVARIANTS kernel DURING the mmap syscall with 'panic: sndbuf_getbufofs: ofs invalid 268435456' (268435456 = 0x10000000 = exactly the attacker-chosen ofs) and the backtrace kern_mmap -> vm_mmap -> dev_dmmap_single -> dsp_mmap_single -> sndbuf_getbufofs β the finding's chain word for word (panic.txt). (2) A second variant (off=0xffffffff00000000 = -2^32, size=4GiB) proves the check bypass reaches the pager sink on a path with ZERO INVARIANTS code: the old-style device pager ctor (device_pager.c old_dev_pager_ctor) validates every page via dev_dmmap -> dsp_mmap -> vtophys, and the walk faults in pmap_kextract on a garbage PDE (Fatal trap 12, fault VA 0xffff80403e005000, Stopped at pmap_kextract.part.23+0x16) β deterministic across boots, fires identically on production non-INVARIANTS builds (panic.pmap.txt). MECHANISM CORRECTION discovered during verification: vm_ooffset_t is SIGNED (typedef __int64_t, sys/cpu/x86_64/include/types.h:44) β the attack is an undetected NEGATIVE mmap offset (0xffffffff10000000 == -0xF0000000), not a 2^64 unsigned wrap; kern_mmap does no sign/range check for VCHR offsets. All PoC numbers and both panics unchanged. FIX: the originally-proposed fix.diff (v1, signed compares) was built and PROVEN INSUFFICIENT β variant B still panicked on the v1 module (signed 'jg' compare lets negative offsets through); the corrected v2 fix compares unsigned ((uint64_t)offset > (uint64_t)sndbuf_getsize(...) || size > (uint64_t)sndbuf_getsize(...) - (uint64_t)offset) and was kernel-build-validated on the guest (sound.ko module β note dsp.c lives in the module, not the kernel binary): variant B -> EINVAL (was panic), variant A2 -> EINVAL (was pmap_kextract fatal trap), legit mmap(off=0, size=bufsize) still works RW (feature preserved), guest stays up across all runs. RW-alias ceiling (arbitrary kernel memory within 4GB of the sound soft buffer, layout-dependent: aliasable window is [b->buf+4GB-S, b->buf+4GB) for mapping size S, every ctor-validated page must be mapped KVA) documented in VERDICT.md with the root-side /dev/kmem oracle plan (kmemprobe.c); left as follow-up. uid=0 not demonstrated.
No comments yet.