DF-3887 / vid_mapping_oob_write.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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | /* * DF-3887 harness — sumo_construct_vid_mapping_table OOB heap write. * * Mirrors sys/dev/drm/amd/amdgpu/kv_dpm.c:159-191 with 1:1 struct layouts * (kv_dpm.h + atombios.h ATOM_AVAILABLE_SCLK_LIST), driven by a crafted * VBIOS sAvail_SCLK[] where usVoltageIndex (USHORT, 0..65535) >= 4. * * Modes: * ./t accounting: runs the verbatim loop against an mmap'd shadow * kv_power_info and reports exactly which byte offsets outside * the 16-byte entries[4] array were overwritten. * ./t crash executes the verbatim write with usVoltageIndex=65535 against * a single-page mapping -> wild write at +262140 bytes -> SIGSEGV * (kernel equivalent: page fault in kernel mode -> panic). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stddef.h> #include <sys/mman.h> /* ---- 1:1 kernel struct layouts (kv_dpm.h) ---- */ #define SUMO_MAX_HARDWARE_POWERLEVELS 5 #define SUMO_MAX_NUMBER_VOLTAGES 4 struct sumo_vid_mapping_entry { uint16_t vid_2bit; uint16_t vid_7bit; }; struct sumo_vid_mapping_table { uint32_t num_entries; struct sumo_vid_mapping_entry entries[SUMO_MAX_NUMBER_VOLTAGES]; }; /* ---- 1:1 VBIOS struct (atombios.h:5705) ---- */ typedef struct _ATOM_AVAILABLE_SCLK_LIST { uint32_t ulSupportedSCLK; /* ULONG */ uint16_t usVoltageIndex; /* USHORT */ uint16_t usVoltageID; /* USHORT */ } ATOM_AVAILABLE_SCLK_LIST; /* ---- kernel kv_power_info shell: only the prefix that matters, then a * tail region standing in for the rest of the ~1.3KB heap object and * the heap beyond it. */ struct kv_power_info_prefix { uint32_t at[SUMO_MAX_HARDWARE_POWERLEVELS]; /* 20 */ uint32_t voltage_drop_t; /* 4 */ struct { uint32_t bootup_uma_clk, bootup_sclk, dentist_vco_freq, nb_dpm_enable; uint32_t nbp_memory_clock[4], nbp_n_clock[4]; uint16_t bootup_nb_voltage_index; uint8_t htc_tmp_lmt, htc_hyst_lmt; uint32_t num_max_dpm_entries; struct { uint32_t sclk_frequency; uint16_t vid_2bit, rsv; } sv_entries[SUMO_MAX_HARDWARE_POWERLEVELS]; struct sumo_vid_mapping_table vid_mapping_table; /* <-- victim */ } sys_info; }; /* ---- verbatim kv_dpm.c:159-191 (loop body unchanged) ---- */ static void sumo_construct_vid_mapping_table(struct sumo_vid_mapping_table *vid_mapping_table, ATOM_AVAILABLE_SCLK_LIST *table) { uint32_t i, j; for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) { if (table[i].ulSupportedSCLK != 0) { vid_mapping_table->entries[table[i].usVoltageIndex].vid_7bit = table[i].usVoltageID; vid_mapping_table->entries[table[i].usVoltageIndex].vid_2bit = table[i].usVoltageIndex; } } for (i = 0; i < SUMO_MAX_NUMBER_VOLTAGES; i++) { if (vid_mapping_table->entries[i].vid_7bit == 0) { for (j = i + 1; j < SUMO_MAX_NUMBER_VOLTAGES; j++) { if (vid_mapping_table->entries[j].vid_7bit != 0) { vid_mapping_table->entries[i] = vid_mapping_table->entries[j]; vid_mapping_table->entries[j].vid_7bit = 0; break; } } if (j == SUMO_MAX_NUMBER_VOLTAGES) break; } } vid_mapping_table->num_entries = i; } int main(int argc, char **argv) { int crash = (argc > 1 && strcmp(argv[1], "crash") == 0); /* Crafted VBIOS: strictly-increasing SCLKs; voltage index out of range. * Index 300 -> byte offset 300*4 = 1200 past entries[]: far outside the * 16-byte array (and past the ~1.3KB kv_power_info heap object the * kernel kzalloc'd), still a legal-looking USHORT to the parser. */ ATOM_AVAILABLE_SCLK_LIST vbios[SUMO_MAX_HARDWARE_POWERLEVELS] = { { .ulSupportedSCLK = 10000, .usVoltageIndex = 0, .usVoltageID = 0x11 }, { .ulSupportedSCLK = 20000, .usVoltageIndex = 1, .usVoltageID = 0x22 }, { .ulSupportedSCLK = 30000, .usVoltageIndex = 300, .usVoltageID = 0xBEEF }, { .ulSupportedSCLK = 40000, .usVoltageIndex = 301, .usVoltageID = 0xCAFE }, { .ulSupportedSCLK = 50000, .usVoltageIndex = 302, .usVoltageID = 0x1234 }, }; if (crash) { /* verbatim kernel access against a minimal mapping: usVoltageIndex * 65535 -> write at entries + 65535*4 = +262140 -> unmapped */ vbios[2].usVoltageIndex = 65535; vbios[3].ulSupportedSCLK = 0; /* keep it to the single wild pair */ vbios[4].ulSupportedSCLK = 0; uint8_t *page = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (page == MAP_FAILED) { perror("mmap"); return 1; } struct kv_power_info_prefix *pi = (void *)page; printf("crash mode: entries at %p, writing vid_7bit at +65535*4 = %ld bytes past it\n", (void *)&pi->sys_info.vid_mapping_table.entries, (long)(65535L * 4)); fflush(stdout); sumo_construct_vid_mapping_table(&pi->sys_info.vid_mapping_table, vbios); printf("NOT REACHED\n"); return 0; } /* accounting mode: big shadow region, observe the corruption footprint */ size_t region = 512 * 1024; uint8_t *shadow = mmap(NULL, region, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (shadow == MAP_FAILED) { perror("mmap"); return 1; } memset(shadow, 0, region); struct kv_power_info_prefix *pi = (void *)shadow; struct sumo_vid_mapping_table *vt = &pi->sys_info.vid_mapping_table; size_t entries_off = (uint8_t *)vt->entries - shadow; printf("entries[] at shadow+%zu, size %zu bytes (4 slots x 4 bytes)\n", entries_off, sizeof(vt->entries)); sumo_construct_vid_mapping_table(vt, vbios); /* find every byte that changed outside the entries[] array */ int nbad = 0; for (size_t k = 0; k < region; k += 2) { uint16_t v; memcpy(&v, shadow + k, 2); if (v == 0) continue; if (k < entries_off || k >= entries_off + sizeof(vt->entries)) { printf("OOB WRITE: shadow+%zu (entries+%zd) = 0x%04x\n", k, (ptrdiff_t)(k - entries_off), v); nbad++; } else { printf("in-bounds: entries+%zd = 0x%04x\n", (ptrdiff_t)(k - entries_off), v); } } printf("num_entries after construct = %u\n", vt->num_entries); printf("%d OOB 16-bit writes landed OUTSIDE the %zu-byte entries array\n", nbad, sizeof(vt->entries)); printf("kernel reach: kv_dpm_init -> kv_parse_sys_info_table (kv_dpm.c:2613) at every KV boot\n"); return 0; } |