# DF-4231 VERDICT — txp_rx_reclaim() rx_len trust → m_copydata OOB read+write

**Verdict: source-certain (High, memcorrupt); runtime inconclusive — the audit
guest has no 3Com 3c990 (Typhoon) device and QEMU has no Typhoon model, so the
DMA-fed precondition cannot be staged. Driver verified present in the running
kernel; guest source byte-identical to the audited tree.**

## Root cause chain (path:line)

1. Firmware caps are advisory only. `txp_attach()`/`txp_init()` *tell* the
   firmware `TXP_CMD_MAX_PKT_SIZE_WRITE, TXP_MAX_PKTLEN` (`if_txp.c:263-267`,
   `if_txp.c:1112-1113`) — `TXP_MAX_PKTLEN = 0x0800 = 2048` (`if_txpreg.h:631`)
   — but nothing on the receive path ever *verifies* a length.

2. The receive descriptors live in the DMA-coherent `sc_ldata` block
   (`if_txp.c:255-256`, `contigmalloc`), shared with the device:
   `struct txp_rx_desc { ... volatile u_int16_t rx_len; ... }` —
   `if_txpreg.h:260-275`. The driver reads `rxd->rx_len` straight from it.

3. `txp_rx_reclaim()` (`if_txp.c:649-744`), called from `txp_intr()`
   (`if_txp.c:624-627`) whenever the device-fed `hv_rx_*_write_idx` differs
   from the read index:
   - `if_txp.c:676` `m->m_pkthdr.len = m->m_len = rxd->rx_len;`
     — the receive cluster (one `MCLGET` = 2048 bytes, `if_txp.c:770-774`)
     is re-labeled to the firmware's length.
   - `if_txp.c:688-704` (inside `#ifdef __STRICT_ALIGNMENT`, unconditionally
     defined at `if_txp.c:85): a replacement `mnew` header mbuf is allocated;
     it gets a cluster only `if (m->m_len > (MHLEN - 2))` (`if_txp.c:693-700`)
     — a single `MCLGET`, still 2048 bytes;
     `m_copydata(m, 0, m->m_pkthdr.len, mtod(mnew, void *))` (`if_txp.c:704`).

4. `m_copydata()` (`sys/kern/uipc_mbuf.c:1671-1696`) does
   `count = min(m->m_len - off, len); bcopy(mtod(m) + off, cp, count);` —
   with `m->m_len == len == rx_len` this is a single `bcopy` of `rx_len`
   bytes: **read** from the 2048-byte source cluster, **write** into the
   2048-byte destination cluster. No `KASSERT` fires (the chain legitimately
   has one mbuf whose `m_len` the driver itself inflated).

For `rx_len = 65535` the write runs 63531 bytes past the destination cluster.
Both clusters come from the mbuf cluster zone, so the overrun smashes
adjacent 2KB cluster slabs with content sourced from kernel heap — with
slab grooming, a corrupted object of the attacker's (firmware's) choosing.
This is the same defect class accepted in this audit as DF-3604 (bce),
DF-1478 (my), DF-1481 (vr), DF-1490 (tx), DF-1514 (ste), DF-1519 (lge),
DF-1562 (pcn), DF-1351 (nfe) — txp was simply missing the clamp.

Pass-1 overlap check: the only prior txp finding is DF-1461 (kfree UAF in
`txp_rxbuf_reclaim`'s error path) — different function, different bug; the
DB was queried, no rx_len finding exists for this file.

## Exploit chain (on real hardware)

device/firmware writes rx_desc{rx_flags=RX_FLAGS_DATA, rx_len=65535} →
intr → `txp_rx_reclaim` → 63KB `bcopy` past `mnew`'s cluster → adjacent
mbuf-cluster-zone objects overwritten with heap content → e.g. corrupt an
in-flight `mbuf`'s `m_data`/`m_ext` of a victim socket, or a neighbouring
kernel object, → uid=0 equivalent for the device's owner. For a
*malfunctioning* (not malicious) firmware the same code is a kernel-crash
generator. (A PCIe device can of course DMA anywhere; the finding's value is
(a) faulty-firmware robustness, (b) virtualization passthrough where the
"device" is a guest, (c) firmware compromise.)

## Why inconclusive at runtime (evidence)

`run.log`:
```
===DRIVER_IN_KERNEL===
		161 pci/if_txp
===THREECOM_DEVICE_PROBE===
NO_3COM_PCI_DEVICE
```
Guest `/usr/src/sys/dev/netif/txp/if_txp.c` md5 `5757b04710b9a2f3b6bbd17196d6df81`
== audit tree. No QEMU Typhoon machine model exists to emulate the DMA side.

## Fix validation

`fix.diff` clamps with the file's own error idiom (`ierrors++` + skip, cf.
`if_txp.c:664-668`). Not runtime-validable for the same hardware-absence
reason (fix_status = not_testable).

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