DragonFlyBSD Kernel Audit
DF-2842 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2842",
  "status": "not_reproduced",
  "reproduced": 0,
  "impact": "none",
  "confidence": "likely",
  "verdict": "vnode_pager_reference() (sys/vm/vnode_pager.c:203-210) is the only vp->v_object entry point with no interlock: it loads vp->v_object unlocked and then does vm_object_reference_quick()'s unlocked atomic_add on ref_count (vm_object.c:527-536). A concurrent forced reclaim (umount -f / revoke -> vclean_vxlocked vfs_subr.c:1285 -> vm_object_terminate vm_object.c:746 -> vnode_pager_dealloc vnode_pager.c:213 -> vm_object_drop -> kfree_obj vm_object.c:369) that reaches kfree while the mmap thread sits between the two statements corrupts the ref_count of freed (typically recycled) vm_object memory. The code defect is certain from source; on the stock guest the kernel-side UAF did not land in bounded stress (10+ cycler cycles, ~1e6 mmap iterations, ~2560 object terminations raced): the window is a few instructions wide, the only reclaimers that touch referenced vnodes are root-gated on default config (vfs.usermount=0 verified), and dounmount()'s kill loop (vfs_syscalls.c:925-950) SIGKILLs fd-holders before the termination sweep, fighting the staging. The userspace leg of the same missing interlock DID reproduce reliably: racer (uid 1002) mmap'd vnodes mid-vclean 40/40 times, installed the dying object, and SIGSEGV'd on first touch (dmesg: 'pid (racer), uid 1002: exited on signal 11'). Fix mirrors vnode_pager_alloc()'s protocol: v_token + vm_object_hold + OBJ_DEAD/v_object recheck before the quick reference.",
  "exploit_chain": "not developed (primitive not reproduced): would be unpriv mmap loop racing owner-run force-unmount of a usermount (vfs.usermount=1) or root-cooperated umount -f with a thread blocked in VOP_GETATTR on a slow FS to survive the kill-before-sweep; hit yields ref_count++ on a recycled vm_object -> premature termination of an unrelated object -> UAF cascade -> potential uid0",
  "evidence": [
    "VERDICT.md: full analysis, run results, staging blockers",
    "dmesg.txt: 40x 'racer uid 1002 exited on signal 11' (mmap-of-dying-object leg)",
    "run.log / run.2.log: cycler output, cycles and forced-umount warnings",
    "racer.c / cycler.sh: the harness",
    "sys/vm/vnode_pager.c:203-210 vs :127-137 (alloc interlock) and sys/kern/vfs_subr.c:1375-1393 (clean interlock)"
  ],
  "kernel_refs": [
    "sys/vm/vnode_pager.c:203",
    "sys/vm/vnode_pager.c:207",
    "sys/vm/vnode_pager.c:213",
    "sys/vm/vm_mmap.c:1410",
    "sys/vm/vm_object.c:527",
    "sys/vm/vm_object.c:367",
    "sys/kern/vfs_subr.c:1375",
    "sys/kern/vfs_syscalls.c:931"
  ],
  "poc_changes": "seed harness rewritten: added multi-file fd round-robin (256 tmpfs files), fork children, touch-after-mmap to force pager activity; cycler restarts racer each umount cycle; guest-side su -m/csh quoting issue fixed by running racer via /tmp/runracer.sh with plain sh",
  "attempts": 3,
  "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": 570,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -o /tmp/racer racer.c",
  "run_cmd": "sh cycler.sh test 150   (root; racer runs as uid 1002)",
  "code_hash": "fd86acd069c522dc24f8bca633cbfb944c806bd475d795e7a7b488e1a749e66a",
  "notes": "impact 'none' = no kernel corruption demonstrated; userspace SIGSEGV manifestation is 40/40 reliable. vfs.usermount=0 on guest (default) keeps the unpriv path gated. Kill-before-sweep ordering (unmount_allproc_cb SIGKILL at retry 7 vs sweep at retry 16) is the practical staging blocker for fd-holding racers.",
  "recommended_fix": "take vp->v_token and vm_object_hold() with OBJ_DEAD/v_object recheck before vm_object_reference_quick() in vnode_pager_reference(), mirroring vnode_pager_alloc()",
  "fix_status": "not_testable",
  "fix_kernel_uname": "",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff authored against sys/vm/vnode_pager.c after verification (hold+recheck protocol); not kernel-built/validated because the corruption primitive itself did not reproduce on the stock guest within the bounded run - a patched build cannot be distinguished from baseline by this harness",
  "fix_evidence": ["fix.diff"]
}