DF-2676 / verdict.json
{ "finding_id": "DF-2676", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "vm_page_grab() dereferences m->valid at sys/vm/vm_page.c:3882 after setting m=NULL on the 'page exists but busy' error path when the caller did not pass VM_ALLOC_RETRY (vm_page.c:3841-3843); the alloc-failure path correctly does 'goto failed' (:3859) but this path forgot. Proven deterministically with a two-kernel-thread KLD: Fatal trap 12 at fault VA 0x76 (offsetof(struct vm_page, valid)), faulting instruction 'movzbl 0x76,%eax' inside vm_page_grab, on the stock INVARIANTS guest kernel. Unprivileged in-tree reachability is currently latent: the only non-RETRY caller (sysv_shm prealloc loop, sysv_shm.c:582) holds the object token exclusively for its whole loop, the loop cannot block mid-iteration, and phys_pager_getpage marks prealloc'd pages PG_UNQUEUED, which excludes the only token-free page busier (the vm_page_hash_get quick-fault soft-busy); two dedicated userland racers (thousands of shmget-prealloc vs shmat+fault races, shm_use_phys=2) could not reach it, matching the source analysis. Any kernel caller (future in-tree code or third-party KLD) using the documented non-RETRY contract trips the NULL deref immediately, as demonstrated. Fixed-address read at NULL - panic/DoS only, no escalation.", "exploit_chain": "KLD trigger (root via kldload, or any future kernel caller): thread A busies a page in an object; thread B calls vm_page_grab(obj, 0, VM_ALLOC_NORMAL) -> vm_page_lookup_busy_try(...,TRUE,...) returns error=TRUE (PBUSY_LOCKED or soft-busy) -> vm_page_sleep_busy sleeps once -> A wakes the page -> B resumes, m=NULL; break -> 'if (m->valid == 0)' reads NULL+0x76 -> Fatal trap 12 -> kernel panic. No user-controllable pointer; read-only primitive.", "evidence": [ "panic.txt: Fatal trap 12, fault virtual address 0x76, 'Stopped at vm_page_grab.cold.30: movzbl 0x76,%eax' with the grabber/holder marker lines", "df2676.c: deterministic KLD trigger (two kthreads)", "run_fixed.log: same trigger on patched kernel -> 'DF-2676: grabber: grab returned 0 (no panic?!)', guest survived", "VERDICT.md: full reachability analysis why the sysv_shm prealloc caller cannot observe a busy page today" ], "kernel_refs": [ "sys/vm/vm_page.c:3838", "sys/vm/vm_page.c:3841", "sys/vm/vm_page.c:3843", "sys/vm/vm_page.c:3882", "sys/vm/vm_page.c:3859", "sys/kern/sysv_shm.c:582" ], "poc_changes": "Finding sketch proposed racing sysv_shm prealloc vs shmat faults; source analysis proved that caller cannot reach the error path (object held exclusively, non-blocking loop, PG_UNQUEUED pages), so the PoC was rewritten as a deterministic in-kernel KLD trigger. Two userland racers kept in the pack as the negative-result evidence. KLD iteration 1 did vm_object_hold in MOD_LOAD (syscall context) causing an unrelated token-accounting panic; fixed by moving all object work into a kernel thread. kthread_create signature fixed to (func,arg,tdp,fmt,...).", "attempts": 5, "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": 2820, "guest_dirty": 0, "build_cmd": "cd /root/df2676_mod && printf 'KMOD= df2676\\nSRCS= df2676.c\\nSYSDIR= /usr/src/sys\\n.include \"${SYSDIR}/conf/kmod.mk\"\\n' > Makefile && make", "run_cmd": "kldload /root/df2676_mod/df2676.ko", "code_hash": "a5bd0d8b1676e6a587901895de07423485c820eb419121c83d3dadb46f515689", "notes": "Latent for unprivileged attackers on stock kernel (parallels DF-0942 class, but with a real in-tree caller and a documented non-RETRY API contract). Fixed-address NULL read: no exploitation beyond panic even when reached. Recommended fix also improves the doc-comment contract.", "recommended_fix": "vm_page_grab(): on the busy-error path without VM_ALLOC_RETRY, 'goto failed' instead of 'break' so the NULL is returned without dereferencing m->valid.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly 6.5-DEVELOPMENT #1: Sun Aug 30 13:38:49 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Patched kernel (fix.diff: break->goto failed; built via make -j6 nativekernel KERNCONF=X86_64_GENERIC + installkernel, uname #1 13:38:49) runs the identical KLD trigger to completion: vm_page_grab returns NULL cleanly ('grab returned 0'), no trap, guest stays up. Baseline stock kernel panics at the same trigger.", "fix_evidence": [ "run_fixed.log (patched-kernel dmesg/console capture)", "fix.diff" ] } |