# DF-3714 — em_tx_collect walks toward an unvalidated NIC TX-head register value

**File:** `sys/dev/netif/em/if_em.c`
**Lines:** 3284 (`tdh = E1000_READ_REG(&adapter->hw, E1000_TDH(0))`), 3299-3322 (cleanup walk), 3302 (KKASSERT)
**Family:** DF-3668 (ix), DF-3680 (igb) — TX-head write-back validation.

`em_tx_collect()` trusts the raw TDH register as the ring stop-index with no
range validation. An out-of-range TDH (device fault) makes `while (first != tdh)`
unreachable: `first` wraps the ring forever in interrupt/serialized context,
freeing every TX buffer on the first wrap (including buffers the hardware may
still own — DMA-after-free), then spinning permanently; `KKASSERT(num_avail <
num_tx_desc)` at `:3302` panics INVARIANTS builds after exactly `num_tx_desc`
increments. The same trust class exists in `em_82547_move_tail_serialized()`
(`:2003-2011`): `hw_tdt` read from TDT is used to index `tx_desc_base[hw_tdt]`
and walked with `++hw_tdt == num_tx_desc` reset logic that an out-of-range
start value turns into a ≤65536-iteration OOB descriptor read walk, ending in
`E1000_WRITE_REG(TDT, hw_tdt)` programming the device with an out-of-ring
tail index.

## Reproduce on hardware

Requires a device fault/descriptor corruption making TDH ≥ num_tx_desc (or
the 82547 TDT variant). DoS-only class, no memory-corruption sink on the host
side beyond the DMA-after-free of TX buffers during the first wrap.

## On this audit guest

No em hardware; `run.sh` presence probe + guest-executed bound-math harness
(`run.harness.log` section [C]): num_tx_desc=512, TDH=4096 → walk never
terminates (demonstrated 1,048,576 iterations / 2048 ring wraps at harness
limit), KKASSERT trip point computed at iteration 513.

```
sh build.sh && ./run.sh
```
