โฌข DragonFlyBSD Kernel Audit
DF-3639 / trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-3639 โ€” bnx(4) RX path trusts NIC-DMA'd return-ring bge_len
 *
 * Trigger (HARDWARE REQUIRED โ€” cannot run on the audit guest):
 *
 *   The BCM5717/5718/5719/5720/577xx (bnx) RX completion path receives
 *   `struct bge_rx_bd` entries in the host-memory return ring that the
 *   NIC fills in via DMA.  sys/dev/netif/bnx/if_bnx.c:
 *
 *     2810:  cur_rx = &ret->bnx_rx_ret_ring[ret->bnx_rx_saved_considx];
 *     2812:  rxidx = cur_rx->bge_idx;                 <- NIC-written (DF-1148)
 *     2842:  if (bnx_newbuf_std(ret, rxidx, 0)) ...  <- swaps in a fresh
 *                                                     2048-byte cluster
 *     2848:  m->m_pkthdr.len = m->m_len = cur_rx->bge_len - ETHER_CRC_LEN;
 *                                            ^^^^^^ NIC-written uint16_t,
 *                                                   NO MCLBYTES bound
 *
 *   The receiving cluster is m_getcl() (MCLBYTES == 2048) with
 *   m_adj(ETHER_ALIGN) => 2046 bytes usable (if_bnx.c:816-822).
 *
 *   A faulty or malicious NIC (firmware compromise, PCIe DMA attack,
 *   silicon erratum) that writes bge_len > 2050 into the return-ring BD
 *   makes the driver hand the network stack an mbuf describing up to
 *   65531 bytes inside a 2048-byte cluster:
 *
 *     - ether_input (sys/net/if_ethersubr.c:1512) runs ETHER_BPF_MTAP()
 *       on it  => the bpf(4) tap copies up to ~63.5 KB of kernel heap
 *       PAST the cluster out to the capturing socket (kernel heap info
 *       disclosure);
 *     - IP-stack header validation and socket copies read the same OOB
 *       range (further leak / fault when the walk crosses an unmapped
 *       page => panic).
 *
 *   bge_len < ETHER_HDR_LEN + ETHER_CRC_LEN (18) yields a negative
 *   m_len which DragonFly's ether_input discards at
 *   sys/net/if_ethersubr.c:1499-1503 (`m_len < sizeof(ether_header)')
 *   => benign drop (no underflow panic on this path โ€” unlike the bce
 *   sibling DF-3604, where the length is unsigned).
 *
 *   Hardware-layer bounds that a *healthy* NIC enforces (and which this
 *   bug removes as the last line of defense):
 *     - std producer BD len is 2046 (if_bnx.c:843,871),
 *     - MAC max RX frame BGE_RX_MTU = mtu+14+4+4 = 1522 (if_bnx.c:3620),
 *     - std RCB max frame 1536 for 57765+ (if_bnx.c:1348-1349).
 *
 *   On real hardware the reproduction is:
 *     1. Attach a BCM5717/5719/5720/5778x based NIC (bnx0).
 *     2. Have the device DMA a return-ring BD with bge_len = 65535 and
 *        clear error flags (modified firmware / malicious peripheral /
 *        erratum).
 *     3. `tcpdump -i bnx0 -w out.pcap` (or any bpf listener) captures
 *        up to 65531 bytes per frame => adjacent kernel heap disclosed.
 *
 *   The audit QEMU guest has virtio-net only (run.log: NO_BROADCOM_
 *   DEVICE); the bnx attach path is unreachable there, so this trigger
 *   CANNOT be exercised on it.  See VERDICT.md for the source-level
 *   proof and the guest evidence.
 */
#include <sys/types.h>
#include <stdio.h>

int
main(void)
{
	fprintf(stderr,
	    "DF-3639: this trigger requires bnx(4) hardware "
	    "(BCM5717/5718/5719/5720/577xx).\n"
	    "On such a system: configure the NIC, force the device to write\n"
	    "bge_len > 2050 (or < 18) into an RX return-ring BD, then capture\n"
	    "the handed-up frame (tcpdump -i bnx0 -w out.pcap) and verify the\n"
	    "captured frame length exceeds MCLBYTES - ETHER_ALIGN (2046).\n");
	return 0;
}