# DF-0008 — PoC

`expub.c` — root-only trigger of the `vfs_setpublicfs()` use-after-`vput` of
the root vnode (and the `VFS_VPTOFH`-error refcount leak on a filesystem whose
`vptofh` can fail).

## Verdict

**Real and reachable, but NOT reproduced as an observable fault on the audited
non-DEBUG GENERIC kernel.** The vulnerable sequence
`VFS_ROOT → vput(rvp) → vn_get_namelen(rvp)` (i.e. `VOP_PATHCONF` on the
already-unlocked/unref'd `rvp`) ran end-to-end **520/520 times** across test
runs with **no panic**, because (1) the `X86_64_GENERIC` kernel has no
`INVARIANTS`/`WITNESS` to catch the unlocked-`VOP_PATHCONF` lock-protocol
violation, and (2) the UFS mount-root vnode retains references through the
race window so `vnlru` does not reclaim it (the actual UAF-free never happens
on this vnode). The deterministic refcount-leak variant needs a filesystem
whose `VFS_VPTOFH` fails; UFS `ffs_vptofh` always returns 0, and none of the
export-capable fs types on this guest have a failing `vptofh`. The bug IS
genuine (master DEV unchanged since 2026-06-22) and the fix applies — see
`VERDICT.md` for the full line-by-line proof.

## The bug

`vfs_setpublicfs()` (`sys/kern/vfs_subr.c:2255-2295`):

```c
if ((error = VFS_ROOT(mp, &rvp)))      /* rvp referenced + locked        */
    return (error);
if ((error = VFS_VPTOFH(rvp, ...)))
    return (error);                    /* :2259  LEAK: no vput(rvp)      */
vput(rvp);                             /* :2261  drops ref AND lock      */
...
if (argp->ex_indexfile != NULL) {
    int namelen;
    error = vn_get_namelen(rvp, &namelen);  /* :2269  USE AFTER vput      */
```

`vn_get_namelen()` → `VOP_PATHCONF(rvp)` (`vfs_subr.c:2552`) runs on a vnode
that has already been `vput()` (unlocked, usecount decremented). If
`VFS_ROOT`'s was the only reference, `rvp` is reclaimable by `vnlru` between
`:2261` and `:2269` — use-after-free. At minimum it is a definite vnode
lock-protocol violation.

## Reachability

`mount(2)` (UPDATE mount of an already-mounted ufs fs with `fspec=NULL` and
`export.ex_flags = MNT_EXPUBLIC|MNT_EXPORTED`, `ex_indexfile` set) →
`ffs_mount` export path (`ffs_vfsops.c:270-273`) → `vfs_export` →
`vfs_setpublicfs`. Mount/export is privilege-gated (root only) — a genuine
kernel memory-safety defect (Low), not an unprivileged escalation.

## Build

```
./build.sh        # cc -o expub expub.c
```

## Run (as root)

```
./run.sh                 # defaults: /boot, 16 loops
# or: ./run.sh /boot 512
```

## Expected output

On a `DEBUG`/`INVARIANTS` kernel racing `vnlru`: a panic from `VOP_PATHCONF`
on a reclaimed/unlocked vnode. On the default GENERIC kernel (this guest): the
run completes with `vfs_setpublicfs UAF path reached ok=N` and **no panic**
— the lock-protocol violation races silently. The `VFS_VPTOFH`-error leak
variant is deterministic but needs a `vptofh`-failing export fs (not available
on this guest).

## Impact

Genuine CWE-416 (UAF) + CWE-401 (refcount leak) + vnode-lock-protocol
violation, gated behind mount/export privilege (root). No panic/leak/uid0
obtained on this non-DEBUG kernel; the finding's value is the real, reachable
code defect and the targeted fix in `fix.diff`.
