โฌข DragonFlyBSD Kernel Audit
DF-3614 / harness.c
โ† back to finding โ†“ download raw
/*
 * 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;
}