⬢ DragonFlyBSD Kernel Audit
DF-4045 / trigger.c
← back to finding ↓ download raw
/*
 * DF-4045 trigger.c - stge_rxeof device-fed length geometry model.
 *
 * Hardware precondition (cannot be met on this guest - virtio-net only,
 * no Sundance/Tamarack TC9021): a faulty or malicious IP1000A/TC9021 DMAs
 * an RFD status quadword into sc->sc_rdata.stge_rx_ring whose 16-bit
 * RxDMAFrameLen field (RFD_RxDMAFrameLen(x) = x & 0xffff, if_stgereg.h:167)
 * lies about the received frame length.  The driver applies it with only a
 * per-IF bound, never a per-buffer bound:
 *
 *   stge_rxeof, if_stge.c:1606-1608 (FrameEnd descriptor):
 *       mp->m_len = RFD_RxDMAFrameLen(status) - sc->sc_cdata.stge_rxlen;
 *       sc->sc_cdata.stge_rxlen += mp->m_len;
 *   if_stge.c:1623-1629 (only check):
 *       m->m_pkthdr.len = sc->sc_cdata.stge_rxlen;
 *       if (m->m_pkthdr.len > sc->sc_if_framesize) drop;
 *
 * sc_if_framesize = if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN (if_stge.c:2013)
 * and SIOCSIFMTU allows up to STGE_JUMBO_MTU = 9004 (if_stge.c:1261,
 * if_stgevar.h:55-57), so framesize can be 9022.  Every RX buffer is a
 * plain 2048-byte cluster from stge_newbuf (m_getcl, if_stge.c:2326-2329;
 * 2046 after the ETHER_ALIGN m_adj when framesize <= 2046, :2335-2336).
 *
 *   Case A (jumbo, OOB read): MTU=9004, single FrameEnd descriptor with
 *     framelen=9022: mp->m_len = 9022 on a 2048-byte cluster; pkthdr.len
 *     9022 <= framesize 9022 -> PASSES the only check.  Every consumer
 *     trusting m_len/pkthdr.len (BPF tap, m_copydata pulls in the stack)
 *     reads up to 6974 bytes of adjacent kernel heap into a delivered
 *     frame -> info leak to any local user with a socket/BPF on stgeX.
 *
 *   Case B (any MTU incl. default, negative m_len): k non-FrameEnd
 *     descriptors (each contributing the FULL buffer: 2046/2048 bytes)
 *     then a FrameEnd with framelen < rxlen: mp->m_len goes NEGATIVE,
 *     pkthdr.len = framelen (small, passes).  m_copydata
 *     (sys/kern/uipc_mbuf.c:1671,1689) computes
 *         unsigned count = min(m->m_len - off, len);
 *     on the negative-tail mbuf -> count ~ 4GB -> bcopy overflow ->
 *     panic/UB as soon as any consumer walks the chain (BPF attached).
 *     The first mbuf is full-sized, so ether_input's m_len < ETHER_HDR_LEN
 *     guard (if_ethersubr.c) does NOT save this chain.
 *
 * This program prints the exact extents implied by the masks/geometry.
 */
#include <stdio.h>

#define	RFD_RxDMAFrameLen_MAX	0xffff		/* if_stgereg.h:167 */
#define	MCLBYTES		2048
#define	ETHER_ALIGN		2
#define	ETHER_HDR_LEN		14
#define	ETHER_CRC_LEN		4
#define	STGE_JUMBO_FRAMELEN	9022		/* if_stgevar.h:55 */

int main(void)
{
	int framesize_dflt = 1500 + ETHER_HDR_LEN + ETHER_CRC_LEN;
	int framesize_jumbo = STGE_JUMBO_FRAMELEN;
	int buf_dflt = MCLBYTES - ETHER_ALIGN;	/* align applied when small */
	int buf_jumbo = MCLBYTES;		/* no align when jumbo */

	printf("DF-4045 stge_rxeof length-trust geometry model\n");
	printf("descriptor DMAFrameLen field    : 16 bits, max %d\n",
	    RFD_RxDMAFrameLen_MAX);
	printf("RX cluster (stge_newbuf)       : %d bytes (%d with "
	    "ETHER_ALIGN)\n", MCLBYTES, buf_dflt);
	printf("only bound in driver           : pkthdr.len > framesize "
	    "(if_stge.c:1625)\n");
	printf("framesize default MTU          : %d\n", framesize_dflt);
	printf("framesize jumbo  MTU 9004      : %d (SIOCSIFMTU allows it, "
	    "if_stge.c:1261)\n\n", framesize_jumbo);

	printf("case A - jumbo MTU, FrameEnd claims framelen = framesize:\n");
	for (int k = 0; k <= 3; k++) {
		int rxlen = k * buf_jumbo;
		if (rxlen >= framesize_jumbo)
			break;
		int mlen = framesize_jumbo - rxlen;
		int cap = buf_jumbo;
		if (mlen > cap)
			printf("  k=%d intermediates: last m_len %d - %d = %d "
			    "on %d-byte cluster -> OOB read %d bytes past "
			    "cluster\n",
			    k, framesize_jumbo, rxlen, mlen, cap, mlen - cap);
	}
	printf("  (default MTU: framelen <= 1518 < 2046 -> case A needs "
	    "jumbo MTU)\n\n");

	printf("case B - any MTU, framelen < rxlen (negative tail m_len):\n");
	{
		int k = 1, framelen = 100;
		int mlen = framelen - k * buf_dflt;
		printf("  k=%d intermediates (rxlen %d), FrameEnd framelen %d "
		    "-> m_len = %d\n", k, k * buf_dflt, framelen, mlen);
		printf("  pkthdr.len = %d <= framesize -> passes if_stge.c:"
		    "1625; first mbuf full -> survives ether_input guard\n",
		    framelen);
		printf("  m_copydata: unsigned count = min(%d - off, len) -> "
		    "~4GB bcopy -> panic (uipc_mbuf.c:1689)\n", mlen);
	}
	printf("\nprecondition: device-DMA'd RFD status lie "
	    "(faulty/malicious TC9021); guest has no stge(4) device\n");
	return 0;
}