DF-3614 / 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 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 | /* * DF-3614 โ heap OOB write past SISLANDS_SMC_STATETABLE.lowSMIO[32] via * untrimmed SVI2 VDDCI voltage table (radeon si_dpm.c:4046-4047). * This is the radeon twin of the "lowSMIO sink" half of amdgpu DF-3585 * (the entries[] half is radeon DF-1136 โ separate sink, same root cause). * * Reachability (all VBIOS-controlled): * si_dpm.c:7001 pi->voltage_control = VDDC has GPIO_LUT (true) * si_dpm.c:7020 vddci_control = VDDCI has GPIO_LUT (false) * si_dpm.c:7021 si_pi->vddci_control_svi2 = VDDCI has SVID2 (true) * si_dpm.c:7011 voltage_control_svi2 = VDDC SVID2 (false) * * si_construct_voltage_tables: * si_dpm.c:3967-3976 VDDC GPIO table fetched and TRIMMED to 32 * si_dpm.c:3998-4004 vddci_control_svi2 -> si_get_svi2_voltage_table() * -> vddci_voltage_table.count = * vddci_dependency_on_mclk.count (u8 VBIOS, up * to 255) โ NO trim (entries[] overflow is * DF-1136; the COUNT itself stays 33..255) * * si_init_smc_table -> si_populate_smc_voltage_tables: * si_dpm.c:4058 if (si_pi->voltage_control_svi2) ... else { // taken: * si_dpm.c:4079 if (eg_pi->vddci_voltage_table.count) { * si_dpm.c:4080 si_populate_smc_voltage_table(rdev, * &eg_pi->vddci_voltage_table, table); * * si_populate_smc_voltage_table: * si_dpm.c:4046 for (i = 0; i < voltage_table->count; i++) * si_dpm.c:4047 table->lowSMIO[i] |= * cpu_to_be32(voltage_table->entries[i].smio_low); * * lowSMIO is uint32_t[32] (sislands_smc.h:215) inside the packed * SISLANDS_SMC_STATETABLE embedded in the kzalloc'd struct si_power_info * (si_dpm.h:196). With count in 33..255 the OR-write walks up to 1020 * bytes past lowSMIO[0] into voltageMaskTable, phaseMaskTable, dpm2Params, * initialState/ACPIState/ULVState/driverState and dpmLevels[] โ corrupting * the SMC-bound power configuration that is uploaded to the GPU power * management microcontroller at si_dpm.c:4779-4781. * * Harness: verbatim packed struct replicas from sislands_smc.h; runs the * exact loop with count=255, dumps which fields got corrupted, then runs * the fixed loop (bounded by SISLANDS_MAX_NO_VREG_STEPS) showing the tail * untouched. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stddef.h> #pragma pack(push, 1) #define SISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE 16 #define SISLANDS_SMC_VOLTAGEMASK_MAX 4 #define SISLANDS_MAX_NO_VREG_STEPS 32 typedef uint8_t uint8; typedef uint16_t uint16; typedef uint32_t uint32; struct SISLANDS_SMC_VOLTAGEMASKTABLE { uint32 lowMask[SISLANDS_SMC_VOLTAGEMASK_MAX]; }; typedef struct SISLANDS_SMC_VOLTAGEMASKTABLE SISLANDS_SMC_VOLTAGEMASKTABLE; struct PP_SIslands_DPM2Parameters { uint32 TDPLimit, NearTDPLimit, SafePowerLimit, PowerBoostLimit, MinLimitDelta; }; typedef struct PP_SIslands_DPM2Parameters PP_SIslands_DPM2Parameters; struct PP_SIslands_Dpm2PerfLevel { uint8 MaxPS, TgtAct, MaxPS_StepInc, MaxPS_StepDec, PSSamplingTime, NearTDPDec, AboveSafeInc, BelowSafeInc, PSDeltaLimit, PSDeltaWin; uint16 PwrEfficiencyRatio; uint8 Reserved[4]; }; typedef struct PP_SIslands_Dpm2PerfLevel PP_SIslands_Dpm2PerfLevel; struct SISLANDS_SMC_SCLK_VALUE { uint32 vCG_SPLL_FUNC_CNTL, vCG_SPLL_FUNC_CNTL_2, vCG_SPLL_FUNC_CNTL_3, vCG_SPLL_FUNC_CNTL_4, vCG_SPLL_SPREAD_SPECTRUM, vCG_SPLL_SPREAD_SPECTRUM_2, sclk_value; }; typedef struct SISLANDS_SMC_SCLK_VALUE SISLANDS_SMC_SCLK_VALUE; struct SISLANDS_SMC_MCLK_VALUE { uint32 vMPLL_FUNC_CNTL, vMPLL_FUNC_CNTL_1, vMPLL_FUNC_CNTL_2, vMPLL_AD_FUNC_CNTL, vMPLL_DQ_FUNC_CNTL, vMCLK_PWRMGT_CNTL, vDLL_CNTL, vMPLL_SS, vMPLL_SS2, mclk_value; }; typedef struct SISLANDS_SMC_MCLK_VALUE SISLANDS_SMC_MCLK_VALUE; struct SISLANDS_SMC_VOLTAGE_VALUE { uint16 value; uint8 index, phase_settings; }; typedef struct SISLANDS_SMC_VOLTAGE_VALUE SISLANDS_SMC_VOLTAGE_VALUE; struct SISLANDS_SMC_HW_PERFORMANCE_LEVEL { uint8 ACIndex, displayWatermark, gen2PCIE, UVDWatermark, VCEWatermark, strobeMode, mcFlags, padding; uint32 aT, bSP; SISLANDS_SMC_SCLK_VALUE sclk; SISLANDS_SMC_MCLK_VALUE mclk; SISLANDS_SMC_VOLTAGE_VALUE vddc, mvdd, vddci, std_vddc; uint8 hysteresisUp, hysteresisDown, stateFlags, arbRefreshState; uint32 SQPowerThrottle, SQPowerThrottle_2, MaxPoweredUpCU; SISLANDS_SMC_VOLTAGE_VALUE high_temp_vddc, low_temp_vddc; uint32 reserved[2]; PP_SIslands_Dpm2PerfLevel dpm2; }; typedef struct SISLANDS_SMC_HW_PERFORMANCE_LEVEL SISLANDS_SMC_HW_PERFORMANCE_LEVEL; struct SISLANDS_SMC_SWSTATE { uint8 flags, levelCount, padding2, padding3; SISLANDS_SMC_HW_PERFORMANCE_LEVEL levels[1]; }; typedef struct SISLANDS_SMC_SWSTATE SISLANDS_SMC_SWSTATE; struct SISLANDS_SMC_STATETABLE { uint8 thermalProtectType, systemFlags, maxVDDCIndexInPPTable, extraFlags; uint32 lowSMIO[SISLANDS_MAX_NO_VREG_STEPS]; SISLANDS_SMC_VOLTAGEMASKTABLE voltageMaskTable; SISLANDS_SMC_VOLTAGEMASKTABLE phaseMaskTable; PP_SIslands_DPM2Parameters dpm2Params; SISLANDS_SMC_SWSTATE initialState; SISLANDS_SMC_SWSTATE ACPIState; SISLANDS_SMC_SWSTATE ULVState; SISLANDS_SMC_SWSTATE driverState; SISLANDS_SMC_HW_PERFORMANCE_LEVEL dpmLevels[SISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE - 1]; }; typedef struct SISLANDS_SMC_STATETABLE SISLANDS_SMC_STATETABLE; #pragma pack(pop) struct PP_SIslands_PAPMParameters { uint32_t NearTDPLimitTherm, NearTDPLimitPAPM, PlatformPowerLimit, dGPU_T_Limit, dGPU_T_Warning, dGPU_T_Hysteresis; }; /* tail of struct si_power_info (si_dpm.h:195-198): smc_mc_reg_table precedes * smc_statetable and papm_parm follows it โ one kzalloc'd allocation */ /* the actual replica used: one allocation holding statetable + what follows */ struct alloc { unsigned char pre[64]; /* preceding si_power_info bytes */ SISLANDS_SMC_STATETABLE smc_statetable; struct PP_SIslands_PAPMParameters papm_parm; uint32_t sram_end, state_table_start; }; struct atom_voltage_table { uint32_t smio; }; /* only what the loop reads */ /* si_dpm.c:4040-4048 verbatim */ static void si_populate_smc_voltage_table(const struct atom_voltage_table *voltage_table, uint32_t count, SISLANDS_SMC_STATETABLE *table) { unsigned int i; for (i = 0; i < count; i++) table->lowSMIO[i] |= voltage_table->smio; /* cpu_to_be32() is a no-op on BE-host; LE host writes value unchanged */ } static size_t first_diff(const unsigned char *a, const unsigned char *b, size_t n) { for (size_t i = 0; i < n; i++) if (a[i] != b[i]) return i; return (size_t)-1; } int main(void) { struct alloc *a = calloc(1, sizeof(*a)); struct alloc before; uint8_t count = 255; /* vddci_dependency_on_mclk.count from VBIOS */ struct atom_voltage_table src = { .smio = 0xA5A55A5A }; printf("== DF-3614 harness: VDDCI SVI2 dependency table count=%u (no trim) ==\n\n", count); printf("sizeof(SISLANDS_SMC_HW_PERFORMANCE_LEVEL) = %zu\n", sizeof(SISLANDS_SMC_HW_PERFORMANCE_LEVEL)); printf("sizeof(SISLANDS_SMC_SWSTATE) = %zu\n", sizeof(SISLANDS_SMC_SWSTATE)); printf("sizeof(SISLANDS_SMC_STATETABLE) = %zu\n", sizeof(SISLANDS_SMC_STATETABLE)); printf("offsetof(lowSMIO) = %zu, lowSMIO spans %zu..%zu (32 entries x 4)\n", offsetof(SISLANDS_SMC_STATETABLE, lowSMIO), offsetof(SISLANDS_SMC_STATETABLE, lowSMIO), offsetof(SISLANDS_SMC_STATETABLE, lowSMIO) + 32 * 4); printf("last byte touched by buggy loop: lowSMIO[%u] at offset %zu (STATETABLE size %zu)\n\n", count - 1, offsetof(SISLANDS_SMC_STATETABLE, lowSMIO) + (size_t)(count - 1) * 4, sizeof(SISLANDS_SMC_STATETABLE)); memcpy(&before, a, sizeof(before)); printf("[A] Buggy loop (si_dpm.c:4046-4047, count=%u):\n", count); si_populate_smc_voltage_table(&src, count, &a->smc_statetable); printf(" lowSMIO[0]=0x%08x lowSMIO[31]=0x%08x lowSMIO[32]=0x%08x (OOB start)\n", a->smc_statetable.lowSMIO[0], a->smc_statetable.lowSMIO[31], a->smc_statetable.lowSMIO[32]); printf(" voltageMaskTable.lowMask[0] (VDDC mask) = 0x%08x CORRUPTED\n", a->smc_statetable.voltageMaskTable.lowMask[0]); printf(" phaseMaskTable.lowMask[0] = 0x%08x CORRUPTED\n", a->smc_statetable.phaseMaskTable.lowMask[0]); printf(" dpm2Params.TDPLimit = 0x%08x CORRUPTED\n", a->smc_statetable.dpm2Params.TDPLimit); printf(" initialState.levels[0].sclk.sclk_value = 0x%08x CORRUPTED\n", a->smc_statetable.initialState.levels[0].sclk.sclk_value); printf(" driverState.flags/levelCount: before 0x%02x/0x%02x -> after 0x%02x/0x%02x\n", before.smc_statetable.driverState.flags, before.smc_statetable.driverState.levelCount, a->smc_statetable.driverState.flags, a->smc_statetable.driverState.levelCount); printf(" papm_parm.NearTDPLimitTherm (past STATETABLE, still in si_power_info) = 0x%08x\n", a->papm_parm.NearTDPLimitTherm); size_t off = offsetof(struct alloc, smc_statetable); size_t d = first_diff((unsigned char *)&before, (unsigned char *)a, sizeof(*a)); printf(" first corrupted byte at allocation offset %zu; last touched offset %zu " "(all inside one kzalloc'd si_power_info: contained corruption of SMC-bound config)\n", d, off + offsetof(SISLANDS_SMC_STATETABLE, lowSMIO) + (size_t)(count - 1) * 4 + 3); printf(" >>> In kernel: corrupted voltageMaskTable/dpm2Params/state levels are\n" " copied to the GPU SMC (si_dpm.c:4779-4781) -> wrong voltage masks /\n" " TDP limits / state clocks programmed on the GPU <<<\n"); memset(a, 0, sizeof(*a)); printf("\n[B] Fixed loop (fix.diff: `i < count && i < SISLANDS_MAX_NO_VREG_STEPS`):\n"); unsigned int i; for (i = 0; i < count && i < SISLANDS_MAX_NO_VREG_STEPS; i++) a->smc_statetable.lowSMIO[i] |= src.smio; printf(" lowSMIO[0]=0x%08x lowSMIO[31]=0x%08x lowSMIO[32]=0x%08x (untouched)\n", a->smc_statetable.lowSMIO[0], a->smc_statetable.lowSMIO[31], (unsigned)a->smc_statetable.lowSMIO[32]); printf(" voltageMaskTable.lowMask[0]=0x%08x dpm2Params.TDPLimit=0x%08x (intact)\n", a->smc_statetable.voltageMaskTable.lowMask[0], a->smc_statetable.dpm2Params.TDPLimit); free(a); return 0; } |