DF-4673 / verdict.json
{ "finding_id": "DF-4673", "status": "reproduced", "reproduced": true, "impact": "leak", "confidence": "certain", "verdict": "REPRODUCED (cross-privilege kernel-memory disclosure). After draining the low-phys DMA reserve with unprivileged METEORSETGEO grows (DF-4672s mechanism, same session), the grow path of the production unfixed bktr.ko installed unzeroed contigmalloc buffers whose pages come from the pfn-ascending PQ_FREE/PQ_CACHE scan; the verbatim bktr_mmap fault path mapped them PROT_READ into uid 1001, which read back ROOTs just-freed memory: 8 scan-served buffers scanned, 14,117 of 16,444 pages carried the ROOT-spray markers (magic, tag, spray page index). Control rounds with the reserve intact read all-zero (two-pool model proven). No OOB access involved -- purely the unzeroed in-bounds buffer.", "exploit_chain": "root (or any user) dirties ~2.6 GiB of anon memory with markers and exits (pages -> PQ_FREE, contents intact) -> uid 1001 opens /dev/bktr0 (0444), grows until the DMA reserve is exhausted (unpriv, DF-4672) -> next contigmalloc takes recycled PQ_FREE/PQ_CACHE pages (first-fit from pfn 0; ~85% spray coverage makes a clean run impossible: P(33-page clean run) ~ 1e-27) -> mmap(PROT_READ) of the frame buffer -> read other processes/roots freed page contents (page cache, heap, anon).", "evidence": "run.log: ROOT-SECRET page 0 (tag=41 spraypage=121355): 524f4f5453454352 ... and == RESULT round 41 == ... ROOT-spray secret pages disclosed to unprivileged user: 14117; KERNEL MEMORY DISCLOSURE CONFIRMED (cross-privilege); control_rounds.log: reserve-intact rounds all-zero; fix_run.log: 127.6 MiB scan-path buffer on DF467XFIX, 0/32,673 nonzero, 0 secrets.", "kernel_refs": [ "sys/dev/video/bktr/bktr_core.c:1667", "sys/dev/video/bktr/bktr_core.c:432", "sys/dev/video/bktr/bktr_os.c:403", "sys/dev/video/bktr/bktr_os.c:656", "sys/dev/video/bktr/bktr_os.c:680", "sys/dev/video/bktr/bktr_os.c:683", "sys/vm/vm_contig.c:308", "sys/vm/vm_contig.c:398", "sys/vm/device_pager.c:348" ], "poc_changes": "Seed assumed /dev/bktr0 + direct PQ sourcing. Rewritten: rig path, adaptive +32-page grows with per-grow wire deltas to distinguish reserve-served (zero, fictitious pages) from scan-served (recycled, contents intact) buffers, root-side 2600 MiB marker spray (23% sprays lose to pristine low-pfn runs -- measured; ~85% wins deterministically).", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (baseline) / #0: Thu Oct 15 00:54:24 UTC 2026 DF467XFIX (fix)", "runtime_sec": 5400, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o poc_stalebuf poc_stalebuf.c ; cc -O2 -Wall -o spray spray.c", "run_cmd": "./run.sh (root: spray 2600 MiB and exit; maxx: ./poc_stalebuf after reserve-draining grow storm)", "code_hash": "598d530f2ec27e29d7e7ad911c3878189368465b8d13e169c0b3a20e478566ca", "notes": "Honest scoping vs the seed: on this DragonFly, contigmalloc prefers the 256 MiB DMA reserve (pristine zero pages), so the disclosure requires reserve exhaustion first -- which the same unprivileged ioctl achieves deterministically (DF-4672), so the composed chain is fully unprivileged. The read() alternative in the finding needs the DMA capture engine (absent in QEMU); mmap() path verified. Incidental: contigmalloc-exhaustion ENOMEM observed at the end of the decisive run.", "recommended_fix": "bzero the newly grown buffer at install (grow path must match the attach paths bzero at bktr_core.c:432); see fix.diff.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fixed", "fix_evidence": "fix_run.log: DF467XFIX kernel, 2600 MiB spray + one 127.6 MiB max-geometry grow (32,673 pages forced through the PQ scan path, exceeding every reserve fragment): all 32,673 pages zero, 0 ROOT-SECRET, 0 nonzero pages vs 14,117/16,444 secret pages baseline." } |