DF-3619 / ci_svi2_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 | /* DF-3619 โ logic/geometry proof for the ci_get_svi2_voltage_table heap OOB write. * * Reproduces, with the exact struct definitions lifted from the tree * (sys/dev/drm/amd/amdgpu/ci_dpm.h, amdgpu_atombios.h, amdgpu_dpm.h), * the write pattern of ci_get_svi2_voltage_table() (ci_dpm.c:2252-2271): * * voltage_table->count = voltage_dependency_table->count; // u8 from VBIOS, up to 255 * for (i = 0; i < voltage_table->count; i++) { * voltage_table->entries[i].value = ...entries[i].v; // attacker u16 * voltage_table->entries[i].smio_low = 0; * } * * into struct atom_voltage_table { u32 count, mask_low, phase_delay; * struct atom_voltage_table_entry entries[32]; } * * embedded three times (vddc/vddci/mvdd) near the tail of the kzalloc'd * struct ci_power_info. The trim to SMU7_MAX_LEVELS_{VDDC,VDDCI,MVDD} in * ci_construct_voltage_tables (ci_dpm.c:2292/2310/2328) happens AFTER the * copy and cannot undo the overflow. * * Build: cc -O2 -Wall -o ci_svi2_overflow ci_svi2_overflow.c * Run: ./ci_svi2_overflow */ #include <stdio.h> #include <stddef.h> #include <stdint.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> /* ---- verbatim from sys/dev/drm/amd/amdgpu/amdgpu_atombios.h:119-133 ---- */ #define MAX_VOLTAGE_ENTRIES 32 struct atom_voltage_table_entry { uint16_t value; uint32_t smio_low; }; struct atom_voltage_table { uint32_t count; uint32_t mask_low; uint32_t phase_delay; struct atom_voltage_table_entry entries[MAX_VOLTAGE_ENTRIES]; }; /* ---- verbatim (types/names/order) from sys/dev/drm/amd/amdgpu/ci_dpm.h:39-51,90-96 ---- */ enum amdgpu_pcie_gen { AMDGPU_PCIE_GEN1 = 0, AMDGPU_PCIE_GEN2 = 1, AMDGPU_PCIE_GEN3 = 2, AMDGPU_PCIE_GEN_INVALID = 0xffff }; struct ci_pl { uint32_t mclk; uint32_t sclk; enum amdgpu_pcie_gen pcie_gen; uint16_t pcie_lane; }; struct ci_ulv_parm { bool supported; uint32_t cg_ulv_parameter; uint32_t volt_change_delay; struct ci_pl pl; }; struct ci_ps { uint16_t performance_level_count; bool dc_compatible; uint32_t sclk_t; struct ci_pl performance_levels[2]; }; /* ---- verbatim from sys/dev/drm/amd/amdgpu/amdgpu_dpm.h (struct amdgpu_ps) ---- */ enum amd_vce_level { AMDGPU_VCE_LEVEL_0 = 0 }; struct amdgpu_ps { uint32_t caps; uint32_t class; uint32_t class2; uint32_t vclk; uint32_t dclk; uint32_t evclk; uint32_t ecclk; bool vce_active; enum amd_vce_level vce_level; void *ps_priv; }; /* ---- the tail of struct ci_power_info AFTER mvdd_voltage_table, member-for-member * from sys/dev/drm/amd/amdgpu/ci_dpm.h:233-297. The members BEFORE the three * voltage tables (dpm tables, smc tables ~9KB, mc_reg_table) are collapsed * into `head`; only distances from the voltage tables onward matter. ---- */ struct ci_power_info_tail { struct ci_ulv_parm ulv; /* 233 */ uint32_t power_containment_features; /* 234 */ const struct ci_pt_defaults_d *powertune_defaults; /* 235 โ pointer! */ uint32_t dte_tj_offset; /* 236 */ struct { int32_t a,b,c; } thermal_temp_setting; /* 238 */ struct { uint32_t uvd,vce,acp,samu,sclk,mclk,pcie; } dpm_level_enable_mask; /* 239 */ uint32_t need_update_smu7_dpm_table; /* 240 */ uint32_t sclk_dpm_key_disabled, mclk_dpm_key_disabled; /* 241-242 */ uint32_t pcie_dpm_key_disabled, thermal_sclk_dpm_enabled;/* 243-244 */ struct { uint16_t max, min; } pcie_gen_performance; /* 245 */ struct { uint16_t max, min; } pcie_lane_performance; /* 246 */ struct { uint16_t max, min; } pcie_gen_powersaving; /* 247 */ struct { uint16_t max, min; } pcie_lane_powersaving; /* 248 */ uint32_t activity_target[8]; /* 249 */ uint32_t mclk_activity_target; /* 250 */ uint32_t low_sclk_interrupt_t; /* 251 */ uint32_t last_mclk_dpm_enable_mask; /* 252 */ uint32_t sys_pcie_mask; /* 253 */ bool caps_power_containment, caps_cac, caps_sq_ramping, caps_db_ramping, /* 255-260 */ caps_td_ramping, caps_tcp_ramping, caps_fps, caps_sclk_ds, caps_sclk_ss_support, caps_mclk_ss_support, caps_uvd_dpm, caps_vce_dpm, caps_samu_dpm, caps_acp_dpm, caps_automatic_dc_transition, caps_sclk_throttle_low_notification, caps_dynamic_ac_timing, caps_od_fuzzy_fan_control_support; bool thermal_protection, pcie_performance_request, dynamic_ss, dll_default_on, /* 274-276 */ cac_enabled, uvd_enabled, battery_state, pspp_notify_required, enable_bapm_feature, enable_tdc_limit_feature, enable_pkg_pwr_tracking_feature; bool use_pcie_performance_levels, use_pcie_powersaving_levels, uvd_power_gated; /* 285-287 */ struct amdgpu_ps current_rps; /* 289 */ struct ci_ps current_ps; /* 290 */ struct amdgpu_ps requested_rps; /* 291 */ struct ci_ps requested_ps; /* 292 */ bool fan_ctrl_is_in_default_mode, fan_is_controlled_by_smc; /* 294-295 */ uint32_t t_min; /* 296 */ uint32_t fan_ctrl_default_mode; /* 297 */ }; struct ci_pt_defaults_d; /* fwd decl only โ layout irrelevant, pointer member */ /* Mirror of the last three members of the real struct (ci_dpm.h:230-232) + tail */ struct ci_pi_voltage_region { struct atom_voltage_table vddc_voltage_table; struct atom_voltage_table vddci_voltage_table; struct atom_voltage_table mvdd_voltage_table; struct ci_power_info_tail tail; }; /* the exact sink, copied from ci_dpm.c:2261-2268 (value source = VBIOS u16) */ static void ci_get_svi2_voltage_table(uint8_t vbios_count, uint16_t vb_v, struct atom_voltage_table *voltage_table) { int i; voltage_table->mask_low = 0; voltage_table->phase_delay = 0; voltage_table->count = vbios_count; for (i = 0; i < (int)voltage_table->count; i++) { voltage_table->entries[i].value = vb_v; voltage_table->entries[i].smio_low = 0; } } int main(void) { struct ci_pi_voltage_region *pi = calloc(1, sizeof(*pi)); size_t sz = sizeof(*pi); size_t off_vddc = offsetof(struct ci_pi_voltage_region, vddc_voltage_table); size_t off_vddci = offsetof(struct ci_pi_voltage_region, vddci_voltage_table); size_t off_mvdd = offsetof(struct ci_pi_voltage_region, mvdd_voltage_table); size_t ent = sizeof(struct atom_voltage_table_entry); printf("sizeof(atom_voltage_table)=%zu entries[] capacity=%d entry=%zu bytes\n", sizeof(struct atom_voltage_table), MAX_VOLTAGE_ENTRIES, ent); printf("offsets in ci_power_info region: vddc=%zu vddci=%zu mvdd=%zu tail_after_mvdd=%zu total=%zu\n", off_vddc, off_vddci, off_mvdd, sizeof(struct ci_power_info_tail), sz); /* worst case VBIOS count: u8 -> 255 (amdgpu_parse_clk_voltage_dep_table, amdgpu_dpm.c:285-302) */ struct { const char *name; size_t base; } v[3] = { { "vddc_voltage_table (ci_dpm.c:2285, SVID2 VDDC)", off_vddc }, { "vddci_voltage_table (ci_dpm.c:2303, SVID2 VDDCI)", off_vddci }, { "mvdd_voltage_table (ci_dpm.c:2321, SVID2 MVDD)", off_mvdd }, }; for (int k = 0; k < 3; k++) { size_t entries_end = v[k].base + offsetof(struct atom_voltage_table, entries) + MAX_VOLTAGE_ENTRIES * ent; size_t worst = v[k].base + offsetof(struct atom_voltage_table, entries) + 255 * ent; /* count = 255 */ size_t past_entries = worst - entries_end; size_t past_object = (worst > sz) ? worst - sz : 0; size_t min_count_past_entries = MAX_VOLTAGE_ENTRIES + 1; size_t min_count_past_object = (sz - v[k].base - offsetof(struct atom_voltage_table, entries)) / ent + 1; printf("\n%s\n base=%zu entries[32] ends at %zu; count=255 writes to %zu\n" " -> %zu bytes past entries[32]; %zu bytes past END of ci_power_info alloc\n" " -> OOB past entries[] begins at count=%d; past object end at count>=%zu\n", v[k].name, v[k].base, entries_end, worst, past_entries, past_object, (int)min_count_past_entries, min_count_past_object); } /* live demonstration with a guard object after the region */ { struct { struct ci_pi_voltage_region pi; uint8_t guard[64]; } *wrap = calloc(1, sizeof(*wrap)); memset(wrap->guard, 0xAA, sizeof(wrap->guard)); ci_get_svi2_voltage_table(255, 0xBEEF, &wrap->pi.mvdd_voltage_table); size_t first_dirty = 0, n = 0; for (size_t i = 0; i < sizeof(wrap->guard); i++) if (wrap->guard[i] != 0xAA) { if (!n) first_dirty = i; n++; } printf("\nlive mvdd-variant run (count=255, value=0xBEEF):\n" " guard bytes corrupted behind object: %zu (first at guard+%zu)\n" " powertune_defaults now = 0x%016llx (was NULL)\n", n, first_dirty, (unsigned long long)(uintptr_t)wrap->pi.tail.powertune_defaults); free(wrap); } free(pi); return 0; } |