# DF-3252 — ffs_vget publish-before-validate: bread-failure branch hands waiters a VBAD zeroed-inode vnode (DF-3208 ufs twin)

**Status: untested** (Low severity; outside the mandatory Phase V buckets).

## What

`ffs_vget()` (sys/vfs/ufs/ffs_vfsops.c:1066) inserts the fresh inode into
the per-mount ihash (`ufs_ihashins`, :1120) **before** reading the dinode
from disk. If the read fails (:1134-1145) the creator sets `vp->v_type =
VBAD` and `vx_put`s the still-hashed, still-uninitialized (M_ZERO) inode.
A concurrent `ufs_ihashget()` waiter (ufs_ihash.c:105 `vget()` — it blocked
on the creator's VX lock holding a reference) wakes and receives the VBAD
vnode with an all-zero dinode (`i_mode 0`, `i_devvp NULL`,
`i_effnlink 0`) as a **successful** `VFS_VGET` result.

Reachability from crafted images: `ufs_lookup()` passes the directory
entry's `dp->i_ino` to `VFS_VGET` with no range validation
(ufs_lookup.c:480,527,567,588), so a crafted dirent whose `ino_to_fsba`
lands beyond the device end makes `bread()` fail deterministically
(verified reachable in principle; the DF-3190 crafted-FFS harness is the
basis for a PoC).

Why only Low (materially weaker than the ext2 twin DF-3251):

* `ufs_vinit` returns 0 unconditionally (ufs_vnops.c:1954) — its error
  branch at ffs_vfsops.c:1158 is dead code, so no fully-initialized
  escaped inode.
* The zeroed inode short-circuits `ufs_inactive` (`i_mode == 0`,
  ufs_inode.c:75) and `ufs_reclaim` tolerates the never-assigned
  `i_devvp == NULL` (all `if (ip)` guards, ufs_inode.c:126-163).
* No memory-safety sink identified; the defect is a VFS-contract violation
  (VBAD vnode escaping a successful lookup into namei/NFS).
* DF-0928 already covers the unlocked hash; this finding is the
  publish-before-validate ordering on top of it.

## Fix sketch

Insert into the ihash only after the dinode bread succeeds (hash a
VX-locked placeholder, complete in place), or `ufs_ihashrem` + VBAD before
`vx_put` on the error branch.
