# DF-4045 VERDICT — stge_rxeof device-fed length trust (source-certain, runtime hardware-gated)

## Reproduced?

Not on hardware — the guest has no stge(4) device (`env.txt`: `vtnet0 lo0`,
`NO_STGE_HW`, DragonFly 6.5-DEVELOPMENT). Per the established family
convention (DF-3989/DF-4024) this is recorded as **inconclusive at runtime,
certain at source level**. The geometry model (`trigger.c`) builds and runs
on the guest with RC=0 (`run.log`) and prints the exact extents below.

## Root cause (path:line)

`stge_rxeof` applies the NIC-DMA'd frame length with only a per-interface
bound, never a per-buffer bound:

- `sys/dev/netif/stge/if_stge.c:1606-1607` — on a FrameEnd descriptor:
  `mp->m_len = RFD_RxDMAFrameLen(status) - sc->sc_cdata.stge_rxlen;`
- `if_stge.c:1608` — `sc->sc_cdata.stge_rxlen += mp->m_len;`
- `if_stge.c:1623-1629` — the ONLY check:
  `m->m_pkthdr.len = stge_rxlen; if (m->m_pkthdr.len > sc->sc_if_framesize) drop;`
- `RFD_RxDMAFrameLen(x) = x & 0xffff` — raw 16-bit device-fed field,
  `sys/dev/netif/stge/if_stgereg.h:167`.
- `sc_if_framesize = if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN`
  (`if_stge.c:2013`), and `SIOCSIFMTU` accepts up to `STGE_JUMBO_MTU = 9004`
  (`if_stge.c:1261`, `if_stgevar.h:55-57`) → framesize up to 9022.
- Every RX buffer is one MCLBYTES (2048) cluster: `stge_newbuf` uses
  `m_getcl` and sets `m_len = MCLBYTES` (`if_stge.c:2326-2329`); 2046 after
  the `ETHER_ALIGN` `m_adj` when framesize ≤ 2046 (`:2335-2336`).

Intermediate (non-FrameEnd) descriptors contribute their FULL buffer length
(2046/2048) to `stge_rxlen`; the frame's total length appears only in the
FrameEnd descriptor's 16-bit field.

## Consequence geometry (model output, run.log)

**Case A — jumbo MTU, over-long last buffer (heap OOB read):**
`framelen = framesize = 9022` with k intermediate buffers:
`last m_len = 9022 − 2048·k`, i.e. up to **9022 on a 2048-byte cluster →
6974 bytes read past the cluster** (k=1: 4926; k=2: 2878; k=3: 830). The
only driver check passes (`9022 ≤ framesize`). Every consumer trusting
`m_len`/`pkthdr.len` — BPF tap, `m_copydata` pulls, protocol header reads
past 2 KB — reads **adjacent kernel heap into a delivered network frame**:
a kernel-heap info leak readable by any local user with a socket on the
interface. Needs jumbo MTU (a documented, `SIOCSIFMTU`-legal config).

**Case B — any MTU (incl. default 1500), negative tail m_len:**
k intermediates then FrameEnd with `framelen < rxlen` (e.g. k=1,
framelen=100): `mp->m_len = 100 − 2046 = −1946`; `pkthdr.len = 100` passes
the check; the first mbuf is full-sized so `ether_input`'s
`m_len < ETHER_HDR_LEN` guard (`sys/net/if_ethersubr.c`) does not drop it.
`m_copydata` (`sys/kern/uipc_mbuf.c:1671,1689`) computes
`unsigned count = min(m->m_len - off, len)` on the negative tail → count
≈ 4 GB → `bcopy` overflow → **panic / memory corruption** as soon as a
consumer walks the chain (BPF attached; the KASSERTs at uipc_mbuf.c:1680+
are compiled out of GENERIC).

## Threat model

Device-fed bug: requires a faulty or malicious TC9021/IP1002A (compromised
NIC firmware, malicious PCIe/Thunderbolt NIC, or a second kernel bug that
corrupts the coherent RX ring). Inbound-only: read primitive (Case A) /
DoS-grade UB (Case B); no write primitive. Same threat framing as the rest
of the DF-3604 family.

## Fix validation

`fix.diff` bounds the FrameEnd `m_len` to `[0, MCLBYTES]` and drops the
frame using the driver's own error-drop idiom (free chain + reset +
`continue`, mirroring the existing iqdrops path at `if_stge.c:1595-1602`).
`git apply --check` passes against the read-only tree; runtime validation
would require stge(4) hardware (not present on the guest) — `fix_status:
not_testable`.

## Negative space checked this pass on the same surface

- txeof producer/consumer arithmetic sound (`if_stge.c:1455-1495`):
  `tx_cnt == len(busyq)` invariant, HIWAT 248 < RING_CNT 256 so `tx_prod`
  can never lap `tx_cons`, init marks all TFDs done with an empty busyq
  (`:2280`) and the `tx_cnt <= 0` guard precedes every control read.
- `stge_encap` freeq non-empty guaranteed by the HIWAT gate (`:1186`);
  error path frees the mbuf before any queue mutation (`:1114-1118`).
- dma_alloc failure paths pair with dma_free (`:849-1036`).
- Residual (not filed): `stge_tx_error`'s register-drain loop (`:1356-1381`)
  is bounded only by hardware clearing TS_TxComplete — device-gated DoS,
  weaker than the DF-3761 unbounded-walk shape (no counter bound applies to
  a status register).

## 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.
