DragonFlyBSD Kernel Audit
DF-1199 / harness.c
← back to finding ↓ download raw
/*
 * DF-1199 harness — radeon_atombios_get_asic_ss_info integer underflow
 * (userspace replica of sys/dev/drm/radeon/radeon_atombios.c:1536/1555/1577)
 *
 * size is uint16_t (from atom_parse_data_header, = BIOS usStructureSize).
 * num_indices is int. The kernel computes:
 *     num_indices = (size - sizeof(ATOM_COMMON_TABLE_HEADER)) / sizeof(V2);
 * sizeof(HEADER)=4, sizeof(V2)=12. The subtraction is done in size_t (unsigned)
 * because sizeof yields size_t: if size<4, size_t wraps to ~0 ... and the
 * division yields 0x5555555555555555, truncated to int = 1431655765. The loop
 * then iterates 1.4 billion times reading 12 bytes per iteration past the BIOS
 * table into kernel memory -> panic on the first unmapped page, or a massive
 * kernel-memory read (info leak) until it faults. This harness reproduces the
 * arithmetic exactly (frev 2 path; frev 3 identical with sizeof(V3)=12).
 *
 * Build: cc -O2 -Wall -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

typedef uint16_t USHORT;
typedef uint8_t  UCHAR;
typedef struct { USHORT usStructureSize; UCHAR a; UCHAR b; } ATOM_COMMON_TABLE_HEADER;
typedef struct { UCHAR x[12]; } ATOM_ASIC_SS_ASSIGNMENT_V2;

static int compute_num_indices(uint16_t size, size_t elem_sz)
{
	int num_indices;
	/* exact kernel expression */
	num_indices = (size - sizeof(ATOM_COMMON_TABLE_HEADER)) / elem_sz;
	return num_indices;
}

int main(void)
{
	printf("== DF-1199 radeon_atombios_get_asic_ss_info underflow harness ==\n");
	printf("sizeof(ATOM_COMMON_TABLE_HEADER)=%zu sizeof(V2)=%zu\n\n",
	       sizeof(ATOM_COMMON_TABLE_HEADER), sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2));

	uint16_t sizes[] = { 0, 1, 2, 3, 4, 16, 40 };
	int bug = 0;
	for (int k = 0; k < (int)(sizeof(sizes)/sizeof(sizes[0])); k++) {
		int ni = compute_num_indices(sizes[k], sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2));
		int dangerous = (sizes[k] < sizeof(ATOM_COMMON_TABLE_HEADER)) ||
				ni < 0 || ni > 1000000;
		printf("size=%5u -> num_indices=%d%s\n", sizes[k], ni,
		       dangerous ? "   <-- UNDERFLOW (huge loop -> OOB read)" : "");
		if (dangerous) bug = 1;
	}

	printf("\n");
	/* Demonstrate the exact wrap value the finding cites (size=2, frev==2). */
	int ni = compute_num_indices(2, 12);
	printf("Finding's cited value: size=2 -> num_indices=%d (== 1431655765 ? %s)\n",
	       ni, ni == 1431655765 ? "YES" : "no");

	/* Show the intermediate size_t arithmetic explicitly. */
	uint16_t size = 2;
	size_t sub = (size_t)size - (size_t)sizeof(ATOM_COMMON_TABLE_HEADER);
	size_t div = sub / sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2);
	printf("size_t trace: (%u - %zu) = 0x%016zx (%zu);  /12 = 0x%016zx; "
	       "trunc->int = %d\n",
	       size, sizeof(ATOM_COMMON_TABLE_HEADER), sub, sub, div, ni);

	printf("\n[BUG %s] malicious VBIOS with size<4 drives a %d-iteration loop "
	       "reading 12B/iter past the BIOS table.\n",
	       bug ? "REPRODUCED" : "not-reproduced", ni);
	printf("Fixed kernel checks `size >= sizeof(ATOM_COMMON_TABLE_HEADER)` before "
	       "the subtraction and bails.\n");
	return bug ? 0 : 1;
}