# DF-3808 — msk_txeof() trusts the device-supplied TX done index without ring bounds

## Verdict

**Source-certain structural finding; not PoC-verified (status: untested).**
Severity Low, confidence likely. The missing validation is provable from
the source; exploitation requires the NIC (or errant DMA) to misreport the
index, so the guest (no Marvell hardware) cannot exercise the path. The
same device-trust class as DF-3604 (bce(4)), filed Low here because the
walk stays memory-safe and the impact is premature completion rather than
out-of-bounds access.

## Root cause (path:line)

`msk_txeof()` (sys/dev/netif/msk/if_msk.c:2884-2927):

```c
2898:	cons = sc_if->msk_cdata.msk_tx_cons;
2900:	for (; cons != idx; MSK_INC(cons, MSK_TX_RING_CNT)) {
2901:		if (sc_if->msk_cdata.msk_tx_cnt <= 0)
2902:			break;
...
2906:		sc_if->msk_cdata.msk_tx_cnt--;
...
2910:		bus_dmamap_unload(...);
2915:		m_freem(txd->tx_m);
2916:		txd->tx_m = NULL;
2917:	}
```

`idx` is device-supplied at both call sites:

- `mskc_handle_events()` OP_TXINDEXLE: `status & STLE_TXA1_MSKL`
  (if_msk.c:3208-3209) — `STLE_TXA1_MSKL = 0x00000fff`
  (if_mskreg.h:2198) → **0..4095**, and for port B
  `((status & 0xff000000) >> 24) | ((len & 0xf) << 8)` → 0..4095
  (if_msk.c:3211-3216), while the ring holds `MSK_TX_RING_CNT = 256`
  entries (if_mskreg.h:2309).
- `msk_watchdog()`: `idx = CSR_READ_2(sc_if->msk_softc, STAT_TXA1_RIDX)`
  (if_msk.c:2671-2674) — a 16-bit hardware register.

There is **no range check** against `MSK_TX_RING_CNT`. For an `idx >= 256`
(or an `idx` behind `cons`), `cons` (wrapped mod 256) can never equal
`idx`; the only terminator is the `msk_tx_cnt <= 0` break. Consequences:

1. The walk drains **all** in-flight descriptors (≤ `msk_tx_cnt` ≤ 256
   iterations — bounded, in-bounds array access via `MSK_INC`'s modulo, so
   no OOB), calling `bus_dmamap_unload()` and `m_freem()` on mbufs whose
   TX LEs still carry `HW_OWNER` — the NIC may still be DMA-reading those
   clusters for transmission → **transmission of stale/freed kernel
   memory onto the wire** and dmamap state desync.
2. After the forced drain, `msk_tx_cons` is advanced to `msk_tx_prod`
   while the NIC's prefetch unit still walks the old LEs; subsequent
   `msk_encap()` calls overwrite hardware-owned descriptors.

The walk itself stays memory-safe (every visited descriptor is a live,
in-flight one, and every EOP descriptor in that window has a non-NULL
`tx_m` set by `msk_encap` at :2575-2579; `m_freem(NULL)` is also guarded,
sys/kern/uipc_mbuf.c). Hence Low, not Medium.

## Threat model

Requires a faulty or compromised Yukon II (or PCIe-level DMA corruption)
to write a bogus TX-index into the status LE / STAT_TXAx_RIDX register —
not reachable from packet contents alone. Same trust boundary as
DF-3604/DF-3807; defense-in-depth hardening.

## Fix

`fix.diff`: range-check `idx` at the top of `msk_txeof()` (covers both
call sites) and ignore out-of-range values.

## Kernel references

sys/dev/netif/msk/if_msk.c:2884-2927 (:2900-2902, :2906, :2910-2916),
:2671-2674, :3206-3217; sys/dev/netif/msk/if_mskreg.h:2198-2210, :2309.

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