⬢ DragonFlyBSD Kernel Audit
DF-3759 / trigger.c
← back to finding ↓ download raw
/*
 * DF-3759 trigger - alc(4) RRS frame-length trust (if_alc.c alc_rxeof)
 * ===================================================================
 *
 * HARDWARE PRECONDITION (why this cannot fire on the audit guest):
 *   The trigger is the Atheros/Qualcomm NIC itself.  alc_rxeof()
 *   (sys/dev/netif/alc/if_alc.c:3293-3405) consumes an Rx Return
 *   Descriptor (RRS) that the CONTROLLER DMA'd into the return ring:
 *
 *     status  = le32toh(rrd->status);                  :3302
 *     rdinfo  = le32toh(rrd->rdinfo);                  :3303
 *     nsegs   = RRD_RD_CNT(rdinfo);   4 bits  -> 0..15 :3305
 *     rxlen   = RRD_BYTES(status);    14 bits -> 0..16383
 *                                              :3307  (if_alcreg.h:1240-1261)
 *
 *   and then builds the stack-visible mbuf WITHOUT ANY BOUND on rxlen
 *   against the buffers that actually back the frame (each holds
 *   sc->alc_buf_size == RX_BUF_SIZE_MAX == 1536 bytes, if_alcreg.h:554,
 *   allocated as MCLBYTES clusters in alc_newbuf, if_alc.c:3187-3190):
 *
 *     m->m_pkthdr.len = rxlen - ETHER_CRC_LEN;                      :3368
 *     (nsegs == 1)  m->m_len = m->m_pkthdr.len;                     :3386
 *     (nsegs > 1)   mp->m_len = rxlen - (nsegs-1)*alc_buf_size;     :3372
 *                   if (mp->m_len <= ETHER_CRC_LEN)                 :3375
 *                       rxtail->m_len -= (ETHER_CRC_LEN - mp->m_len):3378
 *
 *   On a faulty or malicious controller (or after descriptor corruption):
 *     rxlen=16383, nsegs=1 -> m_len = pkthdr.len = 16379 in a 2048-byte
 *         cluster: every consumer past ether_input (bpf, ip_input, TCP,
 *         socket copyout) reads up to ~14.3KB past the end of the
 *         cluster = kernel heap disclosure to unprivileged sockets.
 *     rxlen=0..3           -> pkthdr.len negative (int) -> mbuf
 *         invariant violation.
 *     nsegs=15, rxlen=100  -> last m_len = 100 - 14*1536 = -21404 ->
 *         the CRC-strip branch drives rxtail->m_len NEGATIVE in the
 *         delivered chain -> panics in mbuf walkers.
 *
 *   Note the sibling finding DF-1207 covers the ARRAY-INDEX half
 *   (rx_cons/nsegs unvalidated for rxdesc[] indexing, :3304/:3329);
 *   this finding is the LENGTH half (m_len/pkthdr.len vs cluster size)
 *   - same descriptor, distinct sinks, distinct primitive.
 *
 * This program models the exact arithmetic of if_alc.c:3368-3386 so the
 * corrupted lengths can be inspected without hardware, and prints what
 * the network stack would see.  Compile: cc -o trigger trigger.c
 */
#include <stdio.h>
#include <stdint.h>

#define	ETHER_CRC_LEN		4
#define	RX_BUF_SIZE_MAX		1536	/* if_alcreg.h:554  (alc_buf_size) */
#define	MCLBYTES		2048	/* cluster actually backing each seg */

struct model {
	int	m_len;			/* mbuf->m_len      (int)  */
	int	m_pkthdr_len;		/* mbuf->m_pkthdr.len (int) */
	int	cluster;		/* real backing bytes per seg */
};

static void
rxeof_model(unsigned rxlen, unsigned nsegs, int chain)
{
	int i, pkthdr_len;
	struct model m[16];

	/* :3345 - every chained buffer is assumed full-size */
	for (i = 0; i < (int)nsegs; i++) {
		m[i].m_len = RX_BUF_SIZE_MAX;
		m[i].cluster = MCLBYTES;
	}
	/* :3368 */
	pkthdr_len = (int)(rxlen - ETHER_CRC_LEN);
	if (nsegs > 1) {
		/* :3372 */
		m[nsegs - 1].m_len =
		    (int)(rxlen - (nsegs - 1) * RX_BUF_SIZE_MAX);
		/* :3375-3384 */
		if (m[nsegs - 1].m_len <= ETHER_CRC_LEN) {
			/* :3376-3380: chop CRC off the PREVIOUS mbuf */
			m[nsegs - 2].m_len -=
			    ETHER_CRC_LEN - m[nsegs - 1].m_len;
		} else {
			m[nsegs - 1].m_len -= ETHER_CRC_LEN;
		}
	} else {
		/* :3386 */
		m[0].m_len = pkthdr_len;
	}
	if (chain) {	/* print the delivered chain */
		for (i = 0; i < (int)nsegs; i++)
			printf("    m[%d].m_len=%-7d (cluster %d)%s\n", i,
			    m[i].m_len, m[i].cluster,
			    m[i].m_len > m[i].cluster ?
			    "   <-- OOB READ BEYOND CLUSTER" :
			    (m[i].m_len < 0 ? "   <-- NEGATIVE m_len" : ""));
	}
}

int
main(void)
{
	printf("DF-3759 model of alc_rxeof (if_alc.c:3368-3386), "
	    "alc_buf_size=%d, cluster=%d\n\n", RX_BUF_SIZE_MAX, MCLBYTES);

	printf("[leak] rxlen=16383 (RRS 14-bit max), nsegs=1 "
	    "(device-controlled):\n");
	rxeof_model(16383, 1, 1);
	printf("    => pkthdr.len=16379 vs 2048-byte cluster: ~14.3KB heap "
	    "OOB read,\n       payload copyable to an unprivileged socket\n\n");

	printf("[underflow] rxlen=0, nsegs=1:\n");
	rxeof_model(0, 1, 1);
	printf("    => pkthdr.len=-4 (int underflow)\n\n");

	printf("[negative m_len] rxlen=100, nsegs=15:\n");
	rxeof_model(100, 15, 1);
	printf("    => negative m_len survives into the delivered chain; "
	    "mbuf walkers\n       (m_pullup/m_adj/m_copydata) violate "
	    "invariants -> panic\n\n");

	printf("Real trigger path (needs AR8131/8132/8151/8152/816x/817x "
	    "hardware):\n"
	    "  device writes RRS{RRD_VALID, rd_cnt=1, bytes=16383} with no "
	    "error bits\n"
	    "  -> alc_rxintr (if_alc.c:3236) -> alc_rxeof (if_alc.c:3293)\n"
	    "  -> m handed to if_input (if_alc.c:3400) describing 16379 "
	    "bytes in a\n"
	    "     2048-byte cluster.\n");
	return 0;
}