DF-1138 / harness.c
/* * DF-1138 harness: radeon/si_dpm.c si_parse_power_table VCE-state fill-in * -> OOB read when clk_idx >= ucNumEntries (si_dpm.c:6883-6894) * * Userspace logic harness. The buggy main loop at si_dpm.c:6866 guards * `clock_array_index >= ucNumEntries`, but the VCE-state fill-in loop at * 6883 does NOT -- so a crafted VBIOS where state_entry->ucClockInfoIndex * (passed through as rdev->pm.dpm.vce_states[i].clk_idx) is large relative * to ucNumEntries causes a ~1 MB OOB read of the BIOS mapping. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> #define RADEON_MAX_VCE_LEVELS 6 struct ClockInfoArray { uint8_t ucNumEntries; /* from BIOS */ uint8_t ucEntrySize; /* sizeof(union pplib_clock_info) */ uint8_t clockInfo[]; /* flexible array */ }; struct radeon_vce_state { uint16_t sclk, mclk; uint8_t clk_idx; /* from BIOS via r600_dpm.c:1128 (ucClockInfoIndex & 0x3f) */ }; /* Mirror of buggy main loop (si_dpm.c:6864-6876) -- HAS guard */ static int main_loop_read(struct ClockInfoArray *arr, uint8_t clk_idx) { if (clk_idx >= arr->ucNumEntries) return -1; /* guarded */ uint8_t *p = &arr->clockInfo[0] + (clk_idx * arr->ucEntrySize); return *p; } /* Mirror of buggy VCE loop (si_dpm.c:6883-6894) -- NO guard */ static int vce_loop_read(struct ClockInfoArray *arr, uint8_t clk_idx) { uint8_t *p = &arr->clockInfo[clk_idx * arr->ucEntrySize]; return *p; /* unguarded -- OOB if clk_idx >= ucNumEntries */ } /* Fixed VCE loop -- adds the missing guard */ static int vce_loop_read_fixed(struct ClockInfoArray *arr, uint8_t clk_idx) { if (clk_idx >= arr->ucNumEntries) return -1; uint8_t *p = &arr->clockInfo[clk_idx * arr->ucEntrySize]; return *p; } int main(void) { /* Simulate BIOS-mapped clockInfo array: 5 entries of 16 bytes each. * In the real driver this points into the BIOS mapping (mapped via ioremap). */ enum { N = 5, ESZ = 16 }; uint8_t buf[N * ESZ + 4096]; /* +slack to absorb OOB without page-faulting userspace */ memset(buf, 0xAB, sizeof(buf)); /* fill with marker bytes */ struct ClockInfoArray *arr = (struct ClockInfoArray *)buf; arr->ucNumEntries = N; arr->ucEntrySize = ESZ; /* Mark in-bounds region with 0x11 so we can see when we go OOB */ memset(arr->clockInfo, 0x11, N * ESZ); /* Crafted VBIOS produces vce_state.clk_idx == 60 (0xff & 0x3f == 0x3f). */ struct radeon_vce_state crafted = { .clk_idx = 0x3f }; printf("=== DF-1138 harness: crafted VBIOS with vce_state.clk_idx=0x%02x ===\n", crafted.clk_idx); printf(" clock_info_array: ucNumEntries=%u ucEntrySize=%u (in-bounds=%u..%u)\n\n", arr->ucNumEntries, arr->ucEntrySize, 0, (unsigned)(N * ESZ)); printf("[A] Main-loop guard (si_dpm.c:6866) on same clk_idx:\n"); printf(" buggy code path returns -1 (out-of-range rejected)\n\n"); int rc = main_loop_read(arr, crafted.clk_idx); printf(" rc=%d (guarded)\n\n", rc); printf("[B] VCE-loop WITHOUT guard (si_dpm.c:6885-6887) -- the bug:\n"); int byte = vce_loop_read(arr, crafted.clk_idx); unsigned off = crafted.clk_idx * arr->ucEntrySize; printf(" read byte=0x%02x at offset clockInfo[%u] (=%u bytes into clockInfo)\n", (uint8_t)byte, off, off); printf(" >>> In kernel: OOB read of BIOS mapping by %u bytes (0x%x - 0x%x = %u) <<<\n", off - N * ESZ, off, N * ESZ, off - N * ESZ); printf(" >>> Real-world: a malicious VBIOS can read up to 0x3f * %u = %u bytes OOB <<<\n\n", ESZ, 0x3f * ESZ); printf("[C] VCE-loop WITH fix.diff guard:\n"); rc = vce_loop_read_fixed(arr, crafted.clk_idx); printf(" rc=%d (rejected, no OOB read)\n\n", rc); return 0; } |