{
 "finding_id": "DF-3253",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "dos",
 "confidence": "certain",
 "verdict": "REPRODUCED: hpfs_hphashvget (sys/vfs/hpfs/hpfs_hash.c:104-139) hands half-initialized H_INVAL hpfsnodes to concurrent lookups because hpfs_vget publishes into the inode hash (hpfs_hphashins, hpfs_vfsops.c:525) before bread/FN_MAGIC validation (:529-541) and both error paths vx_put() without unhashing. On the guest (stock INVARIANTS kernel + 10ms cold-I/O-window instrument), an unprivileged user (uid 1001) racing stat() through two crafted directories whose dirents share one always-rejected fnode received poisoned vnodes in 40/40 and 19/19 steady-state lookups (kernel counter INVAL HANDOFF matches), got full utimes()=0 syscall successes on the rejected fnode (hpfs_setattr has no v_type check), and 2 of 4 runs panicked the kernel at the subsequent umount (unmount: dangling vnode). stat() itself aborts EBADF at cvn_stats VNON switch \u2014 the EBADF is the observable signature of the handed-off VNON vnode. Fix (hpfs_hphashrem on both post-publish error paths + H_INVAL retry in hphashvget) validated on the same instrumented kernel: 0 handoffs, 960/960 lookups EINVAL, umount clean.",
 "exploit_chain": "crafted HPFS image (two dirents -> same bad-magic fnode 0x58) -> root/usermount mounts it -> unprivileged user races lookups via the two directories (per-directory dvp locks do not serialize cross-directory vgets) -> waiter blocks in vget on creators VX lock (its ref taken first, vfs_lock.c:589) -> failing creator vx_puts -> waiter receives poisoned H_INVAL node as successful lookup -> hpfs_getattr reads h_fn (rejected fnode / uninit heap in bread-failure variant) -> utimes()=0 mutates the rejected inode (H_PARCHANGE) -> subsequent umount panics unmount: dangling vnode (kernel DoS, 2/4 runs). No user->root chain: read-side/type-confusion primitive only.",
 "evidence": [
  "run.baseline3.badmagic.log: DF-3253 SUMMARY: wins=19 einval=37 ebadf(poisoned handoffs seen by stat)=19 + UTIMES WIN ... utimes()=0 on a fnode the kernel ALWAYS rejects",
  "run.unhashonly.badmagic.log: DF-3253 SUMMARY: wins=0 einval=960 ... ebadf=0 + UMOUNT_OK (fix validation)",
  "panic.txt / panic2.txt: DF3253H: *** INVAL HANDOFF #N *** immediately followed by panic: unmount: dangling vnode (dounmount backtrace)",
  "VERDICT.md: full run matrix (40/19/0 handoffs), fix attribution, race-shape analysis"
 ],
 "kernel_refs": [
  "sys/vfs/hpfs/hpfs_hash.c:104",
  "sys/vfs/hpfs/hpfs_hash.c:116",
  "sys/vfs/hpfs/hpfs_hash.c:122",
  "sys/vfs/hpfs/hpfs_vfsops.c:525",
  "sys/vfs/hpfs/hpfs_vfsops.c:529",
  "sys/vfs/hpfs/hpfs_vfsops.c:538",
  "sys/vfs/hpfs/hpfs_vnops.c:463",
  "sys/vfs/hpfs/hpfs_vnops.c:530",
  "sys/vfs/hpfs/hpfs_vnops.c:1056",
  "sys/kern/vfs_lock.c:589",
  "sys/kern/vfs_vnops.c:894"
 ],
 "poc_changes": "Seed concept (from the DF-3251 twin pattern) reworked entirely: (1) single-directory racing is impossible \u2014 namei holds the dvp lock across VOP_LOOKUP (measured 1451 publishes / 0 waiter hash-hits) \u2014 so the image now has two directories whose dirents share the victim fnode; (2) stat()==0 is unreachable on the stock failure paths (v_type never set -> cvn_stat aborts EBADF, sys/kern/vfs_vnops.c:894) \u2014 the EBADF is kept as the handoff signature and utimes()==0 (hpfs_setattr has no v_type check) as the syscall-success proof; (3) unbuffered stdout + background/poll runner because serial-console kprintf spam costs ~100ms per failed lookup; (4) guest instrumented with a labeled 10ms tsleep in the two failure paths simulating cold bread latency (never applied to the audit sys/ tree).",
 "attempts": 9,
 "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #2: Wed Sep 9 14:50:41 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (stock #0 + labeled DF3253 validation-window instrument; hpfs.ko module swap for fix)",
 "runtime_sec": 3600,
 "guest_dirty": 1,
 "build_cmd": "cd findings/poc/DF-3253 && ./build.sh (cc -O2 -pthread -o /tmp/df3253_poc /tmp/poc.c in guest)",
 "run_cmd": "cd findings/poc/DF-3253 && REPS=1 NTHR=8 ITERS=120 ./run.sh badmagic",
 "code_hash": "af9df1010d12b7743122de510ae6752ec6f5b4f83a87de8a2c5e36e4e3e33a04",
 "notes": "Uninitialized-heap (bread-failure) variant: same two error paths and window; h_fn drives getattr/validateparent kernel-side; direct userland byte disclosure blocked by the VNON abort on this path (noted in VERDICT.md). Panic is timing-dependent: 2/4 racing runs. Dangling-vnode identity not dissected (ddb not interactive via harness).",
 "recommended_fix": "hpfs_vget: hpfs_hphashrem(hp) on both post-publish error paths (bread failure + bad FN_MAGIC) before vx_put; hpfs_hphashvget: retry instead of returning nodes still marked H_INVAL (fix.diff, validated).",
 "fix_status": "fixed",
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #2 (in-guest rebuild, hpfs.ko module swap with fix.diff; tsleep instrument retained so the validation window stayed 10ms)",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "With fix.diff (creator-side unhash validated alone via forced full module rebuild): identical PoC, identical 10ms window \u2014 0 poisoned handoffs (kernel counter 0), 0 EBADF, 960/960 lookups fail EINVAL, umount clean (previously 19-40 handoffs and 2/4 umount panics).",
 "fix_evidence": [
  "findings/poc/DF-3253/fix.diff",
  "findings/poc/DF-3253/run.unhashonly.badmagic.log",
  "findings/poc/DF-3253/VERDICT.md#fix-validation"
 ]
}