⬢ DragonFlyBSD Kernel Audit
DF-5289 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5289",
  "status": "reproduced",
  "verdict": "Reproduced on a vkernel booted inside the QEMU guest: two mlock/munlock cycles on a /dev/upmap (VM_MAPTYPE_UKSMAP) mapping make the stock vkernel panic with vm_page_unwire: invalid wire count: 0 via sys_munlock -> vm_map_user_wiring -> vm_fault_unwire -> vm_page_unwire; the first cycle already silently drops the only wire_count of the live kmallocd upmap kernel slab page (reclaim/UAF-class corruption). Root cause is split between this file (pmap_enter wires a transient fictitious stack page and marks the PTE VPTE_WIRED with no vm_page wire; pmap_unwire fabricates and returns the real vm_page for an unmanaged wired PTE) and the trigger-side misclassification in shared sys/vm/vm_fault.c:2679 (vm_fault_unwires fictitious flag misses VM_MAPTYPE_UKSMAP, unlike vm_fault_wire:2614). A kernel built with fix.diff (vm_fault.c mirror + pmap_unwire managed-only) runs the identical PoC cleanly: all 4 cycles return 0, DF5289: SURVIVED - no panic, shell alive. mlock is SYSCAP_RESTRICTEDROOT-gated, so the trigger needs root inside the vkernel (same gating on pc64, which shares the vm_fault.c half).",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "exploit_chain": "open(\"/dev/upmap\", O_RDWR) -> mmap(MAP_SHARED) creates a VM_MAPTYPE_UKSMAP entry -> mlock: vm_fault fabricates a STACK fictitious vm_page and calls pmap_enter(wired=TRUE) which sets VPTE_WIRED but vm_page_wire() is a no-op on fictitious pages -> munlock: vm_fault_unwire misclassifies UKSMAP as non-fictitious, pmap_unwire returns the REAL vm_page of the kmallocd upmap page -> vm_page_unwire drops the slab pages only wire (cycle 0, silent kernel heap corruption) and panics on cycle 1 (vm_page_unwire: invalid wire count). Push-to-root beyond panic was not pursued: the primitive corrupts wire accounting of an arbitrary-wire page only for UKSMAP-backed frames (upmap/kpmap/lpmap), and the trigger is already root-gated inside the vkernel, so escalation adds nothing in this threat model.",
  "evidence": "run.baseline.log (full vulnerable-vkernel console: cycle-0 munlock returns 0 then panic vm_page_unwire: invalid wire count: 0 with backtrace vm_page_unwire <- vm_fault_unwire <- vm_map_user_wiring <- sys_munlock); run.fixed.log (identical PoC on fix.diff kernel: 4 clean cycles, DF5289: SURVIVED - no panic); panic.txt (decisive excerpt); df5289.c (PoC); run.sh (driver); build.log/fix-build.log (kernel builds); fix.diff (vm_fault.c + pmap.c two-layer fix, applied and validated in guest)",
  "kernel_refs": [
    "sys/platform/vkernel64/platform/pmap.c:2429",
    "sys/platform/vkernel64/platform/pmap.c:2441",
    "sys/platform/vkernel64/platform/pmap.c:2652",
    "sys/vm/vm_fault.c:2679",
    "sys/vm/vm_fault.c:2614",
    "sys/vm/vm_fault.c:562",
    "sys/vm/vm_fault.c:2691",
    "sys/kern/kern_slaballoc.c:1830",
    "sys/vm/vm_mmap.c:1030",
    "sys/kern/kern_memio.c:95"
  ],
  "poc_changes": "PoC authored fresh (reserved IDs class had no seed): open+mmap /dev/upmap + 4x mlock/munlock with per-call errno printing. Environment had to be built from scratch: VKERNEL64 nativekernel build (after working around this commits broken vkernel64 link via a 2-symbol __build_id shim in init.c), a 1200MB whole-disk UFS vkroot.img (bin/lib/libexec + static PoC + vkinit console wrapper), and a fifo-console driver that PRE-FEEDS the mountroot answer ufs:vkd0 because the vkernel console drivers cncheckc returns EOF when no input is buffered (the mountroot prompt cannot wait).",
  "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 (vkernel inside: DragonFly 6.5-DEVELOPMENT #1: Fri Oct  2 05:54:28 UTC 2026, VKERNEL64)",
  "runtime_sec": 1650,
  "guest_dirty": 1,
  "build_cmd": "guest: cd /usr/src && make -j6 nativekernel KERNCONF=VKERNEL64 ; cc -static -O -o /mnt/root/poc df5289.c ; cc -static -O -o /mnt/bin/vkinit vkinit.c",
  "run_cmd": "guest (root): sh run.sh   # boots /usr/obj/usr/src/sys/VKERNEL64/kernel.debug -m 512m -r /vkroot.img -e init_path=/bin/vkinit -s, feeds console via fifo, runs /root/poc",
  "code_hash": "66653717b0793845b64b5020962b3135ac111f8ca5c95f94af92228e03a62115",
  "notes": "Same trigger chain exists on pc64 (shared vm_fault.c misclassification + pc64 pmap_unwires identical unmanaged PHYS_TO_VM_PAGE at pc64/pmap.c:5598-5603/5627-5633); filed against vkernel64/platform/pmap.c per assignment. The cycle-0 silent half (slab page wire 1->0 on a live kmalloc page) is the memory-corruption primitive; the cycle-1 panic is its deterministic witness. vkernel64 target has an unrelated build regression at this commit (kern_mib.c __build_id_* vs script-less SYSTEM_LD) - recorded, shimmed around, worth a separate maintenance note upstream.",
  "recommended_fix": "vm_fault_unwire: mirror vm_fault_wires maptype switch (UKSMAP => fictitious=TRUE); pmap_unwire: return a vm_page only for VPTE_MANAGED wired PTEs (fix.diff, both hunks applied and validated on the guest)",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": true,
  "fix_verdict": "fix.diff applied to guest /usr/src (patch -p1, both hunks), vkernel relinked (vm_fault.o + pmap.o rebuilt), identical PoC re-run: 4/4 mlock+munlock cycles return 0, no panic, vkernel alive at shell prompt; baseline (pre-fix) same binary panicked at cycle 1 munlock. Bad behavior eliminated.",
  "fix_evidence": "run.fixed.log (clean cycles + SURVIVED marker); fix-build.log (patched rebuild); fix.diff (the applied diff)"
}