# DF-5831 — nlookup() retry path NULL-derefs `nd->nl_basench` for `nlookup_init_raw()` nd's → remote NFS-server kernel panic

## What

`nlookup()`'s internal retry path (added for autofs mounts / NFS ESTALE /
namecache generation changes) resets the lookup to the base directory:

```c
	sys/kern/vfs_nlookup.c:1367-1385
	if (doretry && !inretry) {
		...
		cache_drop(&nd->nl_nch);
		cache_copy(nd->nl_basench, &nd->nl_nch);   /* :1380 */
		...
		goto nlookup_start;
	}
```

`nl_basench` is only ever initialized by `nlookup_init()`
(vfs_nlookup.c:149/159/170) and `nlookup_init_at()` (:225/228/239).
`nlookup_init_raw()` (:255-291) and `nlookup_init_root()` (:297-335) leave
it NULL (the whole `nd` is `bzero()`'d).

Any `doretry` during an nlookup on an init_raw/_root-initialized `nd`
therefore executes `cache_copy(NULL, &nd->nl_nch)`:

```c
	sys/kern/vfs_cache.c:996-1000
	void cache_copy(struct nchandle *nch, struct nchandle *target)
	{
		...
		ncp = nch->ncp;		/* NULL deref, ncp is at offset 0 */
```

→ `Fatal trap 12: page fault on read at 0x0` in `cache_copy+0x11`.

## Reachability

`nlookup_init_raw()` is called from:

* `nfs_namei()` (sys/vfs/nfs/nfs_subs.c:1155) — the NFS **server's** name
  resolution for every RPC carrying a (dir filehandle, name) pair:
  LOOKUP, CREATE, REMOVE, RENAME (both sides), MKDIR, LINK, SYMLINK,
  SETATTR/GETATTR-by-name, etc.
* `nfsrv3_public` (sys/vfs/nfs/nfs_serv.c:568) — the WebNFS public
  filehandle index lookup.
* `vfs_mountroot_devfs()` (sys/kern/vfs_conf.c:238) — boot only.

`nlookup_init_root()` — `vfs_synth` only (boot).

The `doretry` triggers, all raceable by an NFS client against concurrent
modification of the exported tree (a second client, a second thread of the
same client, or a local process):

* generation change of the start directory or any component, detected at
  vfs_nlookup.c:1148-1158 / :1313-1323 — e.g. a concurrent
  `RENAME sub -> sub2` of the directory being searched bumps its
  `nc_generation` (`_cache_unlink_parent` + `_cache_link_parent` both
  `_cache_ncp_gen_enter/exit`, vfs_cache.c:783/840, 2155);
* parent destroyed (`NCF_DESTROYED`) during the leaf lookup,
  vfs_nlookup.c:1229-1232 — rename-over of the directory;
* `ESTALE` from `cache_resolve` (vfs_nlookup.c:802-806, :882-886);
* the trailing-`.`/`..` `EINVAL` retry (vfs_nlookup.c:848-852).

Impact: **kernel panic in the nfsd process** — remote denial of service of
the NFS server by any client permitted by the exports (no authentication
beyond the export ACL); also reachable by ordinary concurrent NFS workloads
(lookups racing renames of the containing directory) without hostile
intent. Not enabled by default (requires the NFS server to be running),
hence High rather than Critical.

## Files

* `hammer.c` — client-side LOOKUP hammer: `stat("/mnt/nfs/sub/zzzNNN")`
  loop → server-side `nfsrv_lookup` → `nfs_namei` → `nlookup` with
  `nl_basench == NULL`.
* `renamer.c` — renames `sub <-> sub2` (locally on the export and over
  the NFS mount) to churn `sub`'s `nc_generation`.
* `run.sh` — sets up the loopback NFS server + client, runs the rig.
* `panic.txt` — captured panic (see below).
* `fix.diff` — verified fix.
* `build.log`, `run.log`, `run.2.log` — fix-validation kernel build and
  the two post-fix survival runs.

## Reproduce

In the guest (as root):

```sh
sh /root/poc/DF-5831/run.sh
```

Expected on a vulnerable kernel (observed ≤ 2 minutes into the run):

```
kern_rename: retry due to race on: "sub" -> "sub2" (16,0)
Fatal user address access from kernel mode from nfsd at ffffffff806dac71
Fatal trap 12: page fault while in kernel mode
fault virtual address	= 0x0
...
Stopped at      cache_copy+0x11:        movq    (%rdi),%r13
```

`current process` is the nfsd; the fault is a read of `NULL->ncp`
(`ncp` is the first member of `struct nchandle`), i.e. exactly
`cache_copy(nd->nl_basench, ...)` with `nd->nl_basench == NULL`.
