{
  "finding_id": "DF-2998",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "kernel type confusion -> deterministic panics (two new independent memory-corruption sinks) + server-driven device-node injection into the local namespace",
  "confidence": "certain",
  "verdict": "REPRODUCED on the stock kernel: a lying NFSv3 server retypes LIVE client vnodes through nfs_loadattrcache (nfs_subs.c:723-733, no VNON guard) \u2014 an open, written-to VREG file became a DIRECTORY and then a CHARDEV (server rdev) mid-life via single attribute replies, as root and as an unprivileged user; one vnode was retyped three times (REG->DIR->CHR->DIR). Two independent deterministic panics from the engine: (A) Fatal trap 9 GPF at nfs_removerpc+0x57 \u2014 a cookie-cached node left non-VDIR at inactive makes nfs_inactive read n_sillyrename (aliases the n_un3 cookie-list head, nfsnode.h:125-133) and walk an nfsdmap as struct sillyrename (wild fake-vnode deref, then crfree/vrele/wrong-zone kfree); (B) panic: bgetvp - overlapping buffer via nfs_getattrcache->nvtruncbuf \u2014 the VDIR->VREG retype feeds directory buffer state through the regular-file shrink path (nfs_subs.c:943-949). Injected VCHR nodes are honestly inert for I/O on this DragonFly (nfsv2_spec_vops vop_stdnoread; devfs rdev pointer never set) \u2014 injection is a namespace/type spoof, not the old-BSD raw-device primitive. Fix validated on a single-fix rebuilt kernel: type assigned only at creation (VNON); live-type lies refused with EBADRPC, both panics gone, unmounts clean, full honest lifecycle works.",
  "exploit_chain": "userland fake NFSv3 server (loopback) + mount as root -> unprivileged user opens file (honest VREG attrs, node typed VNON->VREG at creation) -> server flips attrs for the fh (LIE) -> after attribute-cache expiry the next stat/ACCESS/GETATTR applies them in nfs_loadattrcache -> live vnode retyped (v_type + v_ops + addaliasu) -> (A) getdents on the retyped-VDIR file caches nfsdmap cookies, later leave node non-VDIR -> umount/inactive walks the nfsdmap as sillyrename -> nfs_removerpc wild deref -> panic; (B) drain a >4KiB dir (cookies cached), retype VDIR->VREG, stat -> nvtruncbuf on directory buffers -> bgetvp overlap -> panic",
  "evidence": "run.log (baseline: *** live VREG vnode retyped VDIR while fd open ***, *** device node injected ***, root+unpriv, TEST2 -> panic); panic.bgetvp.txt (bgetvp overlapping-buffer trace via nfs_getattrcache+nvtruncbuf); panic.removerpc.txt (GPF at nfs_removerpc+0x57); fix_run.log (all lies refused RPC struct is bad, no panic, clean umounts, honest lifecycle); VERDICT.md carries the full line-accurate analysis",
  "kernel_refs": [
    "sys/vfs/nfs/nfs_subs.c:723-733",
    "sys/vfs/nfs/nfs_subs.c:943-949",
    "sys/vfs/nfs/nfs_subs.c:1338-1356",
    "sys/vfs/nfs/nfs_node.c:392-395",
    "sys/vfs/nfs/nfs_node.c:464-473",
    "sys/vfs/nfs/nfsnode.h:125-133",
    "sys/vfs/nfs/nfs_vnops.c:1913-1918",
    "sys/vfs/nfs/nfs_vnops.c:162-175"
  ],
  "poc_changes": "pass-1 had no pack for DF-2998 (finding was filed from the DF-2996 typelie observation); this run built the full rig: new fakesrv2998.c (control-channel per-fh attribute lies, derived from DF-2996s fakesrv) and trigger2998.c (retype/dirreg/ctrl modes), run.sh with 1s attribute-cache mount options (stock cache timing delayed lies up to 60s via the ancient-mtime acregmax clamp)",
  "attempts": 4,
  "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": 2400,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df2998 && cc -O -o fakesrv2998 fakesrv2998.c && cc -O -o trigger2998 trigger2998.c",
  "run_cmd": "cd /root/df2998 && sh run.sh",
  "code_hash": "52302ba4088fb0943cf6b3f462a31522c9dbb147878e9433df9ca9ba8b7380bb",
  "notes": "uid=0 not pursued within this verify run: demonstrated primitives are wild-pointer derefs through partially server-shaped kernel heap objects (nfsdmap cookies, fake vnode fields from nfsnode bytes); weaponization needs slab grooming + a leak to place fake v_ops; the two deterministic panics + the injection support the findings High/memcorrupt rating. Attribute-cache honesty: with stock mount options the retype fires after the (up to 60s) attribute timeout; the PoC pins acreg*/acdir* to 1s for determinism. Panic A was first observed unplanned (umount of TEST-1s leftover CHR-typed, cookie-cached mount) and is preserved in panic.removerpc.txt. DF-2996s fix (sillyrename identity/type check) is orthogonal: it guards one consumer; this fix kills the retype engine itself.",
  "recommended_fix": "only assign v_type at creation (VNON); on server-type mismatch of a live vnode, invalidate the attr cache and fail the load with EBADRPC (see fix.diff)",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly 6.5-DEVELOPMENT #1: Mon Oct 12 12:33:28 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Single-fix kernel (fix.diff on clean /usr/src, nativekernel X86_64_GENERIC, installkernel, reboot): baseline reproduced live retypes (root+unpriv), device injection, and both panics; on the patched kernel every server lie is refused with EBADRPC (RPC struct is bad), no retype/injection occurs, TEST 1/1b/2 all pass without panic, umounts are clean, and an honest-server lifecycle (create, write, read-back, stat, mkdir, 500-entry readdir, rm, umount) is fully functional. Bad behavior gone; honest NFS unaffected.",
  "fix_evidence": "fix_run.log (refusals + clean umounts + honest lifecycle); fix_build.log (build/install record); compare run.log (baseline retypes/panics)"
}