# DF-3107 — ufs_checkpath reads `vp->v_mount` from a zero-ref/reclaimable vnode

## What

`ufs_checkpath()` (sys/vfs/ufs/ufs_lookup.c:1114) walks the target
directory's `..` chain upward. Each loop iteration ends with:

```c
1160:		vput(vp);
1161:		error = VFS_VGET(vp->v_mount, NULL, dirbuf.dotdot_ino, &vp);
```

For every **intermediate** ancestor the vnode was VFS_VGET'd inside this
loop and is held with refcount 1, so the `vput()` at :1160 drops it to
**refcount 0** — in DragonFly's `vrele()` (sys/kern/vfs_lock.c:345) the
1→0 transition leaves the vnode cached-but-unreferenced (instantly
reclaimable by any concurrent allocvnode/vnlru), or, with VREF_FINALIZE,
runs `vnode_terminate()` (VOP_RECLAIM) synchronously — and the very next
instruction reads `vp->v_mount` from that vnode.

Every rename of a directory into a target ≥ 3 levels deep (e.g.
`mv S /a/b/c/S_moved` — target dir `c`, ancestor `b`) performs this
use-after-vrele **deterministically**; only the *consequence* (the vnode
being recycled and reinstalled by another filesystem between the vput and
the read) is racy. A recycled vnode yields `VFS_VGET` on the **wrong
mount** (cross-filesystem ancestor confusion in the rename guard), and a
terminated vnode yields a mount pointer read from a reclaimed object.

FreeBSD's `ufs_checkpath` caches `mp = ITOV(target)->v_mount` **before**
the loop and calls `VFS_VGET(mp, ...)` for exactly this reason.
DragonFly's `ext2_checkpath` (sys/vfs/ext2fs/ext2_lookup.c:1235-1236)
carries the identical bug.

## Reproduce attempt

`stress.sh`: 20,000 deep renames on a vn-backed UFS mount (each rename
does the zero-ref read on the intermediate ancestor `b`) while a churn
loop performs 60,000 file create/delete cycles for vnode pressure.

Result: no crash, no anomalous dmesg, all renames succeeded — the read
returned the stale-but-valid mount pointer every time; the recycle race
(a window of a few instructions, requiring another CPU to allocate and
reinstall that exact vnode in it) was not won. **not_reproduced** at the
effect level; the defect itself is code-certain.

## Fix

Cache the mount pointer before the loop, exactly like FreeBSD:

```diff
 	struct vnode *vp;
+	struct mount *mp;
 	...
 	vp = ITOV(target);
+	mp = vp->v_mount;
 	...
 		vput(vp);
-		error = VFS_VGET(vp->v_mount, NULL, dirbuf.dotdot_ino, &vp);
+		error = VFS_VGET(mp, NULL, dirbuf.dotdot_ino, &vp);
```

(The same one-liner applies to ext2_checkpath.)
