DF-4026 / verdict.json
{ "finding_id": "DF-4026", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "verdict": "Unprivileged local user (0666 /dev/dsp) obtains a persistent RW page mapping over freed M_DEVBUF kernel heap: dsp_mmap_single() can only acquire the channel lock while chn_resizebuf() is inside one of its three lock-drop windows, and exactly then it maps the old bufsoft buffer that the resize kfree()s microseconds later. Reproduced live: shrink-ioctl-returns-0 + 128K mapping held + live buffer 256B in every session (round 0-73); controlled write into the live kernel buffer that reclaimed the freed block demonstrated via the alias proof (0xDF marker), and post-free kernel rewriting of 31/32 stale pages demonstrated via the mutation proof. No panic is needed for the corruption (silent). uid=0 chain not completed this run - the remaining step is privileged-target grooming within the 128K kmem-slab class.", "exploit_chain": "open /dev/dsp (fresh vchan clone, 0666) -> SETFRAGMENT 2x64K (bufsoft=128K) -> race mmap(128K) vs shrinking SETFRAGMENT 2x16B: the shrink validates CHN_F_MMAP at chn_resizebuf entry, drops the lock (SETBLOCKSIZE + remalloc kmalloc windows), the mapper takes the lock mid-window and maps the OLD buffer, the resize completes and kfrees that buffer -> stale RW mapping over freed heap -> spray fresh dsp channels grown to 128K to reclaim the block -> read/write the reclaiming victims live kernel buffer through the stale alias (proven). Escalation to uid=0 would require reclaiming a privileged 128K-class kernel object (not completed).", "evidence": "run.3.log (alias proof: HIT round 0, READ-after-free alias, WRITE-after-free 0xDF in victim live buffer, CONFIRMED, EXIT=0); run.log (stock baseline round 2 HIT + mutation proof 31/32 pages); run.fix.log (patched sound.ko: no window in 3000 rounds + legit mmap preserved); VERDICT.md for the full matrix and line refs", "kernel_refs": [ "sys/dev/sound/pcm/channel.c:1762", "sys/dev/sound/pcm/channel.c:1815-1819", "sys/dev/sound/pcm/channel.c:1858-1864", "sys/dev/sound/pcm/channel.c:1910", "sys/dev/sound/pcm/buffer.c:224-228", "sys/dev/sound/pcm/buffer.c:229-237", "sys/dev/sound/pcm/dsp.c:2297-2364", "sys/dev/sound/pcm/dsp.c:210-213" ], "poc_changes": "Seed had no runnable PoC. Learned/adjusted during bring-up: fd must be O_RDWR for PROT_READ|PROT_WRITE mmap (EACCES otherwise); no write()/SETTRIGGER before the race (CHN_F_RUNNING poisons CHN_F_MMAP_INVALID -> mmap EINVAL); shrink target must be < allocsize>>5 (256 works from a 128K buffer; the vchan parent limit floors deep shrinks at 256B; 8K working sets never free); reclaim via fresh-channel 128K grow (not shrink/regrow - pool channels with allocsize==128K take the NOCHANGE branch); pre-fault the stale mapping before spraying.", "attempts": 9, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 2400, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -pthread -o /tmp/mmap_resize_uaf /tmp/mmap_resize_uaf.c", "run_cmd": "/tmp/mmap_resize_uaf 3000", "code_hash": "c41acc08ec6c3a02a98bbeea5ec39c7b4d5b04254c5b80cce176427aa418ef6c", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (channel.c recheck of CHN_F_MMAP before sndbuf_remalloc + buffer.c post-relock abort inside sndbuf_remalloc) built into sound.ko: identical PoC caught the window within 73 rounds on stock and never in 3000 rounds on the patched module; legit dsp mmap and fragment resizing preserved", "fix_evidence": "run.fix.log (patched: RESULT no window caught in 3000 attempts, EXIT=1; feature sanity OK); patched module md5 a517bbe17032d9ba88046ab759e578ca vs stock e869fcdebe0137ee26a9de85429c0baf; fix.diff in this pack", "notes": "impact recorded as leak per taxonomy (post-free kernel heap is readable), but a controlled write into a live kernel buffer was also proven (alias proof) - this is a write-capable UAF. Side observation recorded separately: kldunload sound panics in sndstat_sysuninit (root-only; panic.sndstat-unload.txt).", "recommended_fix": "Re-check CHN_F_MMAP after every relock in chn_resizebuf (before sndbuf_remalloc) and abort sndbuf_remallocs swap if the channel got mapped during its kmalloc window." } |