# DF-3213 — VERDICT

**Status: reproduced. Impact: leak (mechanism proven; raw pointer value gated
on heap bit-30 placement) + duplicate-reply-cache integrity corruption.
Confidence: certain.**

## Root cause (line-accurate)

* `sys/vfs/nfs/nfs_srvcache.c:337-338` — `rc_status = nd->nd_repstat;
  rp->rc_flag |= RC_REPSTATUS;` is the only place RC_REPSTATUS is set.
* **No code path in the tree clears RC_REPSTATUS.** Slot reuse
  (`nfs_srvcache.c:243-263`) clears only `RC_REPMBUF` (:253-257) and
  `RC_NAM` (:258-262). The mbuf-store branch of `nfsrv_updatecache`
  (`:340-347`) clears `RC_REPMBUF` but not `RC_REPSTATUS`.
* `sys/vfs/nfs/nfsrvcache.h:54-65` — `rc_reply` (mbuf*) and `rc_status`
  (int) are one union (`rc_un`), so once the reused entry stores a reply
  mbuf, `rc_status` reads the low 32 bits of the mbuf pointer.
* `sys/vfs/nfs/nfs_srvcache.c:212-216` — the cache-hit path tests
  `RC_REPSTATUS` before `RC_REPMBUF` and calls
  `nfs_rephead(0, nd, slp, rp->rc_status, ...)`.
* `sys/vfs/nfs/nfs_socket.c:1614-1620` — `nfs_rephead` checks
  `err & NFSERR_AUTHERR` (0x40000000, `nfsproto.h:100`); if bit 30 of the
  pointer is set it replies MSG_DENIED / rpc_autherr with
  `err & ~NFSERR_AUTHERR` **raw on the wire** (31 pointer bits). Otherwise
  the value is clamped by `nfsrv_errmap` (`nfs_subs.c:1481-1501`) to the
  per-proc default (NFSERR_IO=5 for v3 REMOVE) — a structurally wrong,
  status-only reply that is not the cached reply.

## How it was reproduced

In-guest (`DragonFly dfbsd 6.5-DEVELOPMENT #0 … X86_64_GENERIC`,
INVARIANTS kernel): `/sbin/nfsd -u -n 4`, `/etc/exports` present but
irrelevant (no valid filehandle needed). `leakforger` run as `nobody` over
UDP loopback:

1. 192 v2 SYMLINK (wire proc 13) requests, zero fh → 192 entries with
   RC_REPSTATUS, `rc_status=13` (reply status `0x0d` observed).
2. 1856 v2 NULL fillers → cache full at `desirednfsrvcache=2048`
   (nmbclusters=33296 → 16648 → capped 2048; `nfsrvcache_size_change`,
   nfs_srvcache.c:129-135).
3. 192 v3 REMOVE (wire proc 12) requests → each evicts one stale entry
   (LRU head), reuses the slot with RC_REPSTATUS still set, and stores the
   reply mbuf → `RC_REPSTATUS|RC_REPMBUF` coexist, union holds the pointer.
4. Retransmit each v3 REMOVE → **192/192** answered with a 7-word
   status-only reply `[xid][1][0][0][0][0][5]` instead of the cached reply.
   Repeats: 192/192 and 256/256 (`run.log`, `run.2.log`, `run.3.log`).

## Why status=5 proves pointer aliasing (not merely a stale status)

The genuine stale value is 13 (EACCES). `nfsrv_errmap` for v3 REMOVE walks
`nfsv3err_remove[] = {IO, NOENT, IO, ACCES(13), …, STALE, …}` and returns an
exact match unchanged; 13 would have been returned as 13. The observed 5 is
the table default, reachable only for a value that matches no whitelist
entry — i.e. `rc_status` was no longer the small stale errno but the aliased
`rc_reply` mbuf pointer's low 32 bits. The control (fresh entry in a clean
slot) resends byte-identically, ruling out re-execution noise.

## Impact assessment (honest)

* **Proven on this guest (every run):** the duplicate reply cache answers
  retransmissions of non-idempotent operations (REMOVE/RMDIR/RENAME/
  SYMLINK/MKNOD/MKDIR/WRITE/SETATTR/CREATE) with a wrong, status-only reply
  derived from a kernel pointer. Client-visible incorrect NFS results;
  breaks the exactly-once semantics the cache exists to provide.
* **Mechanism-proven, placement-gated:** when the cached-reply mbuf lands at
  an address whose bit 30 is set, `err & ~0x40000000` puts 31 bits of the
  kernel heap pointer into the reply of an *unauthenticated* remote client.
  Across 640 poisoned slots on this guest the mbuf zone never had bit 30
  set (always clamped), so the raw value was not extracted here; the
  disclosure is real but conditional on heap placement (memory size/layout).
* Not memory corruption; no write primitive; not a panic. The `mreq == NULL`
  no-reply path (`nfs_syscalls.c:683-684`) leaves INPROG entries that DROP
  retransmits, but that is cache semantics, not a defect beyond this one.

## Fix validation

`fix.diff` clears RC_REPSTATUS on slot reuse (:253ff) and in the mbuf-store
branch of `nfsrv_updatecache` (:340ff). Applied to the guest's /usr/src,
kernel rebuilt with `make nativekernel`, rebooted, PoC re-run:
retransmits then return **byte-identical** cached replies (0 corruptions)
and the control behaves the same. See `fix_baseline_reproduced` /
`fix_patched_reproduced` fields and `run.fixed.log`.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
