DF-4003 / trigger_analysis.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 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | /* * DF-4001..DF-4006 guest demonstrator โ fiji_smumgr.c SMC-table population walks. * * Reproduces, in userspace, the exact index arithmetic of: * fiji_populate_all_graphic_levels (fiji_smumgr.c:1026-1093) -> GraphicsLevel[8] * fiji_populate_all_memory_levels (fiji_smumgr.c:1238-1272) -> MemoryLevel[4] * fiji_program_memory_timing_parameters (fiji_smumgr.c:1537-1546) -> STACK MCArbDramTimingTable entries[8][4] * fiji_populate_smc_vce_level (fiji_smumgr.c:1436-1457) -> VceLevel[8] * fiji_populate_smc_uvd_level (fiji_smumgr.c:1572-1601) -> UvdLevel[8] * fiji_populate_smc_acp_level (fiji_smumgr.c:1475-1494) -> AcpLevel[8] * fiji_populate_cac_table (fiji_smumgr.c:772-779) -> BapmVddcVid{Lo,Hi}Sidd[16] + vddc_voltage_table.entries[32] read * fiji_populate_clock_stretcher_data_table (fiji_smumgr.c:1706-1719,1792-1810) -> Sclk_voltageOffset[8] * fiji_get_dependency_volt_by_clk (fiji_smumgr.c:413) -> entries[count].mvdd one-entry OOB heap read * * Struct sizes/offsets are the kernel's own: smu73.h / smu73_discrete.h are * compiled in verbatim (#pragma pack(1), self-contained). * * Counts provenance (all VBIOS/PowerPlay-table controlled, unclamped): * process_pptables_v1_0.c: sclk/mclk dep count = ucNumEntries (u8, <=255) * (get_sclk_voltage_dependency_table :436, get_mclk_... :389, * get_mm_clock_voltage_table :708, get_vddc_lookup_table :176-178), * smu7_hwmgr.c:783-810 dedups sclk/mclk into dpm_table.*.count (no clamp vs 8), * fiji pinned on this path: hwmgr.c:86 default PP_TABLE_V1 (only CI/TOPAZ -> V0). */ #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <setjmp.h> #include <stddef.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include "smu73.h" #include "smu73_discrete.h" static int waitpid_helper(int *st) { while (wait(st) < 0) {} return 0; } #define REDZONE 4096 static unsigned long oob_writes, oob_bytes; /* heap object with guard page-ish redzone: writes past obj[n] land in the redzone */ static void *rz_alloc(size_t sz, unsigned char **obj, const char **rz) { unsigned char *base = malloc(sz + REDZONE); memset(base, 0xAA, sz + REDZONE); /* 0xAA pattern everywhere */ memset(base, 0x00, sz); /* object zeroed, redzone 0xAA */ *obj = base; *rz = (const char *)(base + sz); return base; } static long rz_scan(const unsigned char *base, size_t sz) { long n = 0; for (size_t k = sz; k < sz + REDZONE; k++) if (base[k] != 0xAA) n++; return n; } static void hdr(const char *s) { printf("\n=== %s ===\n", s); } int main(void) { /* ---- attacker-chosen VBIOS counts (each a single PowerPlay-table byte) ---- */ const uint32_t sclk_count = 12; /* > SMU73_MAX_LEVELS_GRAPHICS (8) */ const uint32_t mclk_count = 6; /* > SMU73_MAX_LEVELS_MEMORY (4) */ const uint32_t mm_count = 20; /* > SMU73_MAX_LEVELS_{VCE,UVD,ACP} (8) */ const uint32_t vddc_lookup_count = 40; /* > SMU73_MAX_LEVELS_VDDC (16), > PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES (32) */ const uint32_t dep_sclk_raw = 12; /* raw vdd_dep_on_sclk->count */ printf("fiji_smumgr.c demonstrator โ kernel struct geometry (kernel headers):\n"); printf(" sizeof(SMU73_Discrete_DpmTable)=%zu GraphicsLevel@+%zu MemoryLevel@+%zu Sclk_voltageOffset@+%zu\n", sizeof(SMU73_Discrete_DpmTable), offsetof(SMU73_Discrete_DpmTable, GraphicsLevel), offsetof(SMU73_Discrete_DpmTable, MemoryLevel), offsetof(SMU73_Discrete_DpmTable, Sclk_voltageOffset)); printf(" GraphicsLevel=%zuB MemoryLevel=%zuB Uvd=%zuB Ext=%zuB ArbEntry=%zuB ArbTable(stack)=%zuB\n", sizeof(SMU73_Discrete_GraphicsLevel), sizeof(SMU73_Discrete_MemoryLevel), sizeof(SMU73_Discrete_UvdLevel), sizeof(SMU73_Discrete_ExtClkLevel), sizeof(SMU73_Discrete_MCArbDramTimingTableEntry), sizeof(SMU73_Discrete_MCArbDramTimingTable)); /* ============ DF-4001a: GraphicsLevel[8] heap walk (:1026) ============ */ hdr("DF-4001a fiji_populate_all_graphic_levels -> GraphicsLevel[8] (heap, inside struct fiji_smumgr)"); { const size_t n = SMU73_MAX_LEVELS_GRAPHICS; unsigned char *obj; const char *rz; rz_alloc(sizeof(SMU73_Discrete_GraphicsLevel) * n, &obj, &rz); for (uint32_t i = 0; i < sclk_count; i++) { /* :1026 loop, no clamp */ SMU73_Discrete_GraphicsLevel *lv = (SMU73_Discrete_GraphicsLevel *)obj + i; memset(lv, 0x5A, sizeof(*lv)); /* populate writes whole level */ } oob_writes = sclk_count > n ? sclk_count - n : 0; oob_bytes = oob_writes * sizeof(SMU73_Discrete_GraphicsLevel); printf(" sclk_count=%u: %lu OOB level writes, %lu bytes past GraphicsLevel[8]\n", sclk_count, oob_writes, oob_bytes); printf(" redzone dirty bytes observed: %ld\n", rz_scan(obj, sizeof(SMU73_Discrete_GraphicsLevel) * n)); printf(" levels[sclk_count-1].DisplayWatermark store at :1042 also OOB (count-1=%u > 7)\n", sclk_count - 1); printf(" with ucNumEntries=255: %u bytes past the array; DpmTable tail after GraphicsLevel is ~%zuB -> blows past smc_state_table and the whole fiji_smumgr kzalloc\n", (255 - 8) * (unsigned)sizeof(SMU73_Discrete_GraphicsLevel), sizeof(SMU73_Discrete_DpmTable) - offsetof(SMU73_Discrete_DpmTable, GraphicsLevel) - SMU73_MAX_LEVELS_GRAPHICS * sizeof(SMU73_Discrete_GraphicsLevel)); free(obj); } /* ============ DF-4001b: MemoryLevel[4] heap walk (:1238) ============ */ hdr("DF-4001b fiji_populate_all_memory_levels -> MemoryLevel[4] (heap)"); { const size_t n = SMU73_MAX_LEVELS_MEMORY; unsigned char *obj; const char *rz; rz_alloc(sizeof(SMU73_Discrete_MemoryLevel) * n, &obj, &rz); for (uint32_t i = 0; i < mclk_count; i++) { /* :1238 loop, no clamp */ SMU73_Discrete_MemoryLevel *lv = (SMU73_Discrete_MemoryLevel *)obj + i; memset(lv, 0x5A, sizeof(*lv)); } printf(" mclk_count=%u (Fiji SMU73 bound is 4!): %u OOB writes, %lu bytes past MemoryLevel[4]\n", mclk_count, mclk_count > (uint32_t)n ? mclk_count - (uint32_t)n : 0, (unsigned long)((mclk_count > n ? mclk_count - n : 0) * sizeof(SMU73_Discrete_MemoryLevel))); printf(" redzone dirty bytes observed: %ld\n", rz_scan(obj, sizeof(SMU73_Discrete_MemoryLevel) * n)); free(obj); } /* ============ DF-4001c: STACK MCArbDramTimingTable (:1533,:1537-1546) ============ */ hdr("DF-4001c fiji_program_memory_timing_parameters -> SMU73_Discrete_MCArbDramTimingTable arb_regs (384B STACK object)"); { /* * Exact stack replica: the 384-byte object followed by 256 bytes of * "caller frame" (as the kernel function's own frame would be). * Writes past the 384B table land in the caller-frame region โ * in the kernel that is saved registers / return address. */ unsigned char frame[sizeof(SMU73_Discrete_MCArbDramTimingTable) + 256]; SMU73_Discrete_MCArbDramTimingTable *arb_regs = (SMU73_Discrete_MCArbDramTimingTable *)(void *)frame; memset(frame, 0, sizeof frame); uint32_t rc = 0; for (uint32_t i = 0; i < sclk_count; i++) { /* :1537 */ for (uint32_t j = 0; j < mclk_count; j++) { /* :1538 */ /* fiji_populate_memory_timing_parameters writes the 12-byte entry */ SMU73_Discrete_MCArbDramTimingTableEntry *e = &arb_regs->entries[i][j]; /* :1542 */ memset(e, 0x5A, sizeof(*e)); /* OOB for i*4+j >= 32 */ rc++; } } unsigned long writes_oob = 0, max_lin = 0, smashed = 0; for (uint32_t i = 0; i < sclk_count; i++) for (uint32_t j = 0; j < mclk_count; j++) { unsigned long lin = (unsigned long)i * SMU__NUM_MCLK_DPM_LEVELS + j; if (lin >= 32) { writes_oob++; if (lin > max_lin) max_lin = lin; } } for (size_t k = sizeof *arb_regs; k < sizeof frame; k++) if (frame[k] == 0x5A) smashed++; /* bytes landing in caller frame */ unsigned long last_off = (max_lin + 1) * sizeof(SMU73_Discrete_MCArbDramTimingTableEntry); printf(" sclk=%u mclk=%u: %u entry writes total, %lu PAST the 32-entry table (max linear entry %lu of 32)\n", sclk_count, mclk_count, rc, writes_oob, max_lin); printf(" deepest write offset %luB into a %zuB stack object -> %ld bytes into the caller frame (%lu frame bytes actually smashed here)\n", last_off, sizeof *arb_regs, (long)(last_off - sizeof *arb_regs), smashed); printf(" with ucNumEntries=255/255 the walk reaches offset %luB past the 384B object (kernel stack smash, return-address direction)\n", (255UL * 4 + 255) * sizeof(SMU73_Discrete_MCArbDramTimingTableEntry)); printf(" (the earlier version of this demo smashed its OWN stack frame and died of SIGBUS โ that bus error was the bug demonstrating itself)\n"); } /* ============ DF-4002: VCE/UVD/ACP ExtClkLevel walks ============ */ hdr("DF-4002 fiji_populate_smc_{vce,acp,uvd}_level -> VceLevel[8]/AcpLevel[8]/UvdLevel[8] (heap)"); { unsigned char *o1, *o2, *o3; const char *r; rz_alloc(sizeof(SMU73_Discrete_ExtClkLevel) * SMU73_MAX_LEVELS_VCE, &o1, &r); for (uint8_t c = 0; c < (uint8_t)mm_count; c++) /* :1436 loop, VceLevelCount=(u8)mm_table->count */ memset((SMU73_Discrete_ExtClkLevel *)o1 + c, 0x5A, sizeof(SMU73_Discrete_ExtClkLevel)); printf(" VceLevel[8]: mm_count=%u -> %u OOB writes, %u bytes past\n", mm_count, mm_count - SMU73_MAX_LEVELS_VCE, (mm_count - SMU73_MAX_LEVELS_VCE) * (unsigned)sizeof(SMU73_Discrete_ExtClkLevel)); free(o1); rz_alloc(sizeof(SMU73_Discrete_ExtClkLevel) * SMU73_MAX_LEVELS_ACP, &o2, &r); for (uint8_t c = 0; c < (uint8_t)mm_count; c++) /* :1475 */ memset((SMU73_Discrete_ExtClkLevel *)o2 + c, 0x5A, sizeof(SMU73_Discrete_ExtClkLevel)); printf(" AcpLevel[8]: mm_count=%u -> %u OOB writes, %u bytes past\n", mm_count, mm_count - SMU73_MAX_LEVELS_ACP, (mm_count - SMU73_MAX_LEVELS_ACP) * (unsigned)sizeof(SMU73_Discrete_ExtClkLevel)); free(o2); rz_alloc(sizeof(SMU73_Discrete_UvdLevel) * SMU73_MAX_LEVELS_UVD, &o3, &r); for (uint8_t c = 0; c < (uint8_t)mm_count; c++) /* :1572 */ memset((SMU73_Discrete_UvdLevel *)o3 + c, 0x5A, sizeof(SMU73_Discrete_UvdLevel)); printf(" UvdLevel[8]: mm_count=%u -> %u OOB writes, %u bytes past\n", mm_count, mm_count - SMU73_MAX_LEVELS_UVD, (mm_count - SMU73_MAX_LEVELS_UVD) * (unsigned)sizeof(SMU73_Discrete_UvdLevel)); printf(" (followed in-struct by VceLevel/AcpLevel/SamuLevel/Ulv/Smio[32]; count 255 runs ~3KB past, beyond the fiji_smumgr allocation)\n"); free(o3); } /* ============ DF-4003: BapmVddcVid{Lo,Hi}Sidd[16] + entries[32] read ============ */ hdr("DF-4003 fiji_populate_cac_table -> BapmVddcVidLoSidd/HiSidd[16] write + vddc_voltage_table.entries[32] read"); { unsigned char *obj; const char *rz; /* the two arrays are adjacent in DpmTable (:228-:230 HiSidd[16], LoSidd[16], HiSidd2[16]) */ const size_t n = SMU73_MAX_LEVELS_VDDC; rz_alloc(n, &obj, &rz); /* LoSidd[16] replica */ uint8_t vddc_volt_entries[32]; /* pp_atomctrl_voltage_table.entries[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES=32] */ memset(vddc_volt_entries, 0, sizeof vddc_volt_entries); unsigned oob_read = 0, oob_wr = 0; for (uint32_t c = 0; c < vddc_lookup_count; c++) { /* :772 loop over lookup_table->count */ if (c >= 32) oob_read++; /* :774 data->vddc_voltage_table.entries[c] */ volatile uint8_t v = vddc_volt_entries[c < 32 ? c : 31]; (void)v; if (c < n) obj[c] = 0x5A; else oob_wr++; /* :775/:777 writes */ } printf(" vddc_lookup_count=%u: %u OOB writes past BapmVddcVidLoSidd[16] (and same for HiSidd), %u OOB reads of vddc_voltage_table.entries[32]\n", vddc_lookup_count, oob_wr, oob_read); printf(" count comes from get_vddc_lookup_table ->count = ucNumEntries (u8<=255) [process_pptables_v1_0.c:176]; with 255: 239B past each array, 223 reads past entries[32]\n"); free(obj); } /* ============ DF-4004: Sclk_voltageOffset[8] walk (:1706-1719) ============ */ hdr("DF-4004 fiji_populate_clock_stretcher_data_table -> Sclk_voltageOffset[8]"); { unsigned char *obj; const char *rz; const size_t n = 8; rz_alloc(n, &obj, &rz); uint8_t volt_offset = 0; for (uint32_t i = 0; i < dep_sclk_raw; i++) { /* :1706 loop over table_info->vdd_dep_on_sclk->count (raw!) */ volt_offset = (uint8_t)(i + 1); /* computed cks offset (:1716) */ obj[i] = volt_offset; /* :1718 Sclk_voltageOffset[i] = volt_offset; */ } printf(" dep_sclk->count=%u (RAW count, not the dedup'd dpm count): writes at [8..%u] are OOB\n", dep_sclk_raw, dep_sclk_raw - 1); printf(" array sits at DpmTable+%zu of %zuB, followed by ClockStretcherDataTable/CKS_LOOKUPTable (struct tail); count 255 writes 247B past the array, off the end of smc_state_table\n", offsetof(SMU73_Discrete_DpmTable, Sclk_voltageOffset), sizeof(SMU73_Discrete_DpmTable)); printf(" note: the walk (:1706) runs BEFORE the stretch_amount validation at :1731, so even a 'rejected' VBIOS value smears the table first\n"); free(obj); } /* ============ DF-4005: entries[count].mvdd one-entry OOB heap read (:413) ============ */ hdr("DF-4005 fiji_get_dependency_volt_by_clk fall-through reads dep_table->entries[i].mvdd with i == count"); { /* phm_ppt_v1_clock_voltage_dependency_record: clk,vddc,vddci,mvdd,cks_* โ use the real 4-field head (20B packed) + tail to 32B total as in hwmgr.h */ #pragma pack(push,1) struct dep_rec { uint32_t clk, vddc, vddci; uint16_t mvdd; uint8_t cks_enable, cks_voffset; }; #pragma pack(pop) const uint32_t cnt = 3; unsigned char *base = malloc(cnt * sizeof(struct dep_rec) + 64); memset(base, 0, cnt * sizeof(struct dep_rec) + 64); /* poison the mvdd field of the phantom entries[count] record exactly like adjacent-slab garbage would appear */ base[cnt * sizeof(struct dep_rec) + offsetof(struct dep_rec, mvdd)] = 0x01; base[cnt * sizeof(struct dep_rec) + offsetof(struct dep_rec, mvdd) + 1] = 0x00; struct dep_rec *entries = (struct dep_rec *)base; /* clock > every entries[].clk -> loop falls through with i == count */ uint32_t i = 0, clock = 0xFFFFFFFF; for (i = 0; i < cnt; i++) if (entries[i].clk >= clock) break; /* :399 in-bounds */ volatile uint32_t vddc = entries[i - 1].vddc; (void)vddc; /* :413 THE BUG: reads entries[count].mvdd โ 2 bytes past the allocation */ volatile uint16_t mvdd_oob = entries[i].mvdd; printf(" count=%u, i=%u after fall-through; entries[i].mvdd reads bytes at +%zu, %zu bytes past the %u-entry heap object\n", cnt, i, (size_t)((unsigned char *)&entries[i].mvdd - base), (size_t)((unsigned char *)&entries[i].mvdd - base) - cnt * sizeof(struct dep_rec), cnt); printf(" value read = 0x%04x (nonzero -> *mvdd set from entries[i-1] (in-bounds); zero -> *mvdd left unset) โ kernel heap adjacent-slab bytes consumed as a branch condition\n", mvdd_oob); free(base); } /* ============ DF-4006: NULL mm_dep_table deref (:1433 etc.) ============ */ hdr("DF-4006 fiji_populate_smc_vce_level with usMMDependencyTableOffset==0 -> mm_dep_table NULL -> mm_table->count deref"); { struct phm_ppt_v1_information { void *mm_dep_table; } ti = { NULL }; /* process_pptables_v1_0.c:796 leaves NULL when offset==0 */ void *mm_table = ti.mm_dep_table; printf(" mm_dep_table==NULL when PowerPlay table has usMMDependencyTableOffset==0 (parser keeps result=0 and continues)\n"); printf(" fiji_init_smc_table -> fiji_populate_smc_vce_level:1433 'table->VceLevelCount = (uint8_t)(mm_table->count)'\n"); fflush(stdout); /* prove the derec faults: child performs it, parent reports the trap */ { pid_t pid = fork(); if (pid == 0) { volatile uint32_t count = *(volatile uint32_t *)mm_table; (void)count; _exit(0); /* not reached */ } int st = 0; waitpid_helper(&st); if (WIFSIGNALED(st)) printf(" child took signal %d on mm_table->count โ in-kernel equivalent: page fault on NULL+offset during fiji_init_smc_table (driver attach / resume) => panic\n", WTERMSIG(st)); else printf(" !! deref did not fault (unexpected)\n"); } } printf("\nDEMONSTRATOR COMPLETE: all walks reproduced; counts are single VBIOS bytes (ucNumEntries) reaching the fixed SMC arrays unclamped.\n"); return 0; } |