DF-4038 / polaris_walks.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 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | /* * DF-4036..DF-4041 guest demonstrator โ polaris10_smumgr.c SMC-table walks. * * Reproduces, in userspace, the exact index arithmetic of: * polaris10_populate_all_graphic_levels (polaris10_smumgr.c:1000-1017) -> GraphicsLevel[8] [DF-4036a] * polaris10_populate_all_memory_levels (polaris10_smumgr.c:1138-1148) -> MemoryLevel[4] [DF-4036b] * polaris10_program_memory_timing_parameters (polaris10_smumgr.c:1365-1380) -> STACK MCArb entries[8][4] [DF-4036c] * polaris10_populate_cac_table (polaris10_smumgr.c:712-718) -> BapmVddcVid{Lo,Hi}Sidd[16] + entries[32] read [DF-4037] * polaris10_populate_clock_stretcher_data_table (:1555-1574) -> Sclk_voltageOffset[8] [DF-4038a] * polaris10_populate_avfs_parameters (:1688-1691) -> sclk dep entries[i<NUM_VFT_COLUMNS] read [DF-4038b] * polaris10_get_dependency_volt_by_clk (:396-411) -> entries[count] x2 one-entry heap OOB read [DF-4039] * polaris10_populate_smc_vce_level/uvd_level (:1298/:1404), populate_cac_table (:712) -> NULL derefs [DF-4040] * polaris10_populate_smc_mvdd_table (:651-663) -> entries[count] wrong-index read [DF-4041a] * polaris10_populate_mvdd_value (:1183-1188) -> entries[i] past [32] on dep count [DF-4041b] * * Struct sizes/offsets are the kernel's own: smu74.h / smu74_discrete.h are * compiled in verbatim (#pragma pack(1), self-contained). * * Count provenance (all VBIOS/PowerPlay-table bytes, unclamped into these walks): * sclk/mclk dpm counts: process_pptables_v1_0.c get_{sclk,mclk}_voltage_dependency_table * ucNumEntries (u8 <=255) -> smu7_hwmgr.c:783-810 dpm_table.*.count (no clamp vs 8) * vddc_lookup count: process_pptables_v1_0.c:176 table->count = ucNumEntries (alloc fixed for 16 โ DF-1574) * raw vdd_dep_on_sclk->count / vdd_dep_on_mclk->count: u8 <=255, allocated with exactly count entries * (polaris10 is pinned to PP_TABLE_V1 + SMU74; smu7_hwmgr.c backend, smu74_smumgr.c consumer) */ #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stddef.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include "smu74.h" #include "smu74_discrete.h" #define PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES 32 /* ppatomctrl.h:45 */ static int waitpid_helper(int *st) { while (wait(st) < 0) {} return 0; } #define REDZONE 4096 static unsigned long oob_writes, oob_bytes; static void *rz_alloc(size_t sz, unsigned char **obj, const char **rz) { unsigned char *base = malloc(sz + REDZONE); memset(base, 0xAA, sz + REDZONE); memset(base, 0x00, sz); *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 (single PowerPlay-table bytes each) */ const uint32_t sclk_count = 12; /* > SMU74_MAX_LEVELS_GRAPHICS (8) */ const uint32_t mclk_count = 6; /* > SMU74_MAX_LEVELS_MEMORY (4) */ const uint32_t vddc_lookup_count = 40; /* > 16 and > 32 */ const uint32_t dep_sclk_raw = 12; /* raw vdd_dep_on_sclk->count */ const uint32_t dep_sclk_few = 3; /* under-count for AVFS walk */ const uint32_t dep_mclk_raw = 40; /* raw vdd_dep_on_mclk->count */ printf("polaris10_smumgr.c demonstrator โ kernel struct geometry (kernel headers):\n"); printf(" sizeof(SMU74_Discrete_DpmTable)=%zu GraphicsLevel@+%zu MemoryLevel@+%zu Sclk_voltageOffset@+%zu BapmVddcVidLoSidd@+%zu\n", sizeof(SMU74_Discrete_DpmTable), offsetof(SMU74_Discrete_DpmTable, GraphicsLevel), offsetof(SMU74_Discrete_DpmTable, MemoryLevel), offsetof(SMU74_Discrete_DpmTable, Sclk_voltageOffset), offsetof(SMU74_Discrete_DpmTable, BapmVddcVidLoSidd)); printf(" GraphicsLevel=%zuB MemoryLevel=%zuB UvdLevel=%zuB ExtClkLevel=%zuB ArbEntry=%zuB ArbTable(stack)=%zuB\n", sizeof(SMU74_Discrete_GraphicsLevel), sizeof(SMU74_Discrete_MemoryLevel), sizeof(SMU74_Discrete_UvdLevel), sizeof(SMU74_Discrete_ExtClkLevel), sizeof(SMU74_Discrete_MCArbDramTimingTableEntry), sizeof(SMU74_Discrete_MCArbDramTimingTable)); /* ============ DF-4036a: GraphicsLevel[8] heap walk (:1000-1011) ============ */ hdr("DF-4036a polaris10_populate_all_graphic_levels -> GraphicsLevel[8] (heap, inside struct polaris10_smumgr)"); { const size_t n = SMU74_MAX_LEVELS_GRAPHICS; unsigned char *obj; const char *rz; rz_alloc(sizeof(SMU74_Discrete_GraphicsLevel) * n, &obj, &rz); for (uint32_t i = 0; i < sclk_count; i++) { /* :1000 loop, no clamp */ SMU74_Discrete_GraphicsLevel *lv = (SMU74_Discrete_GraphicsLevel *)obj + i; memset(lv, 0x5A, sizeof(*lv)); /* populate_single writes whole level */ } oob_writes = sclk_count > n ? sclk_count - n : 0; oob_bytes = oob_writes * sizeof(SMU74_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(SMU74_Discrete_GraphicsLevel) * n)); printf(" secondary walk :1028 levels[i].pcieDpmLevel i<count and :1017 GraphicsDpmLevelCount=(u8)count (re-feeds update_dpm_settings:2427)\n"); printf(" with ucNumEntries=255: %u bytes past the array; GraphicsLevel ends %zuB before DpmTable end -> blows past smc_state_table and the polaris10_smumgr kzalloc\n", (255 - 8) * (unsigned)sizeof(SMU74_Discrete_GraphicsLevel), sizeof(SMU74_Discrete_DpmTable) - offsetof(SMU74_Discrete_DpmTable, GraphicsLevel) - SMU74_MAX_LEVELS_GRAPHICS * sizeof(SMU74_Discrete_GraphicsLevel)); free(obj); } /* ============ DF-4036b: MemoryLevel[4] heap walk + [count-1] stores (:1138-1148) ============ */ hdr("DF-4036b polaris10_populate_all_memory_levels -> MemoryLevel[4] (heap)"); { const size_t n = SMU74_MAX_LEVELS_MEMORY; unsigned char *obj; const char *rz; rz_alloc(sizeof(SMU74_Discrete_MemoryLevel) * n, &obj, &rz); for (uint32_t i = 0; i < mclk_count; i++) { /* :1138 loop, no clamp */ SMU74_Discrete_MemoryLevel *lv = (SMU74_Discrete_MemoryLevel *)obj + i; memset(lv, 0x5A, sizeof(*lv)); if (i == mclk_count - 1) { /* :1145 [count-1] stores */ lv->DisplayWatermark = 0x5A; /* PPSMC_DISPLAY_WATERMARK_HIGH */ lv->EnabledForActivity = 0x5A; /* :1146-1147 */ } } printf(" mclk_count=%u: %u OOB writes, %lu bytes past MemoryLevel[4]; [count-1]=%u store is itself OOB\n", mclk_count, mclk_count - (uint32_t)n, (unsigned long)((mclk_count - n) * sizeof(SMU74_Discrete_MemoryLevel)), mclk_count - 1); printf(" redzone dirty bytes observed: %ld\n", rz_scan(obj, sizeof(SMU74_Discrete_MemoryLevel) * n)); printf(" MemoryLevel[4..] overwrite lands on LinkLevel[0..]/ACPILevel/UvdLevel (in-struct) and, from count ~%zu, past the whole polaris10_smumgr allocation\n", (sizeof(SMU74_Discrete_DpmTable) - offsetof(SMU74_Discrete_DpmTable, MemoryLevel)) / sizeof(SMU74_Discrete_MemoryLevel)); free(obj); } /* ============ DF-4036c: STACK MCArbDramTimingTable entries[8][4] (:1365-1380) ============ */ hdr("DF-4036c polaris10_program_memory_timing_parameters -> SMU74_Discrete_MCArbDramTimingTable arb_regs (STACK object)"); { unsigned char frame[sizeof(SMU74_Discrete_MCArbDramTimingTable) + 256]; SMU74_Discrete_MCArbDramTimingTable *arb_regs = (SMU74_Discrete_MCArbDramTimingTable *)(void *)frame; /* :1365 stack local */ memset(frame, 0, sizeof frame); uint32_t rc = 0; for (uint32_t i = 0; i < sclk_count; i++) { /* :1369 sclk count */ for (uint32_t j = 0; j < mclk_count; j++) { /* :1370 mclk count */ SMU74_Discrete_MCArbDramTimingTableEntry *e = &arb_regs->entries[i][j]; /* :1374 */ memset(e, 0x5A, sizeof(*e)); 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++; unsigned long last_off = (max_lin + 1) * sizeof(SMU74_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(" entries is [8][4]: mclk j>=4 wraps into entries[i+1] (wrong pairing, silent) and runs OFF the table at i==7,j>=4 or i>=8\n"); printf(" with ucNumEntries=255/255 the walk reaches offset %luB past the %zuB object (kernel stack smash, return-address direction)\n", (255UL * 4 + 255) * sizeof(SMU74_Discrete_MCArbDramTimingTableEntry), sizeof *arb_regs); } /* ============ DF-4037: BapmVddcVid{Lo,Hi}Sidd[16] write + entries[32] read (:712-718) ============ */ hdr("DF-4037 polaris10_populate_cac_table -> BapmVddcVidLoSidd[16] write + vddc_voltage_table.entries[32] read"); { unsigned char *obj; const char *rz; const size_t n = SMU74_MAX_LEVELS_VDDC; /* 16 */ rz_alloc(n, &obj, &rz); uint8_t vddc_volt_entries[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES]; /* pp_atomctrl_voltage_table.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++) { /* :712 loop bound = lookup_table->count (RAW VBIOS u8) */ if (c >= PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES) oob_read++; /* :714 data->vddc_voltage_table.entries[c] (trimmed to 16!) */ volatile uint8_t v = vddc_volt_entries[c < PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES ? c : 0]; (void)v; if (c < n) obj[c] = 0x5A; else oob_wr++; /* :715-717 writes */ } printf(" vddc_lookup_count=%u: %u OOB writes past BapmVddcVidLoSidd[16] (x3 arrays), %u OOB reads of vddc_voltage_table.entries[32]\n", vddc_lookup_count, oob_wr, oob_read); printf(" redzone dirty bytes observed: %ld\n", rz_scan(obj, n)); printf(" loop bound is lookup_table->count = raw ucNumEntries (process_pptables_v1_0.c:176; buffer sized for 16 โ DF-1574),\n"); printf(" while data->vddc_voltage_table was trimmed to SMU74_MAX_LEVELS_VDDC=16 at smu7_hwmgr.c:323-328 โ the read index is bound to neither\n"); free(obj); } /* ============ DF-4038a: Sclk_voltageOffset[8] walk (:1555-1574) ============ */ hdr("DF-4038a polaris10_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; uint8_t masterEn = 0; for (uint32_t i = 0; i < dep_sclk_raw; i++) { /* :1555 loop over RAW table_info->vdd_dep_on_sclk->count */ masterEn |= (uint8_t)(1u << i); /* :1556-1557 cks_enable<<i โ >=8 shifts out (UB value only) */ volt_offset = (uint8_t)(i + 1); if (i < n) obj[i] = volt_offset; /* :1574 Sclk_voltageOffset[i] = volt_offset; */ } printf(" dep_sclk->count=%u (RAW count, not the dedup'd dpm count): writes at [8..%u] are OOB (skipped here to keep the demo alive)\n", dep_sclk_raw, dep_sclk_raw - 1); printf(" array sits at DpmTable+%zu of %zuB, followed by ClockStretcherDataTable/CKS_LOOKUPTable; count 255 writes 247B past, off smc_state_table\n", offsetof(SMU74_Discrete_DpmTable, Sclk_voltageOffset), sizeof(SMU74_Discrete_DpmTable)); printf(" walk runs BEFORE the stretch_amount validation (:1579), so even a 'rejected' VBIOS smears the table first\n"); free(obj); } /* ============ DF-4038b: AVFS walk reads entries[i<8] regardless of count (:1688-1691) ============ */ hdr("DF-4038b polaris10_populate_avfs_parameters -> sclk dep entries[i < NUM_VFT_COLUMNS=8] read with count < 8"); { /* vdd_dep_on_sclk is allocated with EXACTLY count entries (hwmgr_ppt.h entries[1] flex) */ #pragma pack(push,1) struct dep_rec { uint32_t clk; uint8_t vddInd, vddciInd, mvddInd; uint16_t vdd_offset, vddc, vddgfx, vddci, mvdd; uint8_t phases, cks_enable, cks_voffset; uint32_t sclk_offset; }; #pragma pack(pop) unsigned char *base = malloc(dep_sclk_few * sizeof(struct dep_rec) + 64); memset(base, 0, dep_sclk_few * sizeof(struct dep_rec) + 64); unsigned oob_reads = 0; for (uint32_t i = 0; i < 8; i++) { /* :1688 i < NUM_VFT_COLUMNS */ if (i >= dep_sclk_few) oob_reads++; /* :1689/:1690 entries[i].cks_voffset / .sclk_offset */ volatile uint32_t off = ((struct dep_rec *)base)[i < dep_sclk_few ? i : dep_sclk_few - 1].sclk_offset; (void)off; } printf(" dep count=%u (< NUM_VFT_COLUMNS=8): %u reads past the %u-entry heap allocation (cks_voffset + sclk_offset each)\n", dep_sclk_few, oob_reads, dep_sclk_few); printf(" values land in AVFS_meanNsigma.Static_Voltage_Offset[]/AVFS_SclkOffset[] and are uploaded to SMU (:1697-1710)\n"); free(base); } /* ============ DF-4039: entries[count] one-entry heap OOB reads (:396-411) ============ */ hdr("DF-4039 polaris10_get_dependency_volt_by_clk fall-through reads dep_table->entries[i] with i == count (x2)"); { #pragma pack(push,1) struct dep_rec2 { uint32_t clk; uint8_t vddInd, vddciInd, mvddInd; uint16_t vdd_offset, vddc, vddgfx, vddci, mvdd; }; #pragma pack(pop) const uint32_t cnt = 3; unsigned char *base = malloc(cnt * sizeof(struct dep_rec2) + 64); memset(base, 0, cnt * sizeof(struct dep_rec2) + 64); base[cnt * sizeof(struct dep_rec2) + offsetof(struct dep_rec2, vddc)] = 0x42; /* phantom byte */ base[cnt * sizeof(struct dep_rec2) + offsetof(struct dep_rec2, mvdd)] = 0x01; struct dep_rec2 *entries = (struct dep_rec2 *)base; uint32_t i = 0, clock = 0xFFFFFFFF; for (i = 0; i < cnt; i++) if (entries[i].clk >= clock) break; /* all clk < clock -> fall through */ volatile uint32_t vddc_ok = entries[i - 1].vddc; (void)vddc_ok; /* :396 in-bounds */ volatile uint32_t vddc_oob = entries[i].vddc; (void)vddc_oob; /* :403 OOB (phm_find_closest_vddci arg) */ volatile uint16_t mvdd_oob = entries[i].mvdd; (void)mvdd_oob; /* :410 OOB (branch condition) */ printf(" count=%u, i=%u after fall-through; entries[i].vddc read at +%zu and entries[i].mvdd at +%zu โ past the %u-entry object\n", cnt, i, (size_t)((unsigned char *)&entries[i].vddc - base), (size_t)((unsigned char *)&entries[i].mvdd - base), cnt); printf(" phantom vddc read = 0x%02x.. (feeds phm_find_closest_vddci -> MinVoltage to SMU); mvdd read = 0x%04x gates *mvdd\n", 0x42, mvdd_oob); free(base); } /* ============ DF-4040: NULL mm_dep_table / vddc_lookup_table derefs ============ */ hdr("DF-4040 polaris10 populate with usMMDependencyTableOffset==0 / usVddcLookupTableOffset==0 -> NULL derefs"); { struct ti { void *mm_dep_table; void *vddc_lookup_table; } ti = { NULL, NULL }; printf(" parser leaves mm_dep_table NULL when usMMDependencyTableOffset==0 (result stays 0, parse continues);\n"); printf(" polaris10_init_smc_table:1819 -> polaris10_populate_smc_vce_level:1298 'table->VceLevelCount=(uint8_t)(mm_table->count)'\n"); printf(" polaris10_init_smc_table:1831 -> polaris10_populate_smc_uvd_level:1404 'table->UvdLevelCount=(uint8_t)(mm_table->count)'\n"); printf(" parser leaves vddc_lookup_table NULL when usVddcLookupTableOffset==0 (process_pptables_v1_0.c:281-288);\n"); printf(" polaris10_init_smc_table:1781 -> polaris10_populate_smc_voltage_tables:728 -> populate_cac_table:712 'lookup_table->count'\n"); fflush(stdout); { pid_t pid = fork(); if (pid == 0) { volatile uint32_t count = *(volatile uint32_t *)ti.mm_dep_table; (void)count; _exit(0); } 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 during polaris10_init_smc_table (attach/resume) => panic\n", WTERMSIG(st)); else printf(" !! deref did not fault (unexpected)\n"); } } /* ============ DF-4041: MVDD stash walks (:651-663, :1183-1188) ============ */ hdr("DF-4041 polaris10 MVDD stash: entries[count] wrong-index read (:657) + populate_mvdd_value walk past entries[32] (:1185)"); { /* (a) populate_smc_mvdd_table: loop writes Pattern[level] but reads entries[count] */ uint32_t mvdd_tab_count = 4; /* trimmed to SMU74_MAX_LEVELS_MVDD=4 at smu7_hwmgr.c:344-349 */ uint32_t count = mvdd_tab_count > 4 ? 4 : mvdd_tab_count; uint16_t entries_val[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES]; memset(entries_val, 0, sizeof entries_val); for (uint32_t l = 0; l < count; l++) entries_val[l] = (uint16_t)(1100 + l * 100); /* real table: 1100,1200,1300,1400 */ uint16_t pattern_v[4]; for (uint32_t level = 0; level < count; level++) { /* :656-657 */ pattern_v[level] = entries_val[count]; /* BUG: index is count, not level */ } printf(" (a) mvdd table count=%u: Pattern[0..%u].Voltage all read entries[%u] -> ", count, count - 1, count); for (uint32_t l = 0; l < count; l++) printf("%u ", pattern_v[l]); printf("(expected per-level %u..%u) โ in-bounds of entries[32] but one past the VALID entries: all MVDD smio patterns programmed with the stale/zero slot\n", entries_val[0], entries_val[count - 1]); /* (b) populate_mvdd_value: loop bound vdd_dep_on_mclk->count (RAW u8) over entries[32] */ unsigned oob_reads = 0; for (uint32_t i = 0; i < dep_mclk_raw; i++) { /* :1183 loop bound dep->count */ if (i >= PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES) oob_reads++; /* :1185 data->mvdd_voltage_table.entries[i] */ } printf(" (b) vdd_dep_on_mclk->count=%u walking mvdd_voltage_table.entries[32]: %u reads past the fixed array (intra-struct heap OOB read of smu7_hwmgr); loop breaks early only if a clk matches\n", dep_mclk_raw, oob_reads); } printf("\nDEMONSTRATOR COMPLETE: all walks reproduced; counts are single VBIOS bytes (ucNumEntries) reaching the fixed SMU74 SMC arrays unclamped.\n"); return 0; } |