⬢ DragonFlyBSD Kernel Audit
DF-3849 / trigger.c
← back to finding ↓ download raw
/*
 * trigger.c - arithmetic replica of the dc_rxeof() length-trust defect
 * (sys/dev/netif/dc/if_dc.c:2511-2604).
 *
 * dc_rxeof() takes the frame length from the NIC-DMA'd RX descriptor
 * status word with NO bounds check against the posted mbuf cluster:
 *
 *   total_len = DC_RXBYTES(rxstat);                     // if_dc.c:2523
 *   ...
 *   total_len -= ETHER_CRC_LEN;                         // if_dc.c:2568
 *   if (dc_quick && dc_newbuf(sc, i, NULL) == 0) {
 *           m->m_pkthdr.len = m->m_len = total_len;     // if_dc.c:2583
 *   } else {
 *           m0 = m_devget(mtod(m, char *) - ETHER_ALIGN,
 *                         total_len + ETHER_ALIGN, 0, ifp);   // :2590
 *   }
 *
 * The only sanity gate before this is the RXERR branch (if_dc.c:2548-2565),
 * which reads error bits from the SAME device-produced status word, and
 * explicitly tolerates GIANT (0x80) frames without other error bits
 * ("could be vlans").
 *
 * The posted cluster (dc_newbuf, if_dc.c:2321-2343) is MCLBYTES with
 * m_adj(sizeof(u_int64_t)): 2048 - 8 = 2040 bytes usable.  DC_RXSTAT_RXLEN
 * is a 14-bit field (if_dcreg.h:415), so a device-written descriptor with
 * RXERR clear and FRAMESZ = 16383 produces m_len = 16379 on a 2040-byte
 * cluster: a ~14 KB heap over-read when the stack consumes the mbuf
 * (bpf taps / ether_input / socket copies).  FRAMESZ < 4 wraps total_len
 * negative (assigned to m_len, an int).
 *
 * This program executes the exact integer math with the exact macros from
 * if_dcreg.h. Compile: cc -O2 -o trigger trigger.c
 */
#include <stdio.h>

#define	DC_RXSTAT_RXLEN		0x3FFF0000	/* if_dcreg.h:415 */
#define	DC_RXBYTES(x)		((x & DC_RXSTAT_RXLEN) >> 16) /* :418 */
#define	ETHER_CRC_LEN		4
#define	MCLBYTES		2048		/* sys/sys/param.h */
#define	POSTED_USABLE		(MCLBYTES - 8)	/* m_adj(sizeof(u_int64_t)) */

static void
feed(unsigned rxstat)
{
	int total_len;

	total_len = DC_RXBYTES(rxstat);		/* if_dc.c:2523 */
	total_len -= ETHER_CRC_LEN;		/* if_dc.c:2568 */
	printf("rxstat=0x%08x FRAMESZ=%5u -> m_len=%-6d on a %d-byte cluster",
	    rxstat, DC_RXBYTES(rxstat), total_len, POSTED_USABLE);
	if (total_len > POSTED_USABLE)
		printf("  => OOB READ %d bytes past the cluster (%.1f KB)",
		    total_len - POSTED_USABLE,
		    (total_len - POSTED_USABLE) / 1024.0);
	if (total_len < 0)
		printf("  => NEGATIVE m_len (unsigned ~%u): stack OOB walk",
		    (unsigned)total_len);
	printf("\n");
}

int
main(void)
{
	printf("dc_rxeof() length-trust arithmetic (if_dc.c:2523,2568,2583)\n");
	/* legal frame, RXERR clear, whole frame */       feed(1518 << 16);
	/* VLAN frame (the GIANT tolerance rationale) */  feed(1522 << 16);
	/* crafted: FRAMESZ 2048 (just past cluster) */   feed(2048 << 16);
	/* crafted: FRAMESZ 4096 */                       feed(4096 << 16);
	/* crafted: 14-bit field max, RXERR clear */      feed(16383 << 16);
	/* crafted: max + FIRSTFRAG|LASTFRAG set */       feed((16383 << 16) | 0x0300);
	/* crafted: FRAMESZ 2 -> total_len underflow */   feed(2 << 16);
	return (0);
}