⬢ DragonFlyBSD Kernel Audit
DF-3218 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-3218",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "dirfs_roots open()-failure teardown runs dirfs_free_vp on a node with dn_vnode==NULL: vp->v_data=NULL is a kernel-mode write to 0x128 (verified instruction-for-instruction: Stopped at movq $0,0x128(%r13) = dirfs_free_vp+0x3b), followed by a latent double-free (dirfs_node_drop then dirfs_node_free) and a never-cleared dangling dmp->dm_root. Triggered on the guest by an unprivileged-in-vkernel mountpoint traversal after the host path became a dangling symlink; whole-vkernel fatal trap. fix.diff (dm_root=NULL + single dirfs_node_drop) validated: baseline panics, patched returns clean ENOENT and survives.",
 "exploit_chain": "vkernel-root mounts dirfs from a host dir (mount(2) with data=path); host dir is replaced by a dangling symlink (mv + ln -s); ANY vkernel user runs ls <mountpoint> -> VFS_ROOT -> dirfs_root -> lstat(symlink) OK -> dm_root=dnp -> open(O_DIRECTORY) follows symlink -> ENOENT -> dirfs_free_vp -> vp==NULL -> vp->v_data=NULL -> write to 0x128 -> Fatal trap 12, vkernel dead. No uid0: the store is a constant 0 at a fixed offset (no attacker data), and dirfs corruption is confined to the vkernel userland process on the host.",
 "evidence": [
  "run.baseline.log: Fatal trap 12: page fault while in kernel mode, fault virtual address = 0x128, current process = 3 (ls), Stopped at 0x72080b: movq $0,0x128(%r13)",
  "VERDICT.md: nm/objdump symbolization \u2014 0x72080b is dirfs_free_vp+0x3b, the store vp->v_data=NULL with vp==NULL, immediately before jmpq dirfs_node_drop",
  "run.fixed.log: after fix.diff, ls: /hostroot: No such file or directory and TRAVERSED echoed \u2014 vkernel alive"
 ],
 "kernel_refs": [
  "sys/vfs/dirfs/dirfs_vfsops.c:246",
  "sys/vfs/dirfs/dirfs_vfsops.c:250",
  "sys/vfs/dirfs/dirfs_vfsops.c:258",
  "sys/vfs/dirfs/dirfs_vfsops.c:261",
  "sys/vfs/dirfs/dirfs_vfsops.c:262",
  "sys/vfs/dirfs/dirfs_subr.c:300",
  "sys/vfs/dirfs/dirfs_subr.c:305",
  "sys/vfs/dirfs/dirfs_subr.c:96"
 ],
 "poc_changes": "dirfs does not compile on the current tree: added sys/malloc.h to dirfs_vnops.c and dirfs_subr.c, sys/uio.h to dirfs_vnops.c, and -Wl,--defsym,__build_id_{start,end}=0 to the vkernel link (guest scratch tree only). PoC itself needed: custom VKDIRFS kernel config (VKERNEL64 + options DIRFS), 160MB FFS root image with minimal userland, vkinit.c console-opening init wrapper (start_init opens no fds), vkmount.c direct mount(2) caller, fifo console driver with a persistent writer (vkernel reads console input from fd 0).",
 "attempts": 6,
 "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 (guest); vkernel: VKDIRFS ident, DragonFly 6.5-DEVELOPMENT",
 "runtime_sec": 2100,
 "guest_dirty": 1,
 "build_cmd": "cd /usr/src && make -j6 nativekernel KERNCONF=VKDIRFS (after include+link fixes; full log build.vkernel-full.log)",
 "run_cmd": "sh /tmp/vkpoc2.sh (pack run.sh): vkernel -m 512m -r /vkroot.img -e vfs.root.mountfrom=ufs:vkd0s1a -e init_path=/sbin/vkinit; then /bin/vkmount /dirfs-host /hostroot; host: mv+ln dangling symlink; then: ls /hostroot",
 "code_hash": "548ac03fe363342e1841267fdc2c6ccc90472b640281055afb1e4e717d80462a",
 "notes": "dirfs is vkernel64-only: kernel here is the vkernel process on the guest; the panic kills the entire vkernel (all its processes), reachable by any vkernel user traversing the mountpoint. Double-free + dangling dm_root are latent behind the deterministic NULL-write; fix.diff removes all three. Guest was left with the scratch build tree; reset with-src afterwards.",
 "recommended_fix": "In dirfs_roots open-failure path, clear dmp->dm_root and drop the life-of-mount reference instead of calling dirfs_free_vp/dirfs_node_free on a vnode-less node (see fix.diff).",
 "fix_status": "fixed",
 "fix_kernel_uname": "vkernel VKDIRFS (DragonFly 6.5-DEVELOPMENT, guest scratch tree with fix.diff)",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "Rebuilt the vkernel with fix.diff and re-ran the identical PoC: baseline fatal trap at movq $0,0x128(%r13) is gone; ls /hostroot returns ENOENT cleanly and the vkernel stays alive (run.fixed.log, fix-build.log).",
 "fix_evidence": [
  "run.fixed.log",
  "fix-build.log",
  "fix.diff"
 ]
}