# DF-2993 — NFSv3 READDIR/READDIRPLUS with count=0 → infinite kernel loop (remote DoS)

## Build
```
cc -O -o /root/nfspoc nfspoc.c        # in-guest, base system cc
```

## Run (guest, NFS server exporting /tmp/nfsroot, nfsd -u -t -n 4)
```
FH=$(NFSPOC_SRCPORT=820 /root/nfspoc mount | tail -1)     # via mountd RPC
NFSPOC_SRCPORT=821 NFSPOC_TIMEOUT=5 /root/nfspoc readdir $FH 8192 1   # control: replies OK
NFSPOC_SRCPORT=821 NFSPOC_TIMEOUT=5 /root/nfspoc readdir $FH 0 1      # trigger: count=0
```

## Expected
* Control READDIR (count=8192): `reply OK` in <1ms.
* Trigger READDIR (count=0): **TIMEOUT — no reply ever**; the servicing nfsd
  kernel thread spins forever inside nfsrv_readdir holding slp->ns_token.
* Consequences observed on the stock INVARIANTS guest:
  * userland console writer starves immediately after the trigger (serial log
    goes silent mid-script),
  * sshd flaps (rc=124 "Connection timed out during banner exchange" / rc=255)
    and then stops answering entirely while `vm.sh status` still says `up`,
  * in the first run the guest became fully unresponsive (`vm.sh status` =>
    down; no panic text on serial console — hard livelock, not a crash).
* One UDP request pins one nfsd thread; N requests pin all N.

## Root cause chain
1. `nfsrv_readdir` sys/vfs/nfs/nfs_serv.c:2977-2984 — `cnt` and
   `siz = roundup2(cnt, DIRBLKSIZ)` are taken verbatim from the wire; count=0
   is NOT rejected. `fullsiz = siz = 0`, `rbuf = kmalloc(0)`.
2. `io.uio_resid = 0` ⇒ the EOF short-cut at nfs_serv.c:3066-3095
   (`if (io.uio_resid) { siz -= ...; if (siz == 0) { reply-eof; } }`) is
   **skipped** — the eof reply is only reachable when resid > 0.
3. UFS `ufs_readdir` (sys/vfs/ufs/ufs_vnops.c:1612-1636,1647-1704) with
   resid 0: `vop_write_dirent` (sys/kern/vfs_subr.c:2566-2568) returns 1
   ("doesn't fit") **without setting \*error**; cookie_index stays 0 but
   `*a_cookies` stays non-NULL (kmalloc'd for ncookies=1), uio_offset is
   unchanged, and ufs_readdir returns 0 ("success", eofflag=0).
4. Back in nfsrv_readdir: cookies non-NULL (no NFSERR_PERM), error 0,
   `cpos >= cend` (siz==0) ⇒ nfs_serv.c:3120-3124 `toff = off; siz = fullsiz;
   goto again;` — identical state, identical result: **infinite loop**.
5. Same construction in `nfsrv_readdirplus` (dircount field = 0):
   nfs_serv.c:3277-3286, 3356-3382, 3407-3411.

## Notes
* The nfsd thread spins while holding `slp->ns_token`, so the whole NFS socket
  service dies with it; the unbounded kernel-mode spin starves userland.
* Reachable by any client the export allows (AUTH_SYS uid is irrelevant —
  READDIR only needs VEXEC via nfsrv_access). With `vfs.nfs.nfs_privport=1`
  (guest default) the source port must be < 1024 — trivial for a remote
  attacker (root on their own host).
