DF-3208 / verdict.json
{ "finding_id": "DF-3208", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Static chain certain end-to-end: cd9660_vget_internal publishes the iso_node into the global ihash (vfsops.c:763, node.c:143-160) before on-media validation; all five post-publish error paths (:775/:782/:793/:802/:838) vx_put() without hash removal, so a concurrent cd9660_ihashget() blocked in vget (ref-before-lock, vfs_lock.c:589) wakes with the orphaned half-initialized VNON vnode (i_mnt==NULL) and RETURNS IT AS SUCCESS; the first VOP (cd9660_getattr vnops.c:158) NULL-derefs -> panic. Live on the guest: the failing path executes ~45k/s (stat -> EINVAL from :837-838 with a beyond-device RRIP CL entry; in-device CL -> stat succeeds, proving the vget ran). Dynamically NOT reproduced across ~80M races in 6 configurations: cd9660 is not MPSAFE (vfsops.c:97), every entry is serialized by the global mp_token (LWKT token), tokens are released only on a voluntary block (lwkt_switch), and no reachable failure window contains one -- the beyond-device bread fails synchronously in vn dscheck; interrupt preemption does not release tokens (lwkt_preempt). Boot-tuned buffer starvation (kern.nbuf=1024/256) + up to 40 churn readers did not open a geteblk wait inside the window. The window opens (near-deterministic panic) whenever the in-window code blocks: failing-but-slow device I/O, an MPSAFE conversion of cd9660, or any future sleep added between ihashins and the error paths. DF-3194 (NFS twin, not_reproduced for the same structural reason) is the precedent.", "exploit_chain": "", "evidence": [ "findings/poc/DF-3208/VERDICT.md (full chain with path:line + why-not analysis + run matrix)", "findings/poc/DF-3208/run.log (six race runs, ~80M attempts, EINVAL each attempt, no panic)", "findings/poc/DF-3208/craft_df3208.py (RRIP CL-based trigger image; in-device vs beyond-device CL demonstrated both outcomes)", "findings/poc/DF-3208/trigger.c (two-directory hammer defeating the per-parent vnode lock)", "findings/poc/DF-3208/fix.diff (cd9660_ihashpurge on all 5 error paths + vput in ihashget retry)" ], "kernel_refs": [ "sys/vfs/isofs/cd9660/cd9660_node.c:107-136", "sys/vfs/isofs/cd9660/cd9660_node.c:143-160", "sys/vfs/isofs/cd9660/cd9660_node.c:216-236", "sys/vfs/isofs/cd9660/cd9660_vfsops.c:732-842", "sys/kern/vfs_lock.c:571-647", "sys/kern/vfs_lock.c:761-768", "sys/kern/vfs_default.c:184-243", "sys/vfs/isofs/cd9660/cd9660_vnops.c:151-159" ], "poc_changes": "Crafted ISO per finding sketch was wrong in three ways, all fixed: (1) RRIP lookup derives a non-dir entrys ino from b_bio1.bio_offset+entryoffset (lookup.c:255-259), not isodirino -- trigger entry must be a DIRECTORY (flags=2) with an RRIP CL entry to steer dp->i_ino to an attacker-chosen LBA (relocated=1 -> vfsops.c:837-838 failure); (2) the DF-3146-era vnconfig -S 2048 recipe creates a 2048MB device and silently grows the backing file, making every beyond-device offset in-device -- attach without -S; (3) directory records need even-padded reclen >= 34+namelen (vnops.c:524) and PX fields. Two different parent directories are required to defeat the per-parent vnode lock in vop_compat_nresolve (vfs_default.c:200).", "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", "runtime_sec": 2400, "guest_dirty": 0, "build_cmd": "cc -O2 -pthread -o /root/trigger /root/trigger.c", "run_cmd": "vnconfig -c /dev/vn0 /root/df3208.iso && mount_cd9660 /dev/vn0 /mnt/df3208 && su -m nobody -c /root/trigger /mnt/df3208 5000000 6", "code_hash": "335b18759f2d277013ccab16051aaddcc71d2f4df2f317d0b3ff880cc34f48f4", "notes": "Guest reset with-src after the runs (loader.conf.local kern.nbuf reverted, stock kernel #0, clean source). No panic.txt (never panicked). ~80M races: 200k idle / 60M with ping-flood+churn / 400k rate-measure (45k/s) / 1.2M nbuf=1024 / 6M nbuf=1024+8 readers / 12M nbuf=256+40 readers. Negative-result notes for the file audit are in the task_log and the returned JSON.", "recommended_fix": "Purge the iso_node from the ihash (new cd9660_ihashpurge: v_data=NULL, ihashrem, vrele i_devvp if set, kfree) on all five post-ihashins error paths in cd9660_vget_internal, and add the now-required vput(vp) before the goto-loop retry in cd9660_ihashget.", "fix_status": "not_testable", "fix_kernel_uname": "", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "Baseline never reproduced on the stock guest (window closed by the non-MPSAFE mp_token), so a fix-build A/B validation is vacuous: fix_status=not_testable. The patch is a direct structural closure of the proven handoff (remove-from-hash before vx_put) and compiles by inspection against the cited lines; it was NOT applied to sys/.", "fix_evidence": [] } |