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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3879 Β· 22 files
FileTypeDescriptionSize
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

VERDICT.md
↓ download raw

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-duplex with -audiodev none) to the audit guest; DragonFly's snd_hda.ko binds the QEMU ICH6 HDA controller (8086:2668) and the hda-duplex codec (0x1af40022), creating pcm0 (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)

  1. 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.

  1. 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 in pmap_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 > bufsize is a signed compare (jg in the objdump) β€” a negative offset is "not greater" β€” and size > bufsize - *offset computes 0xF0000000 > 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)

  1. Unvalidated offset in. kern_mmap() page-aligns the user's mmap offset but performs NO range/sign validation of pos for VCHR mappings (sys/vm/vm_mmap.c:195-197), and vm_mmap() hands it straight to dev_dmmap_single() (vm_mmap.c:1377, kern_device.c:286-310) β€” any 64-bit page-aligned value, negative included, reaches dsp_mmap_single().

  2. 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).

  3. 32-bit truncation. *offset = (uintptr_t)sndbuf_getbufofs(c->bufsoft, *offset) β€” dsp.c:2355; the callee takes unsigned int ofs (buffer.c:424-429) and its ONLY bound is KASSERT(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.

  4. 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 calling dev_dmmap() (device_pager.c:326-333) which lands in dsp_mmap() (dsp.c:2287-2294) β€” that function ALWAYS returns 0 with a_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 funnels old_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).

  5. Physical page entered into user PTEs. pmap_kextract on a MAPPED wild kernel address returns its true physical frame β€” which dev_pager_getfake/pmap_phys_address enter 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 address pmap_kextract walks 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->buf is 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

fixed
baseline reproduced→ patch + rebuild →patched clean

v2 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)
↓ fix.diffper-fix-DF-3879

Confirmed kernel references

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.