# DF-4233 VERDICT — txp_init() ignores txp_rxring_fill() failure

**Verdict: source-certain ignore + stale-ring mechanics (High, memcorrupt);
the firmware-side half (whether the 3XP honors the stale
hv_rx_buf_write_idx) is likely-but-not-certain, so overall confidence
"likely". Runtime inconclusive: no 3c990 on the audit guest (run.log).**

## Trace

```
txp_init()  if_txp.c:1097
  :1107  if (ifp->if_flags & IFF_RUNNING) return;    // no double init
  :1110  txp_stop(sc);
           └─ txp_rxring_empty(sc)  if_txp.c:1522 → :1081-1090
                for each of 256 slots: m_freem(sd->sd_mbuf); sd->sd_mbuf = NULL;
                // rb_paddrlo NOT cleared; hv_rx_buf_write_idx NOT reset
  :1112  MAX_PKT_SIZE cmd
  :1122  STATION_ADDRESS cmd
  :1125  txp_set_filter()
  :1127  txp_rxring_fill(sc);          // <-- return value IGNORED
           └─ :1047-1064 for (i = 0; i < RXBUF_ENTRIES; i++) {
                  MGETHDR/MCLGET(M_NOWAIT); on failure return(ENOBUFS); }
              :1066-1067 hv_rx_buf_write_idx = (RXBUF_ENTRIES-1)*16;  // success only
  :1129  TXP_CMD_TX_ENABLE
  :1130  TXP_CMD_RX_ENABLE             // <-- RX starts over a half-filled ring
  :1139  ifp->if_flags |= IFF_RUNNING;
```

State after a failed re-init: prefix of the buffer ring holds fresh
clusters; the rest holds `rb_paddrlo` of **freed** clusters and
`sd->sd_mbuf == NULL`; the firmware's view of available buffers
(`hv_rx_buf_write_idx`, a hostvar word — `if_txpreg.h:546`) still says
"up to the old producer".

Inbound frame on a stale slot:
1. firmware DMAs frame content into the freed cluster (heap UAF write,
   attacker-controlled content, mbuf cluster zone);
2. firmware posts the rx descriptor (round-tripping the slot's `rb_sd`
   pointer, see DF-4232 analysis);
3. `txp_rx_reclaim()` `if_txp.c:671-676`: `sd = rxd->rx_sd; m = sd->sd_mbuf;`
   → `m == NULL` → `m->m_pkthdr.len = m->m_len = rxd->rx_len;` writes
   through NULL → immediate kernel panic.

So even in the least favorable firmware interpretation, the NULL deref at
:676 fires the moment a completion arrives for an unfilled slot; in the
natural interpretation (firmware trusts hostvar, which is the whole design
of the hostvar block) the DMA-into-freed-cluster happens first.

## Precondition realism

`MCLGET(M_NOWAIT)` failure = transient cluster exhaustion. A remote peer
sustaining an inbound flood while a local (or scripted) `ifconfig txp0 up`
runs is the realistic recipe; DFly clusters come from a limited zone, and
memory pressure is externally seedable. First-`up` after attach can also
fail mid-fill under pressure (same consequence, stale = zero paddrs →
firmware DMA to physical 0 — see notes in the finding).

## Why inconclusive at runtime

Same as DF-4231/4232: no Typhoon device on the guest (run.log:
`NO_3COM_PCI_DEVICE`, driver present in kernel, source md5 identical).
The MGET failure precondition could be staged in the guest, but with no
device to consume the ring there is nothing to observe.

## Fix

fix.diff: `if (txp_rxring_fill(sc) != 0) { /* leave interface down */ ... return; }`
mirroring how every other DFly NIC driver aborts init when its rx ring
cannot be filled (cf. re(4) `re_init` → `re_rx_list_init` error paths).

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