# DF-3203 — krpc_call reads rpc accept-status 8 bytes past the validated region

**File:** `sys/vfs/nfs/krpc_subr.c:356-373` (dead chain-level gate), `:391`
(pre-`m_pullup` `rp_status` read), `:417` (the pullup that comes too late)
**Severity:** Low · **CWE-125** · verified in VERIFY mode with the
verbatim-interposition kernel-compiled rig (`dfrig13_krpc.c`, sha256-gated
`#include` of the production `krpc_subr.c`; real UDP stack, real mbufs,
userland attacker `attacker.c`).

## What was reproduced (baseline kernel #1)

A 16-byte "accepted" RPC reply (xid + direction + astatus only) passes every
gate — `len < MIN_REPLY_HDR` at :370 is dead code because `sbinit()` zeroes
`sb_cc` and `soreceive` counts the **delivered** bytes into it, so
`len -= sio.sb_cc` at :366 yields the residual 65536−16, never < 16 for any
UDP datagram — and `reply->rp_status` (offset 20..23 of a 24-byte struct)
is then read at :391 **before** the `m_pullup(24)` at :417: 4 bytes of
stale kernel-heap content beyond the 16-byte datagram, which is

1. **printed to console/dmesg** by the verbatim `kprintf("rpc denied,
   status=%d\n")` at :397, and
2. **used for control flow**: `0` → `gotreply` → `m_pullup(24)` fails on the
   16-byte mbuf → spurious **ENOBUFS** abort (krpc_subr.c:417-419);
   `2` (RPC_PROGMISMATCH) → **EBADRPC leak exit** (the DF-3204 path);
   anything else → the console print and `continue`.

### Attribution (the value is heap memory, not wire data)

- **Userland-planted primer, 18/18 deterministic**: the attacker sprays
  24-byte datagrams to a dead port carrying pattern `P` at offset 20..23
  (their mbufs are freed with `P` in place), then sends the 16-byte short
  reply — the verbatim code reads and prints exactly `P`:
  pattern `0x5a5a5a5a` → `rpc denied, status=1515870810` **9/9**;
  pattern `0x3cc33cc3` → `rpc denied, status=1019428035` **9/9**
  (`run.attrib.log`, `primer_leak_sample.txt`). Bytes 16..23 do not exist
  in a 16-byte datagram: no on-wire content can produce these values.
- **Kernel-side groom hit**: uniform `0xc3` fill → `rpc denied,
  status=-1010580541` = exactly `(int)0xc3c3c3c3` (`run.2.log`).
- **Raw residue across runs**: `943337777`/`943337779` (= ASCII `"8:11"` /
  `"8:13"` timestamp fragments), `153789694`, `357452694` (`run.log`,
  `leak_sample.txt`, `canary_dump.txt`) — uninitialized heap that varies
  run to run.
- **Stale==0 branch**: multiple attempts abort with ENOBUFS when the slot
  previously held a successful reply (status 0 written at +20..23).
- **Stale==RPC_PROGMISMATCH coupling**: primer pattern `0x00000002` →
  3/3 calls return **EBADRPC** with exactly +3 leaked mbufs
  (`run.couple.log`) — DF-3203 steering execution into the DF-3204 leak
  exit on a value the attacker never put on the wire as a status.

No kernel panic occurs (the read stays inside the mbuf slab allocation),
hence no panic.txt; the disclosed artifact is `canary_dump.txt`.

## Fix validation (kernel #2)

With `fix.diff` (len = received `sb_cc`, require the full 24-byte header,
`m_pullup` before any field read): the short reply is dropped at the gate,
**zero** verbatim `rpc denied, status=` lines appear (strict grep `^rpc
denied` = 0 on the fixed boot; the only matches of the loose phrase are the
rig marker text), the call completes on the honest follow-up reply, and
honest replies are unaffected (6/6 in the DF-3205 suite).

## Reproduce (on the DragonFly guest)

```
sh build.sh                     # gate + build + install; reboot
sh attrib.sh                    # primer attribution (2 patterns x 3 rounds)
sh run.sh                       # kernel-groom canaries + groom32 coupling
# fix validation:
sh build.sh af834f6e30225b1af68cc83cbcdaf25e29e0e39ca6deaec6f3c13663a661b886 ; reboot
sh run_fixed.sh                 # (in DF-3205 pack) strict rpc-denied count stays 0
```

## Files in this folder

| file | purpose |
|---|---|
| `dfrig13_krpc.c` / `attacker.c` | harness + userland attacker (modes short, short_p) |
| `build.sh` / `run.sh` / `attrib.sh` | exact commands |
| `run.log` / `run.2.log` | baseline kernel-groom runs (raw residue, c3 hit, ENOBUFS branch) |
| `run.attrib.log` | primer attribution 18/18 |
| `run.couple.log` + `couple.log` | stale==2 → EBADRPC + leaked mbufs (3/3, +3 mbufs) |
| `run.fixed.log` | fixed-kernel A/B (0 verbatim prints) |
| `canary_dump.txt` / `leak_sample.txt` / `primer_leak_sample.txt` | all stale values observed |
| `short_*.log`, `short_p_*.log` | attacker-side logs |
| `build_summary.log` | build gate excerpts (full logs in DF-3205 pack) |
| `fix.diff` / `fix_all.diff` | validated fix (single / combined) |
| `env.txt`, `VERDICT.md`, `manifest.json`, `verdict.json` | context + verdicts |
