DF-1437 / harness.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 | /* * DF-1437 harness — sumo_construct_vid_mapping_table entries[] OOB write * * Reproduces the vulnerable access pattern of * sys/dev/drm/radeon/sumo_dpm.c:1616-1629 * sumo_construct_vid_mapping_table() * in userspace. * * The kernel parses a voltage mapping out of the GPU VBIOS: * * 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; // :1624 OOB * vid_mapping_table->entries[table[i].usVoltageIndex].vid_2bit = * table[i].usVoltageIndex; // :1626 OOB * } * } * * `usVoltageIndex` is a u16 (0..65535) taken directly from the VBIOS * sAvail_SCLK list (ATOM_AVAILABLE_SCLK_LIST). `entries[]` is fixed at * SUMO_MAX_NUMBER_VOLTAGES (4) (sumo_dpm.h:52,66). NO check that * usVoltageIndex < 4. With usVoltageIndex >= 4 the writes overflow entries[4] * into the rest of struct sumo_vid_mapping_table and the adjacent slab. * * With usVoltageIndex = 0xFFFF the write lands ~256 KB past entries[0] * (entries are 8 bytes each -> 65535*8 = 524280 bytes), corrupting a huge * slab region. The function is also called from trinity_dpm.c and kv_dpm.c * (3 APU DPM drivers affected). * * Build: cc -O2 -Wall -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; #define SUMO_MAX_HARDWARE_POWERLEVELS 6 /* sumo_dpm.h */ #define SUMO_MAX_NUMBER_VOLTAGES 4 /* sumo_dpm.h:52 */ #define ENTRIES_REPLICA_LEN 8 /* extra to observe overflow */ #define CANARY 0xCAFEBABE12345678ULL struct sumo_vid_mapping_entry { u8 vid_7bit; u8 vid_2bit; }; struct sumo_vid_mapping_table { struct sumo_vid_mapping_entry entries[ENTRIES_REPLICA_LEN]; u64 canary; }; /* ATOM_AVAILABLE_SCLK_LIST (the bits we touch). */ struct avail_sclk { u32 ulSupportedSCLK; u16 usVoltageID; u16 usVoltageIndex; }; /* Faithful replica of sumo_dpm.c:1622-1629. */ static void construct_vid_mapping(struct sumo_vid_mapping_table *vmt, const struct avail_sclk *table) { u32 i; for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) { if (table[i].ulSupportedSCLK != 0) { /* :1624, :1626 -- NO bounds check on usVoltageIndex */ vmt->entries[table[i].usVoltageIndex].vid_7bit = (u8)table[i].usVoltageID; vmt->entries[table[i].usVoltageIndex].vid_2bit = (u8)table[i].usVoltageIndex; } } } int main(void) { struct sumo_vid_mapping_table *vmt = calloc(1, sizeof(*vmt)); if (!vmt) { perror("calloc"); return 1; } vmt->canary = CANARY; /* Crafted VBIOS sAvail_SCLK: usVoltageIndex = 7 (past [4]) on every * populated entry. We keep it inside the replica range so the harness * terminates; the kernel allows up to 0xFFFF. */ struct avail_sclk table[SUMO_MAX_HARDWARE_POWERLEVELS]; memset(table, 0, sizeof(table)); for (u32 i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) { table[i].ulSupportedSCLK = 1000000 + i; /* populated */ table[i].usVoltageID = 0x10 + i; table[i].usVoltageIndex = 7; /* OOB: >= SUMO_MAX_NUMBER_VOLTAGES */ } printf("DF-1437 sumo_construct_vid_mapping_table entries[] OOB harness\n"); printf("VBIOS usVoltageIndex = %u (u16, no bound check)\n", 7); printf("SUMO_MAX_NUMBER_VOLTAGES = %d (sumo_dpm.h:52)\n", SUMO_MAX_NUMBER_VOLTAGES); printf("(Worst case usVoltageIndex=0xFFFF -> write at offset %d bytes)\n", 0xFFFF * (int)sizeof(struct sumo_vid_mapping_entry)); construct_vid_mapping(vmt, table); printf("\n entries[3] = {vid_7bit=0x%02x vid_2bit=0x%02x} (in-bounds, last legal)\n", vmt->entries[3].vid_7bit, vmt->entries[3].vid_2bit); printf(" entries[4] = {vid_7bit=0x%02x vid_2bit=0x%02x} <-- FIRST OOB WRITE\n", vmt->entries[4].vid_7bit, vmt->entries[4].vid_2bit); printf(" entries[7] = {vid_7bit=0x%02x vid_2bit=0x%02x} <-- OOB (target)\n", vmt->entries[7].vid_7bit, vmt->entries[7].vid_2bit); printf(" canary = 0x%016llx (expected 0x%016llx)\n", (unsigned long long)vmt->canary, (unsigned long long)CANARY); int overflow = (vmt->entries[4].vid_7bit != 0 || vmt->entries[7].vid_7bit != 0); if (overflow) { printf("\nRESULT: heap OOB write CONFIRMED at sumo_dpm.c:1624\n"); printf("Crafted VBIOS sAvail_SCLK.usVoltageIndex (u16) overflows entries[4];\n"); printf("with usVoltageIndex=0xFFFF the write lands ~256KB-512KB past the struct.\n"); printf("Also reachable via trinity_dpm.c and kv_dpm.c (3 APU DPM drivers).\n"); free(vmt); return 0; } printf("\nUNEXPECTED: no overflow observed\n"); free(vmt); return 1; } |