DF-3252 / verdict.json
{ "finding_id": "DF-3252", "status": "untested", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Structural finding only (Low, DF-3208 ufs twin): ffs_vget inserts the inode into the unlocked per-mount ihash at ffs_vfsops.c:1120 before reading the dinode; on the bread error branch (:1134-1145, reachable from crafted images - ufs_lookup.c:480 passes directory-entry ino values to VFS_VGET with no range check, so ino_to_fsba can address past the device end) the creator marks the vnode VBAD and vx_puts it, and a concurrent ufs_ihashget waiter that blocked in vget while holding a reference receives the VBAD vnode with an all-zero dinode (i_mode 0, i_devvp NULL) as a successful VFS_VGET result. Materially weaker than the ext2 twin (DF-3251): ufs_vinits error branch is dead code (ufs_vinit returns 0 unconditionally - ufs_vnops.c:1954), the zeroed inode short-circuits ufs_inactive (ufs_inode.c:75 i_mode==0) and ufs_reclaim tolerates NULL i_devvp, so no memory-safety sink was identified; the escaped VBAD vnode violates the VFS_VGET contract and is cached/handed to namei callers. Note DF-0928 already covers the unlocked hash itself; this finding is the publish-before-validate ordering, not the missing lock.", "exploit_chain": "", "evidence": [ "README.md (path:line analysis)" ], "kernel_refs": [ "sys/vfs/ufs/ffs_vfsops.c:1120", "sys/vfs/ufs/ffs_vfsops.c:1134", "sys/vfs/ufs/ufs_ihash.c:105", "sys/vfs/ufs/ufs_lookup.c:480", "sys/vfs/ufs/ufs_vnops.c:1954", "sys/vfs/ufs/ufs_inode.c:75" ], "poc_changes": "", "attempts": 0, "guest_uname": "", "runtime_sec": 0, "guest_dirty": 0, "build_cmd": "", "run_cmd": "", "code_hash": "", "notes": "Would need a crafted FFS image plus a barriered lookup pair; DF-3190s crafted-FFS harness is the starting point.", "recommended_fix": "Move ufs_ihashins after the dinode bread succeeds (hash a VX-locked placeholder and remove it on error), or ufs_ihashrem + VBAD before vx_put on the error branch." } |