# DF-3668 — ix(4) TX completion trusts NIC-DMA'd head index with no ring bound

**File:** `sys/dev/netif/ix/if_ix.c` · **Severity:** Low · **Class:** hardware-trust DoS

## PoC

No runnable PoC exists: the trigger requires an ixgbe NIC whose TX head
write-back (`*(txr->tx_hdr)`, DMA'd by the NIC) or TDH register reports a value
outside `[0, tx_ndesc)`. The audit guest has no such device (`env.txt`).

Source-level argument (see `VERDICT.md`):

- `if_ix.c:2368` — `while (first != hdr) { ... }` in `ix_txeof()`:
  `first` wraps within the ring, `hdr` is an unmasked `int` from
  `*(txr->tx_hdr)` (declared `volatile uint32_t *` at `if_ix.h:223`) or from
  `IXGBE_READ_REG(hw, IXGBE_TDH(...))`. If `hdr >= tx_ndesc` or `hdr < 0`,
  the equality is unreachable: the loop spins forever in interrupt context
  (INVARIANTS kernels trip `KKASSERT(avail < txr->tx_ndesc)` at 2371 and panic;
  production kernels soft-lockup with `avail` incrementing past `tx_ndesc`).
- Callers feeding the untrusted value: `if_ix.c:1203` (legacy `ix_intr`),
  `if_ix.c:4788` (`ix_msix_tx`), `if_ix.c:4810` (`ix_msix_rxtx`),
  `if_ix.c:4957` (`ix_npoll_tx`), and `ix_txgc` reads TDH directly at 2400.

## Fix validation (compile-level)

`fix.diff` rejects out-of-range `hdr` in both `ix_txeof()` and `ix_txgc()`.
Applied on the guest to `/usr/src` (md5-identical to the audit tree) —
`if_ix.ko` rebuilt with `-Werror`, clean (`build_patched.log`).

## Run (build validation only)

```
# on the audit guest:
cd /usr/src && patch -p1 < fix.diff
cd /usr/src/sys/dev/netif/ix && make obj && make
```
