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