# DF-0768 — PoC and fix evidence

**`nfs_readdirplusrpc_uio` signed-overflow in `nfsm_rndup(i)` corrupts the
READDIRPLUS reply XDR cursor → wild-pointer read → kernel panic (local DoS).**

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 `ls -f /mnt` (getdents) that fires the bug |
| `build.sh`/`run.sh` | exact build/run commands                           |
| `fix.diff`       | the validated one-hunk fix (bound `i` to `NFSX_V3FHMAX` before `nfsm_rndup`) |
| `VERDICT.md`     | full writeup (mechanism, escalation analysis, fix before/after) |
| `panic.txt`      | the `fatal trap 12` panic signature (unpatched)       |
| `run.log` / `fix_run.log` | decisive unpatched (panic) and patched (RC=0, alive) runs |
| `fix_build.log`  | single-fix kernel build log tail (rc=0)               |
| `env.txt`        | guest uname, cc version, sysctls                       |
| `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 WITH
#    -o rdirplus (the standard NFSv3 perf option that selects READDIRPLUS).
#    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,rdirplus 127.0.0.1:/export /mnt

# 3. unprivileged trigger (any non-root user).  On the UNPATCHED kernel
#    this panics the guest immediately:
ls -f /mnt                       # unpatched: panic; patched: empty dir, RC=0
```

The full harness (`run.sh`) does all three steps; run it as root:
`sh /abs/path/to/run.sh maxx`.

## Expected result

- **Unpatched audit-source kernel (`6.5-DEVELOPMENT #0`):** kernel panic,
  `Fatal trap 12: page fault`, `Stopped at memmove+0x24f: repe movsq
  (%rsi),%es:(%rdi)` (the `bcopy` in `nfsm_disct`'s pull-up branch reading
  from the corrupted `dpos`, ~2 GiB below the real mbuf data). Guest is down
  (DDB). Serial-console capture in `panic.txt`. (3/3 runs panicked.)
- **Single-fix kernel (`6.5-DEVELOPMENT #1`, sha256 `458d4b05…`):** `ls`
  returns `RC=0` (empty dir — the malicious entry's out-of-range `i` is
  rejected with `EBADRPC`); guest stays up; no panic. Reproducible over 4
  consecutive triggers.

## The bug in one paragraph

In `nfs_readdirplusrpc_uio()` (`sys/vfs/nfs/nfs_vnops.c:2924-2928`), the
"else" branch taken when a READDIRPLUS entry's `name_attributes` has
`attributes_follow == 0` reads the next server-controlled u32 as a handle
length `i` and calls `nfsm_adv(&info, nfsm_rndup(i))`. `nfsm_rndup(a) =
((a)+3) & ~3` overflows signed int for `a >= 0x7FFFFFFD`, producing `INT_MIN`.
`nfsm_adv()` then treats the negative length as "fits in the current cluster"
(`n >= INT_MIN` is always true) and does `info->dpos += INT_MIN`, corrupting
the XDR cursor into a wild kernel pointer ~2 GiB below the real data. The
next `nfsm_dissect()` routes to `nfsm_disct()`; on a multi-cluster reply its
pull-up branch runs `bcopy(corrupted_dpos, fresh_mbuf, (size_t)negative ~= 2^64)`
and page-faults on the wild source → panic. A malicious/compromised NFSv3
server (AUTH_SYS is cleartext) crafts the reply; any unprivileged local user
who reads the mounted share triggers it.

## Threat model

A malicious, compromised, or MITM'd NFSv3 server replies to a READDIRPLUS
request with an entry whose `name_attributes.attributes_follow == 0` is
followed by `0x7FFFFFFD`. Any local user who then issues `getdents`
(`ls`) on the mounted share panics the kernel. Pre-condition: an admin has
mounted the (malicious) share with `-o rdirplus` — ordinary for NFS-using
deployments (rdirplus is the default NFSv3 readdir mode in the diskless-root
path and a common perf mount option). Impact: local DoS / kernel panic. No
write primitive → no `uid=0` escalation.

## Fix

See `fix.diff`. Bound the server-controlled handle length `i` to
`NFSX_V3FHMAX` (= 64) before `nfsm_rndup(i)`, mirroring the bounds check
`nfsm_getfh` already applies. Matches the finding's primary recommendation.
