# DF-4231 — txp(4) trusts NIC-fed `rxd->rx_len` → unbounded `m_copydata` (heap OOB read+write)

**File:** `sys/dev/netif/txp/if_txp.c` (pass 2, GLM 5.3)
**Severity:** High (memcorrupt bucket) — hardware-gated (3Com 3c990 Typhoon)
**Status:** source-certain; runtime inconclusive on the audit guest (no 3c990 device, QEMU has no Typhoon model)

## Build / run

```
scp trigger.c guest:/tmp/t4231.c
ssh guest-root 'cd /tmp && cc -o trig_4231 t4231.c && ./trig_4231 && kldstat -v | grep if_txp && (pciconf -l | grep -i 10b7 || echo NO_3COM_PCI_DEVICE)'
```

`trigger.c` is an annotated hardware-precondition probe (per DF-3604 convention):
it prints the exact sink lines and proves the driver is in the running kernel
while no 3Com device exists, i.e. the path cannot be exercised on this guest.

## Expected output

```
DF-4231: txp_rx_reclaim() trusts NIC-fed rxd->rx_len
  ...
===DRIVER_IN_KERNEL===
		161 pci/if_txp
===THREECOM_DEVICE_PROBE===
NO_3COM_PCI_DEVICE
```

## The bug

* `sys/dev/netif/txp/if_txp.c:676` — `m->m_pkthdr.len = m->m_len = rxd->rx_len;`
  `rxd` lives in the DMA-shared receive ring (`sc_ldata`), `rx_len` is
  `volatile u_int16_t` (`if_txpreg.h:263`) → fully firmware-controlled,
  never checked against `TXP_MAX_PKTLEN` (2048) or `MCLBYTES`.
* `sys/dev/netif/txp/if_txp.c:688-704` (`__STRICT_ALIGNMENT` is defined at
  `if_txp.c:85`, so always compiled): destination `mnew` gets at most one
  2048-byte cluster, then `m_copydata(m, 0, m->m_pkthdr.len, mtod(mnew, void *))`
  copies `rx_len` bytes from the (2048-byte) source cluster into it.
* `sys/kern/uipc_mbuf.c:1671` — `m_copydata()` trusts `m->m_len` (which the
  driver just stamped with `rx_len`); `count = min(m->m_len - off, len)` =
  `rx_len` in one `bcopy`.

Result for `rx_len > 2048`: OOB read of up to 63531 bytes past the source
cluster **and** an equal-size OOB write past the destination cluster —
kernel heap corruption with heap-sourced contents, from a
malicious/faulty Typhoon firmware. With the copy path compiled out the same
inflated `m_len` would instead hand the network stack an over-long mbuf
(heap info leak); here the write variant is the live one.

## Fix

`fix.diff` (this pack) drops any frame whose length exceeds `MCLBYTES`
before the stamp/copy, using the file's own error idiom.
