DF-3241 / verdict.json
{ "finding_id": "DF-3241", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Deterministic kernel NULL-deref panic reproduced on the stock INVARIANTS kernel: an fhstat(2) resolving a UFS mounts fsid inside the dounmount() window between VFS_UNMOUNT() (ffs_unmount frees the ufsmount, mp->mnt_data=0 at ffs_vfsops.c:862-866) and mountlist_remove() (vfs_syscalls.c:1040) faults at ffs_fhtovp+0xb reading 0x18(%rax) with rax=mnt_data=NULL. Baseline hit within 3 mount/umount -f rounds. vfs_getvfs() (vfs_mount.c:413-424) hands out mounts in that window because it never checks MNTK_UNMOUNT. Unauthenticated reachability exists via nfsrv_fhtovp() (nfs_subs.c:1235-1246) against any exported UFS with nfsd running; the fh* syscalls used for the PoC are root-gated (same accepted methodology as the DF-3236 devfs twin). Impact ceiling is a kernel DoS panic (fixed-address read of a NULL pointer field), not a corruption primitive; no uid0 chain exists for this bug.", "exploit_chain": "", "evidence": [ "panic.txt: Fatal trap 12 ... fault virtual address = 0x18 + Stopped at ffs_fhtovp+0xb: movq 0x18(%rax),%r9 from process fhbomb (stock kernel, <=3 rounds)", "run.log: baseline host transcript and window analysis (0x18 == offsetof(struct ufsmount, um_fs))", "run.fix.log: fix-v2 kernel SANITY_OK + round 6000 done + SURVIVED 6000 rounds without panic", "VERDICT.md: why fix v1 (vfs_getvfs flag filter alone) still panicked and why the vfs_busy() interlock closes it", "fix.diff: 5-hunk validated fix (git-apply clean)" ], "kernel_refs": [ "sys/vfs/ufs/ufs_vfsops.c:242-246", "sys/vfs/ufs/ffs_vfsops.c:1215-1219", "sys/vfs/ufs/ffs_vfsops.c:862-866", "sys/kern/vfs_syscalls.c:1006", "sys/kern/vfs_syscalls.c:1040", "sys/kern/vfs_mount.c:413-424", "sys/vfs/nfs/nfs_subs.c:1235-1246" ], "poc_changes": "No seed PoC existed; written from scratch for this finding. Fixes during bring-up: ESTALE needs sys/errno.h on DragonFly; guest sh choked on a stray * comment line in race.sh; DFly vnconfig takes no -s size arg (size comes from the backing file, created with dd); truncate -s 64m unsupported, used dd; newfs on /dev/vn0 auto-creates the compatibility slice (mounted as /dev/vn0s0).", "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": 3780, "guest_dirty": 0, "build_cmd": "host: bash findings/poc/DF-3241/build.sh; guest: cc -O2 -o fhbomb fhbomb.c (etc.); fix kernel: cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC", "run_cmd": "guest(root): ROUNDS=6000 NBOMB=8 sh /root/poc/race.sh", "code_hash": "459441aa6038f35135d2f536019a19317b3eb27eafdde72fe24b1dfe0429f02c", "notes": "code_hash is sha256 of fhbomb.c (trigger); race.sh sha256 = 942bbbb16ea6bcaa632c9bd9bd4a3517c772e2d43863d1ef8f49a53d29bc6155. Fix iteration result worth upstream attention: a vfs_getvfs() MNTK_UNMOUNT filter ALONE is insufficient (proven by a second panic on the fix-v1 kernel) because check-and-use is preemptible; the vfs_busy(mp, LK_NOWAIT) interlock (shared mnt_lock excludes the whole dounmount body incl. forced unmount) is required. fhopen/fhstatfs/fhstatvfs and kern_checkpoint should get the same vfs_busy pattern (mechanical follow-up, root-gated paths). Post-panic disk state preserved in qcow2 snapshot df3241-postpanic. Guest reset to with-src after validation.", "recommended_fix": "Interlock fsid-based file-handle resolution against unmount: vfs_busy(mp, LK_NOWAIT) around VFS_CHECKEXP/VFS_FHTOVP in nfsrv_fhtovp() and sys_fhstat() (and the remaining fh* syscalls), bail with ESTALE on failure; additionally filter MNTK_UNMOUNT mounts in vfs_getvfs() and NULL-guard mnt_data in ufs_check_export()/ffs_fhtovp().", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Wed Sep 9 05:56:10 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Validated on-guest: baseline (stock #0 kernel) panicked at ffs_fhtovp+0xb within 3 rounds; a first fix attempt (vfs_getvfs MNTK_UNMOUNT filter only, kernel built and booted) STILL panicked within ~20 s, proving the preemptible check-and-use TOCTOU; the final 5-hunk fix (vfs_busy interlock in sys_fhstat + nfsrv_fhtovp + vfs_getvfs filter + NULL guards in ufs_check_export/ffs_fhtovp, fix.diff) survived 6000/6000 mount+umount -f rounds with 8 fhstat spinners and zero panics (run.fix.log).", "fix_evidence": [ "fix.diff (git apply --check clean, 6 hunks across 5 files)", "run.fix.log: SANITY_OK, round 6000 done, SURVIVED 6000 rounds without panic", "build.log: full nativekernel build of the patched kernel (3756 objects relinked)", "serial log of fix-v1 panic captured in session (same ffs_fhtovp+0xb signature) documented in VERDICT.md" ] } |