⬢ DragonFlyBSD Kernel Audit
DF-3230 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-3230",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "Deterministic kernel NULL-deref panic, reproduced 3x on the stock INVARIANTS guest (2 incidental during DF-3229 staging + 1 with the minimal PoC), identical signature every time: fuse_unmount() clears mp->mnt_data (fuse_vfsops.c:294); an fstatfs(2) on a still-open fd reaches fuse_statfs() via kern_fstatfss fp->f_nchandle.mount with no vnode ops, where the unguarded VFSTOFUSE(mp) NULL feeds fuse_ipc_get() (fuse_ipc.c:100) -> atomic_fetchadd at NULL+0x160 -> Fatal trap 12, supervisor write, page not present / Stopped at fuse_ipc_get+0x63. The NULL window lives inside the umount syscall, so a plain fstatfs race loop catches it with no timing precision. Distinct from DF-3193 (freed-fmp walk on the wake path of an in-flight statfs); this is the entry path with a NULL fmp. Fix validated in-guest: NULL/dead guard on fuse_statfs+fuse_statvfs entries, module rebuilt, identical PoC rerun -> no panic, guest up.",
 "exploit_chain": "",
 "evidence": [
  "panic.txt / serial_full.txt: full captured panic on the serial console from the minimal df3230.c run (current-boot capture, not reconstructed)",
  "run.log: mount fd opened; umount thread + fstatfs race loop followed by the panic on stock",
  "fuse_vfsops.c:294 (mnt_data=NULL) + :393 (unguarded VFSTOFUSE->fuse_ipc_get) + fuse_ipc.c:100 (fetchadd on fmp->unique)"
 ],
 "kernel_refs": [
  "sys/vfs/fuse/fuse_vfsops.c:294",
  "sys/vfs/fuse/fuse_vfsops.c:388",
  "sys/vfs/fuse/fuse_vfsops.c:393",
  "sys/vfs/fuse/fuse_ipc.c:100",
  "sys/kern/vfs_syscalls.c:1495"
 ],
 "poc_changes": "Original discovery was incidental (two DF-3229 staging panics, signature fuse_ipc_get+0x63 fault 0x160). Minimal PoC written from scratch; the naive sequential form (umount then fstatfs) wedged in vflush, so the final form races a fstatfs loop against a umount thread, which catches the mnt_data==NULL window inside the umount syscall deterministically.",
 "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": 900,
 "guest_dirty": 0,
 "build_cmd": "mkdir -p /tmp/df3230d && cc -O2 -Wall -o /tmp/df3230d/df3230 /tmp/df3230.c -lpthread",
 "run_cmd": "kldload fuse && timeout 100 /tmp/df3230d/df3230  (stock expectation: kernel panic Stopped at fuse_ipc_get+0x63)",
 "code_hash": "3581d138291a0ab448ba95eb46996d1cbbbe46b5d38dff4efc2348522768a0be",
 "notes": "Guest reset to clean with-src state after validation. Reachability: unmount requires mount-owner/root (or usermount policy) while the fstatfs side is any process holding an fd on the mount - a standard admin-unmounts-under-user-traffic DoS. The fuse_vop_* paths have the same VFSTOFUSE pattern but are unreachable post-vgone (dead-vnode VOP failure), so fstatfs/getvfsstat-style mp-based entries are the fix surface; statvfs guarded for symmetry.",
 "recommended_fix": "Guard fuse_statfs() and fuse_statvfs() entries: if (fmp == NULL || fuse_test_dead(fmp)) return ENOTCONN; (fix.diff)",
 "fix_status": "fixed",
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0 + in-guest rebuilt fuse.ko (fix.diff composed with DF-3229/DF-3227 fixes)",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "Baseline: 3/3 stock panics (identical signature). Patched module (fuse_vfsops.c guards): identical PoC rerun -> race loop completes with ENOTCONN returns, no panic, guest up.",
 "fix_evidence": "fix.diff; validation session output: patched run mount fd opened; umount thread + fstatfs race loop then clean completion, guest up (vs panic.txt baseline)"
}