# DF-3087 — VERDICT: reproduced (deterministic kernel panic), fix validated

## Bottom line
`nfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD)` — the documented resume step of the
kerberos nfsd protocol — deterministically panics the DragonFly kernel
with a NULL-deref inside `sys_nfssvc`/`nfssvc_nfsd` at
`sys/vfs/nfs/nfs_syscalls.c:671`. Reproduced on the **stock INVARIANTS
kernel #0** from a fresh `vm.sh reset with-src`, first clean attempt;
fix validated by full kernel rebuild (panic gone, guest serves on).

## Reproduction narrative (stock kernel, 2026-09-06)
1. Fresh `vm.sh reset with-src` → DragonFly 6.5-DEVELOPMENT #0
   (Thu Jul 2 06:02:54 UTC 2026), X86_64_GENERIC (INVARIANTS), 6 vCPU.
2. `killall nfsd` — the guest image auto-starts stock nfsd(8) whose
   waiting workers steal every `nfsrv_wakenfsd` assignment; without
   this step the PoC's own nfsd thread never gets the socket (this was
   the only reason the first two attempts "hung" instead of crashing).
3. `/root/kerbd 20497`:
   - forks a sender that connects and writes one 80-byte record-marked
     AUTH_KERB/RPCAKN_FULLNAME NFSv3 GETATTR request, holding the
     connection open;
   - parent accepts, `nfssvc(NFSSVC_ADDSOCK)`, then
     `nfssvc(NFSSVC_NFSD, &nsd)` with pre-allocated auth/verf buffers;
   - `nfs_getreq()` takes the FULLNAME path (nfs_socket.c:2392-2434),
     fills `nfsd_authstr`/`nfsd_verfstr`, sets `NFSD_NEEDAUTH`;
   - `nfssvc_nfsd()` :609-623: the three copyouts succeed (buffers
     valid) → **returns ENEEDAUTH (errno 81)** with the request parked
     in `nfsd->nfsd_nd`, `NFSD_REQINPROG` set, `nfsd->nfsd_slp` held;
   - daemon prints the pointers and immediately calls
     `nfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD, &nsd)`;
   - `sys_nfssvc` :215-297 runs the AUTHIN credential insert (works,
     `nfsd->nfsd_slp` still valid, `nfsd_nd` still set), then
     `nfssvc_nfsd()` :443 re-enters with **local `nd = NULL`** (:448),
     takes the NFSD_REQINPROG else-branch (:549-570) which never
     reloads `nfsd->nfsd_nd`, falls past the `if (nd)` block (:599),
     and the reply do-loop evaluates `nd->nd_procnum == NFSPROC_WRITE`
     at **:671** with `writes_todo == 0` (init :457) →
     `movl 0x70(%rdi),%eax` with rdi=NULL.
4. Serial console:
   ```
   Fatal user address access from kernel mode from kerbd at ffffffff80807cc4
   Fatal trap 12: page fault while in kernel mode
   fault virtual address	= 0x70        <- offsetof(struct nfsrv_descript, nd_procnum)
   Stopped at      sys_nfssvc+0x3d4:       movl    0x70(%rdi),%eax
   current process = 872 (kerbd)
   ```
   Guest dead (`vm.sh status` → down). Identical fault on the
   instrumented build (#3) at sys_nfssvc+0x3e4, same VA 0x70.

## Why severity is Low
- `nfssvc(2)` requires `SYSCAP_RESTRICTEDROOT` (uid 0 outside jail) —
  the direct trigger is a privileged local user.
- Base-system `nfsd(8)` (sbin/nfsd/nfsd.c:826-841) passes a zeroed
  `nsd`: at :616-620 `copyout(..., nsd.nsd_authstr=NULL, ...)` fails →
  `cacherep = RC_DROPIT` — the request is dropped, ENEEDAUTH never
  escapes, no base-system crash path.
- Remote amplification exists only against a (nonexistent in base)
  daemon implementing the kerb AUTHIN protocol: a remote client sends
  one AUTH_KERB FULLNAME request and the daemon's protocol-mandated
  resume panics the host.
- Also two adjacent lifetime defects in the same dead code, fixed by
  the same patch: `nfsd->nfsd_nd` is never freed if the daemon abandons
  the ENEEDAUTH'd request (permanent leak of nfsrv_descript + request
  mbufs), and :760's `kfree(nd)` leaves `nfsd_nd` dangling until the
  next `nfsrv_dorec` overwrites it.

## Fix validation (full cycle)
- Baseline: stock #0 → panic (run.log, panic.txt).
- `vm.sh reset with-src`; `cd /usr/src && patch -p1 < fix.diff`
  (reload `nd = nfsd->nfsd_nd` in the :549 else-branch; clear
  `nfsd->nfsd_nd` at both nd-kfree sites); `make -j6 nativekernel
  KERNCONF=X86_64_GENERIC && make installkernel`; reboot into #1
  (Sun Sep 6 04:53:45 UTC 2026).
- Same PoC: ENEEDAUTH returns identically; AUTHIN resume **re-dispatches
  the parked request inside the kernel** (error reply for the
  fh-less GETATTR goes to the still-open connection) and the nfsd keeps
  serving until the 40 s timeout kills it — no panic, guest up
  (run.fixed.log).

## Impact summary
Class: kernel NULL-pointer read → fatal trap (local privileged DoS);
no write primitive, no escalation. CWE-476.
