DF-1251 / harness.c
/* * DF-1251 harness โ unbounded COMBIOS table-walking loops over-read rdev->bios * and feed heap-sourced values into WREG32 (GPU MMIO writes). * * sys/dev/drm/radeon/radeon_combios.c: * combios_parse_mmio_table (:3005): while (RBIOS16(offset)) { ... * addr = RBIOS16(offset) & 0x1fff; offset += 2; * val = RBIOS32(offset); offset += 4; WREG32(addr, val); (:3021) * combios_parse_pll_table (:3084): while (RBIOS8(offset)) { ... WREG32_PLL(addr,val); (:3100) * * where (sys/dev/drm/radeon/radeon.h:2686-2688): * #define RBIOS8(i) (rdev->bios[i]) * #define RBIOS16(i) (RBIOS8(i) | (RBIOS8((i)+1) << 8)) * #define RBIOS32(i) ((RBIOS16(i)) | (RBIOS16((i)+2) << 16)) * * The loops advance `offset` purely from BIOS content and have NO upper bound * against the BIOS allocation. A malicious ROM whose table never contains a * zero terminator within the BIOS allocation drives RBIOS16/RBIOS32 to read * PAST rdev->bios into adjacent kernel heap, and those heap-sourced addr/val * pairs are written to GPU MMIO via WREG32 โ arbitrary GPU register writes. * * Kernel trigger requires an AMD/ATI radeon GPU with a malicious Video BIOS * (e.g. hotplug PCIe card with crafted ROM) โ absent from this guest. This * harness replicates the mmio-table walk over a small BIOS slab whose table * has no in-bounds terminator, proving the loop reads past the allocation. */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> static uint8_t *g_bios; static size_t g_bios_len; static unsigned g_oob_reads = 0; static unsigned g_mmio_writes = 0; /* Verbatim radeon.h:2686-2688 semantics, with OOB accounting. */ static uint8_t rbios8(uint16_t i) { if ((size_t)i >= g_bios_len) g_oob_reads++; return g_bios[i]; } static uint16_t rbios16(uint16_t i) { return rbios8(i) | (rbios8((uint16_t)(i+1)) << 8); } static uint32_t rbios32(uint16_t i) { return rbios16(i) | (rbios16((uint16_t)(i+2)) << 16); } static void wreg32(uint32_t addr, uint32_t val) { g_mmio_writes++; (void)addr; (void)val; } /* Verbatim structure of combios_parse_mmio_table (radeon_combios.c:3005-3030). */ static void combios_parse_mmio_table(uint16_t offset) { if (!offset) return; while (rbios16(offset)) { /* :3010 โ NO bound on offset */ uint16_t cmd = (rbios16(offset) & 0xe000) >> 13; uint32_t addr = rbios16(offset) & 0x1fff; offset += 2; switch (cmd) { case 0: { /* :3018-3022 */ uint32_t val = rbios32(offset); offset += 4; wreg32(addr, val); break; } default: /* other cmds also advance + write */ offset += 4; wreg32(addr, 0); break; } /* safety for the harness only โ kernel has none */ if (offset > g_bios_len * 4) { printf("[harness cap hit]\n"); break; } } } int main(void) { g_bios_len = 512; /* small BIOS slab */ g_bios = calloc(1, g_bios_len); /* Build a malicious mmio table starting at offset 0x40 that never * produces a zero RBIOS16 terminator inside the allocation: every word * is non-zero. */ uint16_t table = 0x40; for (size_t i = table; i + 1 < g_bios_len; i += 2) { g_bios[i] = 0x01; /* cmd=0, addr=1, non-zero word */ g_bios[i + 1] = 0x00; } /* Ensure the loop's terminating condition (a zero word) does NOT appear * inside the allocation. (calloc zeroed the tail; fill it.) */ for (size_t i = 0; i < g_bios_len; i++) if (g_bios[i] == 0) g_bios[i] = 0x55; combios_parse_mmio_table(table); printf("DF-1251 combios_parse_mmio_table harness\n"); printf("BIOS allocation = %zu bytes; table starts at 0x%x (no in-bounds terminator)\n", g_bios_len, table); printf("RBIOS8/16/32 reads past BIOS allocation (OOB): %u\n", g_oob_reads); printf("WREG32 GPU-MMIO writes with heap-sourced addr/val: %u\n", g_mmio_writes); if (g_oob_reads > 0) { printf("\nPRIMITIVE CONFIRMED: unbounded loop read %u words past rdev->bios into heap,\n", g_oob_reads); printf("and wrote %u (addr,val) pairs to GPU MMIO (WREG32). Bug is REAL.\n", g_mmio_writes); free(g_bios); return 0; } printf("\nUNEXPECTED: no OOB\n"); free(g_bios); return 1; } |