โฌข DragonFlyBSD Kernel Audit
DF-3989 / trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-3989 trigger.c โ€” arithmetic model of ale_rxeof trusting the
 * controller-DMA'd 14-bit RRS frame length (DF-3604 / DF-3759 / DF-3933
 * family, ale(4) instance).
 *
 * HARDWARE PRECONDITION (absent on audit guest โ€” virtio-net only, no
 * 1969:1026 AR8121/8113/8114 PCI device): a faulty or malicious Atheros
 * L1E controller writes an rx_rs into the DMA'd Rx page whose
 * length field (ALE_RD_LEN_MASK 0x3FFF0000, 14 bits โ€” if_alereg.h:675)
 * disagrees with the frame actually written, with none of the
 * ALE_RD_CRC|CODE|DRIBBLE|RUNT|OFLOW|TRUNC error bits set.
 *
 * Path (sys/dev/netif/ale/if_ale.c):
 *   ale_intr :1981 -> ale_rxeof :2153
 *     :2204 length = ALE_RX_BYTES(le32toh(rs->length));   // 0..16383, no
 *                                                          // bound vs page
 *     :2232 m = m_devget(rs + 1, length - ETHER_CRC_LEN, 0, ifp);
 *            // bcopy of (length-4) bytes from
 *            //   rx_page->page_addr + rx_page->cons + sizeof(rx_rs)
 *            // into a fresh mbuf -> delivered to the network stack
 *   Page geometry (if_alevar.h / if_ale.c:940):
 *     ALE_RX_PAGE_SZ     = roundup(1522,32)*128            = 196608
 *     non-jumbo page     = roundup(1522+196608,32)         = 198144
 *     jumbo page (8121)  = roundup(8132+196608,32)         = 204768
 *   cons is a multiple of 32 bounded only by ">= ALE_RX_PAGE_SZ -> reset"
 *     (ale_rx_update_page :2068-2075), so the last in-page rs sits at
 *     cons = 196576; the copy then reads up to 196576+8+16379 = 212963.
 *
 * Result: heap OOB READ past the bus_dmamem_alloc'd page into adjacent
 * kernel memory, copied into an mbuf and passed up (bpf/socket payload =
 * kernel heap disclosure).  Unlike bce (DF-3604)/alc (DF-3759), the
 * length<4 underflow is SAFE here: length-ETHER_CRC_LEN is uint32, passed
 * to int tot in m_devget -> <=0 loop never runs -> NULL -> drop
 * (sys/kern/uipc_mbuf.c:2235).
 */
#include <stdio.h>
#include <stdint.h>

#define ALE_RX_PAGE_SZ		196608u
#define GUARD_NONJUMBO		1522u	/* ALE_MAX_FRAMELEN */
#define GUARD_JUMBO		8132u	/* ALE_JUMBO_FRAMELEN  (AR8121) */
#define RX_PAGE_ALIGN		32u
#define RS_SZ			8u	/* sizeof(struct rx_rs) __packed */
#define ETHER_CRC_LEN		4u
#define LEN_MASK_MAX		0x3FFFu	/* 14-bit ALE_RD_LEN_MASK */

static unsigned roundup32(unsigned v) { return (v + 31u) & ~31u; }

static void model(unsigned guard, const char *tag)
{
	unsigned pagesize = roundup32(guard + ALE_RX_PAGE_SZ);
	unsigned cons_last = ALE_RX_PAGE_SZ - RX_PAGE_ALIGN; /* 196576 */
	unsigned copy_len = LEN_MASK_MAX - ETHER_CRC_LEN;	  /* 16379 */
	unsigned read_end = cons_last + RS_SZ + copy_len;

	printf("%s page = roundup(%u+%u,32) = %u bytes\n",
	    tag, guard, ALE_RX_PAGE_SZ, pagesize);
	printf("  worst-case rs at cons=%u, copy %u bytes -> read ends %u\n",
	    cons_last, copy_len, read_end);
	if (read_end > pagesize)
		printf("  => OOB READ past DMA page: %u bytes of adjacent "
		    "kernel heap into mbuf -> if_input (kernleak)\n",
		    read_end - pagesize);
	else
		printf("  => in-bounds\n");
}

int main(void)
{
	printf("DF-3989 model: ale_rxeof unbounded 14-bit RRS length\n"
	    "  (sys/dev/netif/ale/if_ale.c:2204,2232; if_alereg.h:675)\n\n");
	model(GUARD_NONJUMBO, "non-jumbo (AR8113/AR8114)");
	model(GUARD_JUMBO, "jumbo     (AR8121)");
	printf("\nlength<4 variant: m_devget(int tot <= 0) returns NULL "
	    "-> iqdrops (SAFE, unlike bce/alc)\n");
	printf("legitimate max frame (jumbo): %u (mtu %u + 18 hdr/vlan + 4 "
	    "crc) < 16383 mask max -> any length in (%u..16383] is a "
	    "device lie\n", 8110u + 18u + 4u, 8110u, 8110u + 22u);
	printf("\nTRIGGER_RC=0 (model OK; hardware-gated on this guest: "
	    "no Atheros NIC)\n");
	return (0);
}