# DF-5848 — VERDICT

**Status: REPRODUCED** (deterministic kernel panic, seconds, kernel
nfsd on loopback TCP; original capture on stock #0 from the DF-3072
run, fresh capture this run). **Impact: remote/local DoS (panic) of
INVARIANTS kernels via nfsd; no memory corruption, no escalation.**

## 1. Reproduction

- Original (stock kernel `#0: Thu Jul 2 06:02:54 UTC 2026`, from the
  DF-3072 verification batch): pipelined kernel-NFS-client GETATTR/fstat
  load over a `mount -t nfs -o tcp 127.0.0.1:/export` loopback mount
  panicked after ~10 min — `incidental_nfsd_panic.txt`:
  `panic: assertion "m->m_type == MT_DATA" failed in m_dup_data at
  /usr/src/sys/kern/uipc_mbuf.c:1806`, stack `m_dup_data ←
  nfs_realign.isra.3 ← nfsrv_rcv ← sys_nfssvc ← syscall2`.
- This run (fresh, deterministic): `df5848_pipe.c` — a raw SunRPC/TCP
  client to 127.0.0.1:2049 (no mount, no valid RPC, no auth) streaming
  TCP_NODELAY LASTFRAG records of 142-149 bytes plus 16-record
  pipelined bursts. Panic in **under 90 s** (`panic-run1.txt`), stack
  identical. Booted kernel at capture time was the DF-5847 diagnostic
  build (#1 Oct 26) — its only delta vs stock is nd6.c (inert for the
  mbuf/nfs path; md5-verified equal for uipc_mbuf.c and nfs_socket.c:
  179f41cf…/84b1b11d… are the FIXED copies, stock equals the tree at
  0c8009550937a3fd4597a1b9225543ef/84b1b11d8894dff5f44b90337aa707ff —
  nfs_socket.c untouched, uipc_mbuf.c stock at capture).

## 2. Root cause (line-accurate; each step verified in source and the
   final step empirically)

1. `sys/netinet/tcp_output.c:896-922` — every segment is allocated
   `MT_HEADER` (`m_gethdr`/`m_getl(..., MT_HEADER, M_PKTHDR, ...)` at
   :898/:900/:942); if `len <= MHLEN - hdrlen - max_linkhdr` (:909) the
   payload is copied INTO that mbuf — one MT_HEADER mbuf holds hdr+data.
2. `sys/net/if_loop.c:202+` (`if_simloop`) hands the SAME mbuf to
   netisr; `tcp_input` strips IP/TCP headers via `m_adj` — an MT_HEADER
   mbuf carrying pure data lands in the receiving socket buffer.
   `soreceive` explicitly accepts `MT_DATA || MT_HEADER` as stream data
   (`sys/kern/uipc_socket.c:1473`, `:1779`) and returns the original
   mbufs to the `sio` caller (`:1842-1845`).
3. `sys/vfs/nfs/nfs_socket.c:2614` (`nfsrv_rcv` soreceive into sio) →
   `nfsrv_getstream` → on completed record `nfs_realign(&ns_frag,...)`
   at `:2816`. `nfs_realign` (:2256-2260) breaks on the first mbuf with
   `m_len & 3` or misaligned `m_data` — guaranteed by sending records
   whose size % 4 != 0 (or by pipelined records split mid-mbuf by
   `m_copym` at :2769) — then `m_dup_data(m, M_WAITOK)` at :2267.
4. `sys/kern/uipc_mbuf.c:1806`: `KKASSERT(m->m_type == MT_DATA)` fails
   on the loopback MT_HEADER data mbuf → panic.

Why the original capture needed ~10 min of NFS-client luck while this
trigger is deterministic: GETATTR requests are 4-byte-aligned XDR, so
misalignment only arose from incidental record-mark straddling; sending
deliberately misaligned record sizes hits the path immediately.

## 3. Fix and A/B validation

`fix.diff` relaxes the invariant to `MT_DATA || MT_HEADER` in
`m_dup_data` — matching `soreceive`'s own contract for stream data. The
copy loop reads only the data region and always produces MT_DATA
mbufs, so the output contract ("non-packet data chain for
nfs_realign") is unchanged; MT_SONAME/MT_CONTROL mbufs remain rejected.

- Validation kernel: tri-fixed build (`#1 Mon Oct 26`, INVARIANTS,
  -Werror, RC=0): uipc_mbuf.c fix + DF-5846 + DF-5847 fixes (disjoint
  subsystems).
- A/B: identical `df5848_pipe` load (90 s+ budget, same nfsd setup) on
  the fixed kernel — no panic, nfsd up, connection serviced
  (run-fixed.log). Repeated for a longer 300 s run alongside the other
  two triggers' budgets.

## 4. Impact ceiling / privesc assessment

The assertion is a *guard*, not corruption: tripping it panics an
INVARIANTS kernel (all this project's kernels) — DoS. Without
INVARIANTS the copy itself would be harmless (it ignores m_type). There
is no write primitive, no info leak, no lifetime issue — **no path to
uid=0**. Reachability honestly stated: any local user can hit a
loopback-serving nfsd (localhost mounts, common in testing/jails); a
network-facing nfsd is hittable by any remote host that can reach
2049. Filed Low — consistent with a panic-only, service-load-gated
trigger.
