# DF-3639 — bnx(4) trusts NIC-DMA'd return-ring `bge_len` with no buffer bound

**File:** `sys/dev/netif/bnx/if_bnx.c` (pass 2, GLM 5.3)
**Class:** kernel heap OOB read / info disclosure (CWE-125 / NIC-trust)
**Severity filed:** Medium, bucket `kernleak`, confidence certain (source-level)
**Verdict:** `inconclusive` at runtime — the audit guest has no Broadcom
hardware; the driver is compiled in but `bnx_attach()` is unreachable there.

## 1. Root cause (path:line)

`bnx_rxeof()` consumes RX return-ring buffer descriptors that the NIC has
filled in **via DMA into host memory**:

- `sys/dev/netif/bnx/if_bnx.c:2810` — `cur_rx = &ret->bnx_rx_ret_ring[...]`
- `sys/dev/netif/bnx/if_bnx.c:2848` —
  `m->m_pkthdr.len = m->m_len = cur_rx->bge_len - ETHER_CRC_LEN;`

`struct bge_rx_bd` (`sys/dev/netif/bge/if_bgereg.h:2327-2346`) is the shared
host-memory descriptor; its `bge_len` field is a **uint16_t written by the
device**. The receiving mbuf was allocated as:

- `if_bnx.c:816-822` — `m_getcl(M_*, MT_DATA, M_PKTHDR)` + `m_adj(ETHER_ALIGN)`
  => a 2048-byte cluster with **2046 usable bytes**.

Between 2842 (`bnx_newbuf_std()` swaps in a fresh cluster) and 2848 there is
**no check that `bge_len - ETHER_CRC_LEN` fits the cluster**. There is also no
lower bound check (a `bge_len < 18` yields a negative `m_len`).

## 2. Consequence trace

For a device that writes `bge_len = 65535` (firmware compromise, malicious
peripheral / Thunderclap-style DMA, or a silicon erratum) with the error flag
clear:

1. `m->m_len = m->m_pkthdr.len = 65531` describing a 2048-byte cluster
   (`if_bnx.c:2848`).
2. `ifp->if_input(ifp, m, pi, cpuid)` (`if_bnx.c:2881`) → `ether_input()`.
3. `sys/net/if_ethersubr.c:1512` — `ETHER_BPF_MTAP(ifp, m)` copies
   `min(m->m_pkthdr.len, ...)` bytes out of the cluster to every attached
   bpf(4) listener → **up to ~63.5 KB of kernel heap adjacent to the RX
   cluster is disclosed** (pointers, stale packet data, etc.).
4. IP-stack parsing (`ip_input`) and socket copies walk the same OOB range;
   crossing an unmapped page faults → panic (local DoS from a driver defect,
   or crash-for-remote if the OOB range hits a guard page).

For `bge_len < ETHER_HDR_LEN + ETHER_CRC_LEN` (18): `m_len` goes negative;
DragonFly's `ether_input` discards it at `sys/net/if_ethersubr.c:1499-1503`
(`m->m_len < sizeof(struct ether_header)` → `m_freem`) — a benign drop, **no**
underflow panic on this path (differs from bce DF-3604, where the length is
unsigned and the m_adj happens after the assignment).

## 3. Why hardware bounds don't save you

A healthy NIC clamps frames to the posted BD length (2046, `if_bnx.c:843,871`),
the MAC `BGE_RX_MTU` = 1522 (`if_bnx.c:3620-3621`) and the std RCB max frame
1536 (`if_bnx.c:1348-1349`). All of these are device-side registers/memory the
defecting device itself controls; the kernel-side check at 2848 is the missing
last line of defense. This is exactly the DF-3604 (bce) pattern; the pass-1
sibling sweep (DF-1478 my, DF-1481 vr, DF-1490 tx, DF-1514 ste, DF-1519 lge,
DF-1562 pcn, DF-1351 nfe, DF-1526 sf) never covered bnx (DB-verified), so this
is not a re-report.

## 4. Guest evidence (why `inconclusive`, honestly)

`run.log`:
- `kldstat -v` → `61 pci/if_bnx` — driver compiled into the running kernel.
- `pciconf -l | grep 14e4` → `NO_BROADCOM_DEVICE` — no Broadcom PCI device.
- `ifconfig -l` → `vtnet0 lo0` — no bnx interface; `sysctl hw.bnx` → unknown
  oid — `bnx_attach()` never executed.
- `md5 /usr/src/sys/dev/netif/bnx/if_bnx.c` = `49a18c4b7642b9b2ebd1d02ad11542e4`
  == audited tree → the unreachable code is byte-identical to what was audited.

`trigger.c` builds clean (`cc -O -Wall`) and runs, printing the hardware
precondition. No QEMU emulation of a BCM5717+ exists in this setup, so the
DMA-dependent primitive cannot be exercised: verdict `inconclusive` /
`reproduced: 0`, impact conditional on hostile/faulty hardware.

## 5. Exploit chain (on real hardware)

device writes bge_len=65535, error-flag clear → driver hands stack an mbuf
with m_len=65531 in a 2048B cluster → unprivileged user with a bpf listener on
bnx0 (or a TCP/UDP peer receiving forwarded data copied from the OOB range)
reads adjacent kernel heap → info leak. No write primitive from this bug alone
(the NIC's DMA write is bounded by the posted BD length it honors).

## 6. Fix

`fix.diff` — drop-and-count when `bge_len` is outside
`[ETHER_HDR_LEN + ETHER_CRC_LEN, m->m_len + ETHER_CRC_LEN]`, using the mbuf's
own posted length as the bound (stays correct even if jumbo ring support is
ever enabled). Fix is not runtime-validatable on this guest (no bnx hardware):
`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.
