DF-4665 / smu7_odn_overflow.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 | /* * DF-4665 harness — verbatim kernel function + 1:1 struct layouts. * * Bug: sys/dev/drm/amd/powerplay/hwmgr/smu_helper.c:675-699 * smu_get_voltage_dependency_table_ppt_v1() has NO destination-size * parameter: it writes allowed_dep_table->count records into whatever * the caller points it at. * * Proven broken caller (the NEW leg; vega10 leg is DF-3703): * smu7_hwmgr.c:844-845 / 856-857 (smu7_odn_initial_default_setting, * reached from smu7_setup_default_dpm_tables:967 at driver load when * OverDrive is enabled, and from smu7_odn_edit_dpm_table:4845 on * PP_OD_RESTORE_DEFAULT_TABLE) casts * &odn_table->vdd_dependency_on_sclk (entries[MAX_REGULAR_DPM_NUMBER=8]) * to phm_ppt_v1_clock_voltage_dependency_table* and lets the helper * copy count (= raw VBIOS ucNumEntries, u8, up to 255) records into it. * * count provenance: process_pptables_v1_0.c:436/468 * sclk_table->count = (uint32_t)tonga_table->ucNumEntries; (no upper bound) * * This harness replicates, in order: * [1] the DF-1166 site (smu7_setup_dpm_tables_v1 dedup loop into * dpm_levels[8]) with a table of 16 entries but only 2 DISTINCT clocks * -> stays in bounds (shows this input does NOT trip the known finding); * [2] the smu7 ODN copy through the verbatim helper -> 16 x 28-byte records * into entries[8] -> out-of-bounds heap write with VBIOS-controlled * contents, corrupting vdd_dependency_on_mclk / odn_mclk_min_limit / * min_vddc / max_vddc and the members that follow odn_dpm_table in * struct smu7_hwmgr (here: canary region). * [3] worst case count=255 -> (255-8)*28 = 6916 bytes past entries[7]. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> /* ---- verbatim: hwmgr_ppt.h:31-54 ---- */ struct phm_ppt_v1_clock_voltage_dependency_record { uint32_t clk; uint8_t vddInd; uint8_t vddciInd; uint8_t mvddInd; uint16_t vdd_offset; uint16_t vddc; uint16_t vddgfx; uint16_t vddci; uint16_t mvdd; uint8_t phases; uint8_t cks_enable; uint8_t cks_voffset; uint32_t sclk_offset; }; struct phm_ppt_v1_clock_voltage_dependency_table { uint32_t count; struct phm_ppt_v1_clock_voltage_dependency_record entries[1]; }; /* ---- verbatim: hardwaremanager.h:384-397 ---- */ struct phm_odn_performance_level { uint32_t clock; uint32_t vddc; int enabled; /* bool */ }; struct phm_odn_clock_levels { uint32_t size; uint32_t options; uint32_t flags; uint32_t num_of_pl; struct phm_odn_performance_level entries[8]; }; /* ---- verbatim: smu7_hwmgr.h:98-110 + 177-190 ---- */ #define MAX_REGULAR_DPM_NUMBER 8 struct smu7_dpm_level { int enabled; /* bool */ uint32_t value; uint32_t param1; }; struct smu7_single_dpm_table { uint32_t count; struct smu7_dpm_level dpm_levels[MAX_REGULAR_DPM_NUMBER]; }; struct smu7_dpm_table { struct smu7_single_dpm_table sclk_table; struct smu7_single_dpm_table mclk_table; struct smu7_single_dpm_table pcie_speed_table; struct smu7_single_dpm_table vddc_table; struct smu7_single_dpm_table vddci_table; struct smu7_single_dpm_table mvdd_table; }; struct smu7_odn_clock_voltage_dependency_table { uint32_t count; struct phm_ppt_v1_clock_voltage_dependency_record entries[MAX_REGULAR_DPM_NUMBER]; }; struct smu7_odn_dpm_table { struct phm_odn_clock_levels odn_core_clock_dpm_levels; struct phm_odn_clock_levels odn_memory_clock_dpm_levels; struct smu7_odn_clock_voltage_dependency_table vdd_dependency_on_sclk; struct smu7_odn_clock_voltage_dependency_table vdd_dependency_on_mclk; uint32_t odn_mclk_min_limit; uint32_t min_vddc; uint32_t max_vddc; }; /* * struct smu7_hwmgr prefix (smu7_hwmgr.h:203-206) + a canary region standing * in for the members that follow odn_dpm_table (voting_rights_clients[8] ...). */ struct smu7_backend_like { struct smu7_dpm_table dpm_table; struct smu7_dpm_table golden_dpm_table; struct smu7_odn_dpm_table odn_dpm_table; uint32_t canary[64]; /* stands for everything after odn_dpm_table */ }; #define CANARY 0xDEADBEEFU #define EINVAL 22 /* pp_debug.h equivalent (userspace) */ #define PP_ASSERT_WITH_CODE(cond, msg, code) do { \ if (!(cond)) { \ fprintf(stderr, "amdgpu: [powerplay] %s\n", msg); \ code; \ } \ } while (0) /* ---- VERBATIM: smu_helper.c:675-699 ---- */ int smu_get_voltage_dependency_table_ppt_v1( const struct phm_ppt_v1_clock_voltage_dependency_table *allowed_dep_table, struct phm_ppt_v1_clock_voltage_dependency_table *dep_table) { uint8_t i = 0; PP_ASSERT_WITH_CODE((0 != allowed_dep_table->count), "Voltage Lookup Table empty", return -EINVAL); dep_table->count = allowed_dep_table->count; for (i=0; i<dep_table->count; i++) { dep_table->entries[i].clk = allowed_dep_table->entries[i].clk; dep_table->entries[i].vddInd = allowed_dep_table->entries[i].vddInd; dep_table->entries[i].vdd_offset = allowed_dep_table->entries[i].vdd_offset; dep_table->entries[i].vddc = allowed_dep_table->entries[i].vddc; dep_table->entries[i].vddgfx = allowed_dep_table->entries[i].vddgfx; dep_table->entries[i].vddci = allowed_dep_table->entries[i].vddci; dep_table->entries[i].mvdd = allowed_dep_table->entries[i].mvdd; dep_table->entries[i].phases = allowed_dep_table->entries[i].phases; dep_table->entries[i].cks_enable = allowed_dep_table->entries[i].cks_enable; dep_table->entries[i].cks_voffset = allowed_dep_table->entries[i].cks_voffset; } return 0; } /* ---- end verbatim ---- */ #define UNUSED_EOL_MARKER 0 /* source "VBIOS-parsed" dependency table (heap, count = raw ucNumEntries) */ static struct phm_ppt_v1_clock_voltage_dependency_table *make_dep_table(unsigned count, unsigned distinct_clocks) { struct phm_ppt_v1_clock_voltage_dependency_table *t; unsigned i; t = calloc(1, sizeof(*t) + sizeof(struct phm_ppt_v1_clock_voltage_dependency_record) * (count + 1)); if (!t) { perror("calloc"); exit(1); } t->count = count; for (i = 0; i < count; i++) { /* monotonic runs of equal clock values: `distinct` levels total, * so the DF-1166 dedup loop appends only `distinct` dpm_levels — * attacker controls every field (VBIOS records) */ t->entries[i].clk = 100000 + ((uint64_t)i * distinct_clocks / count) * 2500; t->entries[i].vddInd = (uint8_t)i; t->entries[i].vddciInd = (uint8_t)(i + 1); t->entries[i].mvddInd = (uint8_t)(i + 2); t->entries[i].vdd_offset = (uint16_t)(0x1100 + i); t->entries[i].vddc = (uint16_t)(900 + i); t->entries[i].vddgfx = (uint16_t)(950 + i); t->entries[i].vddci = (uint16_t)(1000 + i); t->entries[i].mvdd = (uint16_t)(1050 + i); t->entries[i].phases = 1; t->entries[i].cks_enable = (uint8_t)(i & 1); t->entries[i].cks_voffset = 0; t->entries[i].sclk_offset = 0x41410000u + i; } return t; } /* the DF-1166 site, verbatim dedup loop (smu7_hwmgr.c:784-810 essence) */ static void smu7_setup_dpm_tables_v1_leg(struct smu7_single_dpm_table *tab, const struct phm_ppt_v1_clock_voltage_dependency_table *dep) { uint32_t i; tab->count = 0; for (i = 0; i < dep->count; i++) { if (i == 0 || tab->dpm_levels[tab->count - 1].value != dep->entries[i].clk) { tab->dpm_levels[tab->count].value = dep->entries[i].clk; tab->dpm_levels[tab->count].enabled = (i == 0) ? 1 : 0; tab->count++; } } } static unsigned count_smashed(const uint32_t *c, unsigned n) { unsigned i, s = 0; for (i = 0; i < n; i++) if (c[i] != CANARY) s++; return s; } int main(int argc, char **argv) { unsigned count = (argc > 1) ? (unsigned)atoi(argv[1]) : 16; unsigned distinct = (argc > 2) ? (unsigned)atoi(argv[2]) : 2; struct smu7_backend_like *be; struct phm_ppt_v1_clock_voltage_dependency_table *dep; size_t recsize = sizeof(struct phm_ppt_v1_clock_voltage_dependency_record); char *p_end, *p_entries8; printf("record size = %zu bytes, MAX entries = %d\n", recsize, MAX_REGULAR_DPM_NUMBER); be = calloc(1, sizeof(*be)); for (size_t k = 0; k < 64; k++) be->canary[k] = CANARY; dep = make_dep_table(count, distinct); /* [1] known DF-1166 site first: dedup loop into dpm_levels[8] */ smu7_setup_dpm_tables_v1_leg(&be->dpm_table.sclk_table, dep); printf("[1] smu7_setup_dpm_tables_v1 dedup: %u VBIOS entries -> %u distinct " "dpm_levels (array holds %d) -> %s\n", count, be->dpm_table.sclk_table.count, MAX_REGULAR_DPM_NUMBER, be->dpm_table.sclk_table.count <= MAX_REGULAR_DPM_NUMBER ? "IN BOUNDS (DF-1166 site does NOT fire for this input)" : "OVERFLOW (DF-1166 would fire)"); /* [2] the audited call: smu7_hwmgr.c:844-845 */ smu_get_voltage_dependency_table_ppt_v1(dep, (struct phm_ppt_v1_clock_voltage_dependency_table *) &be->odn_dpm_table.vdd_dependency_on_sclk); p_entries8 = (char *)&be->odn_dpm_table.vdd_dependency_on_sclk.entries[MAX_REGULAR_DPM_NUMBER]; p_end = (char *)(be + 1); printf("[2] odn copy wrote dep->count=%u records into entries[%d]\n", be->odn_dpm_table.vdd_dependency_on_sclk.count, MAX_REGULAR_DPM_NUMBER); if (count > MAX_REGULAR_DPM_NUMBER) { size_t oob = (count - MAX_REGULAR_DPM_NUMBER) * recsize; size_t past_alloc = (p_entries8 + oob > p_end) ? (size_t)(p_entries8 + oob - p_end) : 0; printf(" OOB bytes past entries[%d] : %zu\n", MAX_REGULAR_DPM_NUMBER - 1, oob); printf(" ...of which past end of backend-like object (%zu bytes): %zu\n", sizeof(*be), past_alloc); } printf(" vdd_dependency_on_mclk.count now = %u (was 0)\n", be->odn_dpm_table.vdd_dependency_on_mclk.count); printf(" odn_mclk_min_limit now = 0x%08x (was 0)\n", be->odn_dpm_table.odn_mclk_min_limit); printf(" min_vddc now = 0x%08x, max_vddc now = 0x%08x (were 0)\n", be->odn_dpm_table.min_vddc, be->odn_dpm_table.max_vddc); { unsigned smashed = count_smashed(be->canary, 64); printf(" canary words after odn_dpm_table smashed: %u/64\n", smashed); if (count > MAX_REGULAR_DPM_NUMBER && (smashed || be->odn_dpm_table.vdd_dependency_on_mclk.count != 0 || be->odn_dpm_table.odn_mclk_min_limit != 0)) { printf("OOB-WRITE CONFIRMED: smu_get_voltage_dependency_table_ppt_v1 wrote " "%zu attacker-controlled bytes out of bounds (vdd_dependency_on_mclk " "and/or later members of struct smu7_hwmgr corrupted)\n", (size_t)(count - MAX_REGULAR_DPM_NUMBER) * recsize); return 0; } if (count > MAX_REGULAR_DPM_NUMBER) { fprintf(stderr, "UNEXPECTED: overflow expected but no corruption observed\n"); return 1; } printf("in-bounds (count <= 8): no overflow, as expected\n"); } return 0; } |