# DF-3718 — mxge_tx_done() walks the TX ring toward an unvalidated NIC completion counter

## Vulnerability

`sys/dev/netif/mxge/if_mxge.c` `mxge_tx_done()` (:2364-2415):

```c
while (tx->pkt_done != mcp_idx) {        /* :2369 */
        idx = tx->done & tx->mask;
        tx->done++;
        m = tx->info[idx].m;
        if (m != NULL) {                  /* first-segment slots only */
                tx->pkt_done++;
                ...
                bus_dmamap_unload(tx->dmat, tx->info[idx].map);
                m_freem(m);               /* :2386 */
        }
}
```

`mcp_idx` is `(int)send_done_count` — the firmware statistics block's
packet-completion counter, **NIC-DMA'd data** (`mcp_irq_data_t.send_done_count`,
mxge_mcp.h) — read at :2695/:2767/:2844 and passed from all three interrupt
paths (`mxge_legacy` :2699, `mxge_msi` :2770, `mxge_msix_rxtx` :2847).
There is **no bound** relating `mcp_idx` to `tx->pkt_done`/`tx->req`:

1. **Free of unsent descriptors (DMA-after-free).**  A counter even a few
   packets ahead makes the loop walk forward, unloading dmamaps and freeing
   mbufs for descriptors that are still queued-but-unsent.  The NIC keeps
   owning those DMA mappings → post-free DMA reads of freed mbuf memory
   (content disclosure to the wire / corruption), the same shape as
   DF-3714 (em tx_collect) and DF-3680 (igb txeof).
2. **Infinite interrupt-context spin.**  `pkt_done` only advances on
   non-NULL `info[idx].m`; after one full ring revolution every slot has
   `m == NULL`, `pkt_done` freezes, and if the NIC's counter still doesn't
   match (garbage, desync), the `while` spins forever — holding
   `tx->tx_serialize` (entered at :2769/:2846) and, in the MSI/legacy
   paths, `main_serialize` (`mxge_msi` asserts it at :2737) — wedging the
   interface and blocking every thread that later tries to take those
   serializers.  `tx->done`/`idx` stay masked (no OOB write), so the
   failure mode is a permanent spin/DoS, not memory corruption on the host
   side.

## Threat model

NIC-DMA'd counter (hostile/compromised device or device fault), identical
trust boundary to DF-3680/DF-3714 (both filed Low/certain).

## Verification

Hardware-gated (no Myricom NIC on the guest; module never attaches) —
source-certain only, family convention → `inconclusive`.

## Fix

`fix.diff` bounds the walk by ring occupancy (`tx->done >= tx->req` →
warn + break), which kills the runaway spin; a mismatched-but-in-range
counter still must be dealt with by the watchdog.  git-apply clean.
