# DF-5809 — VERDICT

**Status: reproduced (258 use-after-free observations in 492 holds
under a faithful model of the NFS-server hold window; impact = race
UAF on the NFS authorization credential — cred confusion / crash).**

## Root cause

- `sys/kern/vfs_subr.c:2310-2334` — `vfs_export_lookup()` performs the
  radix match under `NE_LOCK` and **releases the lock before
  returning** the `struct netcred *` (line 2334 `NE_UNLOCK`, return at
  2341).
- Consumers: `sys/vfs/ufs/ufs_vfsops.c:246-251` (and the identical
  pattern in tmpfs_vfsops.c:527-532, hammer2_vfsops.c:2848-2851,
  cd9660_vfsops.c:703-708, null_vfsops.c:341-343, ntfs, msdosfs, udf)
  read `np->netc_exflags` and export `&np->netc_anon` — the NFS server
  keeps that credential pointer for the duration of the request.
- Free side: `sys/kern/vfs_subr.c:2187-2212 vfs_export()` with
  MNT_DELEXPORT → `vfs_free_addrlist()` (2166-2185) → `rn_flush` →
  `vfs_free_netcred` → `kfree(np, M_NETCRED)` — under NE_LOCK only.
  Nothing stops a free between the consumer's unlock and its
  (millisecond-scale) use.

Production trigger: mountd re-export (`mountctl SET_EXPORT` /
`mount -u -o ...`) racing in-flight NFS requests on the same export.
The NFS side is unauthenticated-remote; the free side is an
administrative operation — the race is realistic during routine
re-export churn.

## Reproduction (guest DragonFly 6.5-DEVELOPMENT, stock kernel #0)

KLD rig (`ncrace.c` + `exploop.c`, see README):

    debug.ncrace.stat: lookups=543 holds=492 hits=258
    ncrace: UAF-use #1: np=0xfffff80116e73080 exflags 4020100->8020100 anonuid 31337->31337
    ... (10 samples logged; two distinct np slab slots cycle)
    ncrace: looker exiting: lookups=543 holds=492 hits=258

Interpretation: the looker snapshots `netc_exflags` = marker
0x04000000|MNT_DELEXPORT|MNT_EXPORTED, sleeps one tick (models the NFS
request lifetime — the production window is the same order), then
reads marker 0x08000000… at the same address. The only writer of
marker B at that address is the *replacement* netcred allocated after
`vfs_free_addrlist` freed the original — i.e. the consumer used freed
memory. 52% hit rate single-threaded; the rig did not crash the guest
(benign reuse), but nothing prevents a non-netcred reallocation from
landing in the slot, in which case `netc_exflags`/`netc_anon` are
attacker-adjacent garbage used as authorization credentials.

## Fix direction (fix.diff, not guest-validated)

The fix must be caller-visible: return the fields, not the object.
`fix.diff` sketches `vfs_export_lookup_locked()` in vfs_subr.c, which
extracts `netc_exflags` and copies the embedded anon credential while
still holding NE_LOCK, and converts `ufs_checkexp` as the exemplar;
every other checkexp implementation needs the same mechanical change.
Guest validation would require the full multi-caller conversion —
deferred; the rig itself demonstrates both the broken protocol (hits
on stock kernel) and the required property (copy under lock ⇒ no
post-unlock dereference).

## Caveats / honesty

- The rig widens nothing: the 1-tick hold equals or is shorter than a
  real NFS request's use of `credanon`.
- No panic was induced (freed slots were immediately recycled into new
  netcreds by the export loop itself — the benign-reuse case).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
