DF-1127 / harness.c
/* * DF-1127 userspace harness — si_parse_power_table BIOS-index OOB read * primitive. * * The kernel bug (sys/dev/drm/amd/amdgpu/si_dpm.c:7253-7297): * - nonClockInfoIndex read from BIOS, used unvalidated to index * non_clock_info_array->nonClockInfo[] at :7253-7255 (NO check vs * ucNumEntries). * - VCE clk_idx at :7288-7290 also unvalidated. * - The sibling clock loop at :7267-7275 DOES check * `if (clock_array_index >= clock_info_array->ucNumEntries) continue;` * proving the omission is an oversight, not policy. * * Trigger: crafted/malformed VBIOS (VFIO GPU passthrough romfile, corrupt * EEPROM) -> OOB read from BIOS mapping -> info leak via sysfs pp_dpm_sclk or * panic. * * Reachability: requires AMD GPU (Tahiti/si) with si_dpm. Not in GENERIC, no * AMD GPU on this audit guest. * * Build: cc -O2 -o harness harness.c * Run: ./harness * Expected: demonstrates the OOB read past the BIOS array. */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <stddef.h> #define NONCLOCK_INFO_ENTRIES 4 #define ENTRY_SIZE 16 struct _NonClockInfoArray { uint8_t ucNumEntries; uint8_t ucEntrySize; /* entries follow */ uint8_t nonClockInfo[NONCLOCK_INFO_ENTRIES][ENTRY_SIZE]; }; /* Simulate a BIOS mapping: a small region with the valid array, then unrelated * bytes beyond it (in kernel these are whatever follows in the BIOS mapping). */ static uint8_t bios_mapping[256]; static void dump_array(struct _NonClockInfoArray *a) { printf("non_clock_info_array @ %p, ucNumEntries=%u, ucEntrySize=%u\n", (void *)a, a->ucNumEntries, a->ucEntrySize); } int main(void) { memset(bios_mapping, 0xAA, sizeof(bios_mapping)); /* poison */ struct _NonClockInfoArray *arr = (struct _NonClockInfoArray *)bios_mapping; arr->ucNumEntries = NONCLOCK_INFO_ENTRIES; arr->ucEntrySize = ENTRY_SIZE; for (int i = 0; i < NONCLOCK_INFO_ENTRIES; i++) memset(arr->nonClockInfo[i], 'A' + i, ENTRY_SIZE); dump_array(arr); /* crafted BIOS supplies a bogus nonClockInfoIndex */ uint8_t bad_index = 42; /* >> ucNumEntries (4) */ printf("\n[buggy path] si_dpm.c:7253-7255 -- NO bounds check\n"); printf(" nonClockInfoIndex = %u (ucNumEntries = %u)\n", bad_index, arr->ucNumEntries); /* the exact indexing the kernel does */ uint8_t *non_clock_info = (uint8_t *) &arr->nonClockInfo[bad_index]; ptrdiff_t off = non_clock_info - bios_mapping; printf(" accesses bios_mapping[%td] (mapping size = %zu)\n", off, sizeof(bios_mapping)); if ((size_t)off >= sizeof(bios_mapping)) { printf(" -> READ PAST MAPPING BOUNDARY -> kernel OOB read\n"); } else if (bad_index >= arr->ucNumEntries) { printf(" -> READ PAST nonClockInfo[%u] (valid 0..%u) -> OOB within mapping\n", arr->ucNumEntries - 1, arr->ucNumEntries - 1); printf(" bytes read: "); for (int i = 0; i < 8; i++) printf("%02x ", non_clock_info[i]); printf("...\n"); } printf("\n[fixed path] sibling clock loop si_dpm.c:7269 DOES check:\n"); printf(" if (clock_array_index >= clock_info_array->ucNumEntries) continue;\n"); printf(" -> the fix is to apply the same check to nonClockInfoIndex.\n"); return 0; } |