DF-3740 / harness_mclk.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | /* * DF-3740 harness: radeon_atom_get_mclk_range_table() overflow via uncapped num_entries * fill of mclk[MAX_AC_TIMING_ENTRIES=16] โ destination is a KERNEL STACK LOCAL * (cypress_get_memory_clock_range_table(), cypress_dpm.c:1075-1089). * * Verbatim arithmetic from sys/dev/drm/radeon/radeon_atombios.c:3925-3946 (frev1/crev4): * mclk_range_table->num_entries = (u8)((usModuleSize - offsetof(V4, asMemTiming)) / mts); * for (i = 0; i < num_entries; i++) mclk[i] = format->ulClkRange; * NO bound of num_entries (u8 -> 0..255) against mclk[16] exists. * * Modes: * default (heap model) โ counts the OOB writes precisely without crashing; * "stack" (argv[1]) โ performs the overflow on the harness's own stack frame * exactly like the kernel would -> crashes (SIGBUS/SIGSEGV). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stddef.h> typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef struct { u32 ulClkRange; u16 usMRS, usEMRS; u8 t[14]; union { struct { u8 a, b; }; u16 u; }; } ATOM_MEMORY_TIMING_FORMAT; /* atombios.h:6441-6469 == 24 bytes */ typedef struct { u32 ulChannelMapCfg; u16 usModuleSize, usPrivateReserved, usReserved; u8 ucExtMemoryID, ucMemoryType, ucChannelNum, ucChannelWidth, ucDensity, ucFlag, ucMisc, ucVREFI, ucNPL_RT, ucPreamble, ucMemorySize, ucReserved[3]; u16 usEMRS2Value, usEMRS3Value; u8 ucMemoryVenderID, ucRefreshRateFactor, ucReserved2[2]; ATOM_MEMORY_TIMING_FORMAT asMemTiming[5]; /* atombios.h:6595-6627 */ } ATOM_VRAM_MODULE_V4; #define MAX_AC_TIMING_ENTRIES 16 /* radeon_mode.h:642 */ struct atom_memory_clock_range_table { /* radeon_mode.h:644-649 */ u8 num_entries; u8 rsv[3]; u32 mclk[MAX_AC_TIMING_ENTRIES]; }; int main(int argc, char **argv) { int stack_mode = (argc > 1 && strcmp(argv[1], "stack") == 0); printf("sizeof(ATOM_MEMORY_TIMING_FORMAT)=%zu offsetof(asMemTiming)=%zu\n", sizeof(ATOM_MEMORY_TIMING_FORMAT), offsetof(ATOM_VRAM_MODULE_V4, asMemTiming)); if (sizeof(ATOM_MEMORY_TIMING_FORMAT) != 24 || offsetof(ATOM_VRAM_MODULE_V4, asMemTiming) != 32) { fprintf(stderr, "layout replica mismatch\n"); return 2; } /* VBIOS: vram_module->usModuleSize = 0xFFF0 */ u16 usModuleSize = 0xFFF0; size_t mem_timing_size = sizeof(ATOM_MEMORY_TIMING_FORMAT); /* gddr5=false */ unsigned long oob = 0; if (stack_mode) { /* perform the overflow on this frame, exactly as the kernel would */ u32 bios_fmt[8192]; for (unsigned i = 0; i < 8192; i++) bios_fmt[i] = 0x41410000u + i; ATOM_VRAM_MODULE_V4 *vram_module = (ATOM_VRAM_MODULE_V4 *)bios_fmt; vram_module->usModuleSize = usModuleSize; struct atom_memory_clock_range_table range_table; memset(&range_table, 0, sizeof(range_table)); printf("stack mode: overflowing a %zu-byte stack struct...\n", sizeof(range_table)); range_table.num_entries = (u8)((usModuleSize - offsetof(ATOM_VRAM_MODULE_V4, asMemTiming)) / mem_timing_size); ATOM_MEMORY_TIMING_FORMAT *format = &vram_module->asMemTiming[0]; for (u8 i = 0; i < range_table.num_entries; i++) { range_table.mclk[i] = format->ulClkRange; /* OOB for i >= 16 */ format = (ATOM_MEMORY_TIMING_FORMAT *)((u8 *)format + mem_timing_size); } printf("unreachable: survived %u writes\n", range_table.num_entries); /* not reached */ return 1; } /* heap model: oversized destination so we can COUNT what lands OOB */ u32 model[4 + 1 + 256 * 4]; /* pre[] hdr+mclk[16] post[] */ u32 *mclk = model + 4; /* mclk[0..15] valid */ u32 bios_fmt[8192]; for (unsigned i = 0; i < 8192; i++) bios_fmt[i] = 0x41410000u + i; ATOM_VRAM_MODULE_V4 *vram_module = (ATOM_VRAM_MODULE_V4 *)bios_fmt; vram_module->usModuleSize = usModuleSize; /* === verbatim kernel arithmetic, radeon_atombios.c:3938-3946 === */ u8 num_entries = (u8)((usModuleSize - offsetof(ATOM_VRAM_MODULE_V4, asMemTiming)) / mem_timing_size); ATOM_MEMORY_TIMING_FORMAT *format = &vram_module->asMemTiming[0]; for (u8 i = 0; i < num_entries; i++) { mclk[i] = format->ulClkRange; /* OOB write when i >= 16 */ if (i >= MAX_AC_TIMING_ENTRIES) oob++; format = (ATOM_MEMORY_TIMING_FORMAT *)((u8 *)format + mem_timing_size); } /* === end verbatim === */ printf("usModuleSize=0x%04x -> num_entries=%u ; mclk[] capacity=%d\n", usModuleSize, num_entries, MAX_AC_TIMING_ENTRIES); printf("first OOB value (VBIOS u32 0x%08x) lands at struct offset %d โ i.e. exactly " "at the end of the %zu-byte struct; total overflow %lu bytes past it\n", mclk[MAX_AC_TIMING_ENTRIES], 4 + 4 * MAX_AC_TIMING_ENTRIES, sizeof(struct atom_memory_clock_range_table), oob * 4UL); printf("OOB u32 writes past mclk[16]: %lu (%lu bytes) of attacker-chosen VBIOS data " "onto the caller kernel stack frame\n", oob, oob * 4UL); printf("note: run `./mclk_bin stack` to perform the overflow on the harness's own " "stack (crashes, as the kernel would)\n"); if (oob == 0) { printf("FAIL: no overflow\n"); return 1; } printf("REPRODUCED (harness): mclk[16] overflow via uncapped num_entries\n"); return 0; } |