# DF-0767 — PoC and fix evidence

**`nfs_lookitup` returns an uninitialized `nfsnode` pointer when the server
echoes the parent filehandle; `nfs_create`/`nfs_mkdir`/`nfs_mknodrpc`/
`nfs_symlink` then dereference the wild pointer.**

Status: **REPRODUCED (panic / local unprivileged DoS).** Fix authored,
built, and **validated** — see `VERDICT.md` and `fix.diff`.

## What is here

| file             | what                                                  |
|------------------|-------------------------------------------------------|
| `nfs_mal_server.c` | malicious NFSv3 server stub (the trigger)           |
| `trigger.sh`     | the unprivileged `mkdir` that fires the bug           |
| `build.sh`/`run.sh` | exact build/run commands                           |
| `fix.diff`       | the validated one-hunk fix (returns `EEXIST` in the CMPFH branch) |
| `VERDICT.md`     | the full writeup (mechanism, escalation analysis, fix before/after) |
| `panic.txt`      | the `fatal trap 9` panic signature (unpatched)        |
| `run.log` / `fix_run.log` | decisive unpatched (panic) and patched (EEXIST) runs |
| `fix_build.log`  | full single-fix kernel build log                      |
| `manifest.json`  | machine-readable artifact catalog                     |

## How to reproduce

```sh
# 1. build the malicious server (any user with cc)
sh build.sh                      # -> ./nfs_mal_server

# 2. admin pre-condition (root): start the malicious server and mount it.
#    This mirrors "an admin mounted an NFS share" — the realistic trigger
#    environment.
./nfs_mal_server &               # listens on 127.0.0.1:111 and :2049
mount_nfs -3 -T -o tcp,nfsv3 127.0.0.1:/export /mnt

# 3. unprivileged trigger (any non-root user).  On the UNPATCHED kernel
#    this panic's the guest immediately:
mkdir /mnt/df0767_pwn            # unpatched: panic; patched: "File exists"
```

## Expected result

- **Unpatched audit-source kernel (`6.5-DEVELOPMENT #0`):** kernel panic,
  `Stopped at nfs_mkdir+0x328: cmpl $0x2,0xe8(%rdi)` (`fatal trap 9`
  general-protection-fault). Guest is down (DDB). Serial-console capture in
  `panic.txt`.
- **Single-fix kernel (Fix B, `6.5-DEVELOPMENT #1`):** `mkdir` returns
  `File exists` (`EEXIST`); guest stays up. No panic. Reproducible over
  fresh mount/remount cycles.

## The bug in one paragraph

`nfs_lookitup()` (sys/vfs/nfs/nfs_vnops.c:3056) declares
`struct nfsnode *np` uninitialized (line 3061). When the LOOKUP reply's
filehandle equals the parent directory's filehandle, the `NFS_CMPFH` branch
(line 3087) runs `vref(dvp); newvp = dvp;` but never assigns `np`; the
epilogue (line 3127) then stores the still-garbage `np` into the caller's
out-pointer. The create callers do `newvp = NFSTOV(np)` (lines 2354, 1776,
1646, 2262) and dereference the wild pointer. `nfs_lookup()` — the
reference — avoids this by pre-setting `np = VTONFS(dvp)` at line 1145;
`nfs_lookitup()` is the lone caller that forgot.

## Threat model

A malicious, compromised, or MITM'd NFSv3 server (AUTH_SYS is cleartext and
trivially spoofable) replies to a create's follow-up LOOKUP with the parent
filehandle. Any local user who then issues `mkdir`/`creat`/`mknod`/
`symlink` on the mounted share panic's the kernel. Pre-condition: an admin
has mounted the (malicious) share — ordinary for NFS-using deployments.

## Fix

See `fix.diff`. The branch is only reachable when `*npp == NULL` (the
update-existing case is handled by the preceding `if (*npp)`), i.e. only the
create-style callers; treat the echoed filehandle as a name collision and
return `EEXIST`. This both closes the uninitialized-use and avoids the
vnode-lifecycle corruption that the finding's naive `np = dnp` proposal
causes (double-unlock panic in `kern_mkdir`/`vop_compat_nmkdir` — verified
by building and booting it).
