# DF-3714 — VERDICT

**Status: source-certain, runtime hardware-gated (inconclusive on this guest — no em NIC).**

## Root cause

`em_tx_collect()` (`sys/dev/netif/em/if_em.c:3275-3335`) is the TDH-driven TX
cleanup path (invoked from `em_start:1169/1184`, `em_try_txgc:480`,
`em_tx_purge:3349`):

- `:3284` `tdh = E1000_READ_REG(&adapter->hw, E1000_TDH(0));` — used directly
  as the loop terminator; no `tdh < num_tx_desc` validation and no masking.
- `:3299-3322` `while (first != tdh) { ... num_avail++;
  tx_buffer = &tx_buffer_area[first]; if (m_head) em_free_txbuffer(...);
  if (++first == num_tx_desc) first = 0; }`.
- If `tdh` is outside `[0, num_tx_desc)` (as a 32-bit register read it can be
  any device-supplied value), `first` never equals `tdh`: the first wrap
  frees **every** TX buffer (including descriptors the NIC may still DMA
  from — DMA-after-free), `num_avail` counts past `num_tx_desc` tripping
  `KKASSERT(num_avail < num_tx_desc)` at `:3302` on INVARIANTS builds
  (panic), and on production builds (`KKASSERT` compiled out,
  `sys/sys/systm.h:117-119`) the loop spins forever while holding the
  interface serializer — permanent interrupt-context soft-lockup.
- Note `em_txeof()` (`:3213-3272`) is safe: it walks driver-internal
  `tx_dd[]` indices, not the register.

Same class in the 82547 workaround: `em_82547_move_tail_serialized()`
(`:2003-2024`) reads `hw_tdt = E1000_READ_REG(TDT)` into a `uint16_t`, uses it
to index `tx_desc_base[hw_tdt]` and walks `++hw_tdt == num_tx_desc` — an
out-of-range start value yields up to 65536 OOB descriptor reads
(`length += tx_desc->lower.flags.length` over adjacent kernel memory) and a
final `E1000_WRITE_REG(E1000_TDT(0), hw_tdt)` programming the device with an
out-of-ring tail (device-side descriptor fetch beyond the ring).

## Threat

Device-trust DoS, same premise/gravity as accepted DF-3668 (ix) and DF-3680
(igb): needs a faulty device (TDH/TDT corruption or erratum). Low severity.

## Verification

- Guest has no em hardware (`run.sh`).
- Guest-executed bound-math harness (`run.harness.log` [C]): TDH=4096 with a
  512-entry ring → non-terminating walk (1,048,576 iterations at harness
  cap), KKASSERT trip at the 513th descriptor, production build →
  serializer-held permanent spin. In-range TDH (100) verified safe.

## Fix

`fix.diff`: bail out of `em_tx_collect()` when
`(uint32_t)tdh >= (uint32_t)num_tx_desc` (device fault; do not walk).
Not hardware-testable on this guest (`fix_status: not_testable`).

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