# DF-2996 — nfs_sillyrename consumes nfs_lookitup() result without any validation → wild-pointer write / VDIR type confusion → deterministic kernel panic (malicious NFS server; unpriv local trigger)

## What this is
The NFS client's silly-rename machinery (`unlink()` of an open file) calls
`nfs_lookitup()` for the freshly renamed `.nfsXXX` entry and then executes

```c
	error = nfs_lookitup(dvp, sp->s_name, sp->s_namlen, sp->s_cred,
		cnp->cn_td, &np);
	np->n_sillyrename = sp;          /* sys/vfs/nfs/nfs_vnops.c:3036-3038 */
	return (0);
```

with **no error check, no initialization of `np`, and no verification that
the node returned is the renamed file**.  `nfs_lookitup()` leaves `*npp`
untouched on error, and returns whatever nfsnode the server's file handle
maps to.  Consequences:

1. **error path**: `np` is uninitialized caller stack → `np->n_sillyrename = sp`
   is a wild-pointer write of a heap pointer (`sp`).
2. **non-NULL garbage `np`**: `nfs_lookitup()` takes its `*npp != NULL`
   branch → overwrites the file handle of an arbitrary node
   (`bcopy(nfhp, np->n_fhp, fhlen)`) and applies the server's attributes to
   it — including a **type change** (`nfs_setvtype()`), observed turning a
   VREG node into VDIR.
3. **server returns a directory's fh**: `np->n_sillyrename` aliases the
   `n_cookies` LIST_HEAD on VDIR nodes (`union` in `struct nfsnode`,
   sys/vfs/nfs/nfsnode.h:131-132) → `nfs_reclaim()` walks and `kfree()`s
   `sp` (a `struct sillyrename` from M_NFSREQ) as an `nfsdmap`
   (M_NFSDIROFF) → wild pointer walk + wrong-zone frees.

Trigger: any unprivileged local user `unlink()`ing a file that is still
open, against a mounted NFS share whose server (malicious, compromised, or
spoofing on-path — AUTH_SYS/UDP has no integrity) replies to the final
LOOKUP with an error or a lying file handle.

## Build (in-guest, base system cc)
```
cd /root/df2996
cc -O -o fakesrv fakesrv.c
cc -O -o trigger trigger.c
cc -O -o lsdir   lsdir.c
```

## Run (baseline — stock INVARIANTS kernel)
```
cd /root/df2996
(./fakesrv dirfh > /tmp/fakesrv.log 2>&1 &)   # fake rpcbind+mountd+nfsd on 127.0.0.1
sleep 1
mount_nfs -o port=2049,udp 127.0.0.1:/P /mnt
mkdir /mnt/d
./trigger                                      # open + unlink-while-open
```

## Expected (baseline)
* `trigger` prints `fd=N open...`, `unlink returned 0`, and then the kernel
  panics before/during trigger exit:
```
Fatal user address access from kernel mode from trigger at ffffffff807f61b0
Fatal trap 12: page fault while in kernel mode
fault virtual address	= 0x1
instruction pointer	= 0x8:0xffffffff807f61b0
Stopped at      nfs_reclaim+0x150:      movq    (%rdi),%r12
```
  `nfs_reclaim+0x150` is exactly the directory-cookie free loop
  (`dp = dp->ndm_list.le_next` / `kfree(dp, M_NFSDIROFF)`,
  sys/vfs/nfs/nfs_node.c:466-473) — 3/3 deterministic reproductions.

* Alternate mode `./fakesrv err`: final LOOKUP replies NFSERR_NOENT.
  The wild write happens silently (unlink still returns 0); on this guest
  the uninitialized stack slot held a benign value, so no fault — evidence
  that the error path corrupts whatever the stale pointer points at.

## Expected (after fix.diff, rebuilt kernel)
* `./trigger` under `fakesrv dirfh` or `fakesrv err`: `unlink` fails with
  an RPC error (`EBADRPC`/EIO-ish), **no panic**, guest stays up, `/mnt`
  remains usable.  Verified in run.fixed.log.

## Files
* `fakesrv.c`  — malicious NFSv3 server (rpcbind :111, mountd :779, nfs :2049)
* `trigger.c`  — open + unlink-while-open + post-unlink stat probe
* `lsdir.c`    — bounded getdents reader (leak-demo helper)
* `run.baseline.log` / `panic.txt` — baseline reproduction
* `run.fixed.log` — same PoC on the fixed kernel
* `fix.diff`  — the verified fix (nfs_sillyrename validation + nfs_lookitup echo branch)
