# DF-3737 — emx(4) unvalidated TDH walk in emx_tx_collect (DF-3714 em(4) twin)

## What
`emx_tx_collect()` reads the raw TDH register into an `int` and walks the
TX ring until `first == tdh`, with no range validation:

- `tdh = E1000_READ_REG(&tdata->sc->hw, E1000_TDH(tdata->idx));` —
  sys/dev/netif/emx/if_emx.c:2817
- `while (first != tdh) { ... }` — if_emx.c:2832
- `KKASSERT(num_avail < tdata->num_tx_desc);` — if_emx.c:2835
- wrap: `if (++first == tdata->num_tx_desc) first = 0;` — :2853-2854

An out-of-range TDH — e.g. MMIO read-back of `0xFFFFFFFF` on surprise
removal, narrowing to `tdh = -1`, or any value ≥ `num_tx_desc` from a
faulty device — makes the terminator unreachable: `first` cycles
0..num_tx_desc-1 forever.  First pass frees every `tx_buf` slot
(DMA-after-free window vs the still-armed device), then
`KKASSERT`/`num_avail` overflow: INVARIANTS builds panic at :2835;
production builds spin forever holding the TX serializer (permanent wedge
of every ifsq/ifconfig path on that ring).

Exact twin of em(4)'s DF-3714 (Low: device-fault-triggered local DoS).

## Build / run
```
cc -O -o /root/emx_bounds_harness /root/emx_bounds_harness.c
/root/emx_bounds_harness        # section [C]
```

## Expected output
```
[C] num_tx_desc=512, ... raw TDH register=0xffffffff -> int tdh=-1
  TDH out of ring range: walked 4194304 descriptors (8192 full ring wraps)
  without reaching TDH -> unbounded loop under the TX serializer
  [OVERFLOW] KKASSERT(num_avail<num_tx_desc) fires on INVARIANTS builds ...
```
(iteration-capped replica of the loop math; the real loop has no cap).

## Device-side injection
Guest has no emx hardware (see env.txt).  Triggering for real needs
surprise removal / faulty-device register read-back — documented in
README; source-certain / hardware-gated (`inconclusive` per family
convention).
