# VERDICT — DF-4055 (et_rxeof trusts NIC-DMA'd rxstat length → heap OOB read)

**Status: reproduced** (production-handler KLD dispatch, DF-4106 vge /
DF-4266 wb evidence class) — **and fix validated** on a rebuilt kernel.

## What ran

The guest has no ET131x NIC, but `device et` is built into
X86_64_GENERIC, so the driver's real static functions live in the
running kernel image. `et_rxeof` exists only inlined into `et_intr`
(nm has no separate symbol; disassembly of `et_intr+0x0..+0x570`
contains the STATRING shift-16 extraction and the 0x3ff/0x400 ring
bound comparisons), so the RX path was driven through the **real
`et_intr()`** — the production interrupt entry — with:

- a fabricated post-attach softc in module .bss: real busdma
  rings/tags/maps built with the verbatim `et_dma_alloc()`/
  `et_dma_mbuf_create()` call sequence, rings armed by the kernel's
  own `et_newbuf_hdr`/`et_newbuf_cluster` (production
  `et_init_rx_ring:1485` loop), a zeroed-mii fake miibus, a real lwkt
  serializer held across the dispatch exactly as the interrupt context
  holds it, per-CPU ifdata, real ifaltq subqueues;
- the "NIC" simulated the only way the driver observes it: an RX stat
  ring write-back (`LEN=60000 BUFIDX=0 RINGIDX=1`) plus a 64-byte frame
  DMA'd into the armed ring-1 cluster, and `ET_INTR_STATUS =
  ET_INTR_RXEOF` in the MMIO stand-in.

Struct-layout agreement printed and cross-checked (env.txt).

## Baseline (stock kernel #0, Jul 2 2026) — the bug

`et_rxeof` extracts the 16-bit `ET_RXST_INFO2_LEN` (if_et.c:1863),
checks only `buflen < ETHER_CRC_LEN` (:1892), then publishes
`m->m_pkthdr.len = m->m_len = buflen` (:1896) and hands the mbuf to
`if_input` (:1902). With the device claiming 60000 for a ring-1 buffer
(one MCLBYTES = 2048 cluster):

```
dfet: if_input: m=0xfffff80089786400 m_len=59996 pkthdr.len=59996
      M_EXT=1 ext_size=2048 (backing-store capacity) rcvif=dfet0
dfet:   OOB: m_len exceeds backing store by 57948 bytes; heap bytes
      [2048..2080) past the cluster: <kernel heap>
```

- 3/3 iterations over-length (df_oob=3), guest kept alive by the
  sink's bounded read + deliberate leak.
- **Leak samples differ per run and are kernel-heap content**:
  run A ASCII text `"ation an\nd# PasswordAuthenticati"`; run B the
  previous slot's crafted frame (cross-slot bleed); run C pointer
  soup `0000010052540012 345652550a000202 0800400000000000`
  (leak_sample.txt). In production this window is BPF/ether_input's.
- **Stock-stack panic corroboration**: freeing the delivered mbuf —
  exactly what the real stack does — panics the kernel:
  `panic: overflowed mbuf` at `m_free` (KASSERT M_TRAILINGSPACE >= 0,
  sys/kern/uipc_mbuf.c:1317), backtrace `m_free ← m_freem ←
  et_intr+0x4bf` (panic.txt; first rig run before the sink switched
  to leak-instead-of-free). The kernel's own invariant check confirms
  m_len exceeded the backing store.

## Fix validation (kernel #1, Oct 14 2026, same tree + fix.diff)

fix.diff adds a per-ring capacity check (MHLEN ring 0 / MCLBYTES
ring 1 / ET_JUMBO_FRAMELEN jumbo); a device length beyond the backing
store drops the frame with `ierrors++`. `make -j6 nativekernel` +
installkernel (if_et.c recompiled at build-log line 18438, BUILD_OK),
reboot, identical rig (addresses re-resolved; et_intr moved
0xffffffff804b8bd0 → 0xffffffff804b8c70):

| scenario | baseline (#0) | fixed (#1) |
|---|---|---|
| LEN=60000 claim ×3 | 3 mbufs delivered with m_len=59996 on 2048-B cluster; heap bytes read past the cluster; panic at m_freem | delivered=0, oob_events=0, ierrors=3 — dropped 3/3, no OOB mbuf, no panic |

fix_status: **fixed** (baseline_reproduced=1, patched_reproduced=0).

## Impact

Kernel heap disclosure (up to ~63KB per frame past a 2048-byte
cluster) into the if_input/BPF window, plus a deterministic panic once
the over-length mbuf is freed on stock configs. Attacker model:
faulty/hostile NIC or PCIe DMA tampering — hardware-mediated, so
severity Medium stands (as filed).

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