/*
 * DF-4665 harness — verbatim kernel function + 1:1 struct layouts.
 *
 * Bug: sys/dev/drm/amd/powerplay/hwmgr/smu_helper.c:675-699
 *      smu_get_voltage_dependency_table_ppt_v1() has NO destination-size
 *      parameter: it writes allowed_dep_table->count records into whatever
 *      the caller points it at.
 *
 * Proven broken caller (the NEW leg; vega10 leg is DF-3703):
 *      smu7_hwmgr.c:844-845 / 856-857 (smu7_odn_initial_default_setting,
 *      reached from smu7_setup_default_dpm_tables:967 at driver load when
 *      OverDrive is enabled, and from smu7_odn_edit_dpm_table:4845 on
 *      PP_OD_RESTORE_DEFAULT_TABLE) casts
 *      &odn_table->vdd_dependency_on_sclk  (entries[MAX_REGULAR_DPM_NUMBER=8])
 *      to phm_ppt_v1_clock_voltage_dependency_table* and lets the helper
 *      copy count (= raw VBIOS ucNumEntries, u8, up to 255) records into it.
 *
 * count provenance: process_pptables_v1_0.c:436/468
 *      sclk_table->count = (uint32_t)tonga_table->ucNumEntries;  (no upper bound)
 *
 * This harness replicates, in order:
 *   [1] the DF-1166 site (smu7_setup_dpm_tables_v1 dedup loop into
 *       dpm_levels[8]) with a table of 16 entries but only 2 DISTINCT clocks
 *       -> stays in bounds (shows this input does NOT trip the known finding);
 *   [2] the smu7 ODN copy through the verbatim helper -> 16 x 28-byte records
 *       into entries[8] -> out-of-bounds heap write with VBIOS-controlled
 *       contents, corrupting vdd_dependency_on_mclk / odn_mclk_min_limit /
 *       min_vddc / max_vddc and the members that follow odn_dpm_table in
 *       struct smu7_hwmgr (here: canary region).
 *   [3] worst case count=255 -> (255-8)*28 = 6916 bytes past entries[7].
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>

/* ---- verbatim: hwmgr_ppt.h:31-54 ---- */
struct phm_ppt_v1_clock_voltage_dependency_record {
	uint32_t clk;
	uint8_t  vddInd;
	uint8_t  vddciInd;
	uint8_t  mvddInd;
	uint16_t vdd_offset;
	uint16_t vddc;
	uint16_t vddgfx;
	uint16_t vddci;
	uint16_t mvdd;
	uint8_t  phases;
	uint8_t  cks_enable;
	uint8_t  cks_voffset;
	uint32_t sclk_offset;
};

struct phm_ppt_v1_clock_voltage_dependency_table {
	uint32_t count;
	struct phm_ppt_v1_clock_voltage_dependency_record entries[1];
};

/* ---- verbatim: hardwaremanager.h:384-397 ---- */
struct phm_odn_performance_level {
	uint32_t clock;
	uint32_t vddc;
	int      enabled;	/* bool */
};

struct phm_odn_clock_levels {
	uint32_t size;
	uint32_t options;
	uint32_t flags;
	uint32_t num_of_pl;
	struct phm_odn_performance_level entries[8];
};

/* ---- verbatim: smu7_hwmgr.h:98-110 + 177-190 ---- */
#define MAX_REGULAR_DPM_NUMBER 8

struct smu7_dpm_level {
	int      enabled;	/* bool */
	uint32_t value;
	uint32_t param1;
};

struct smu7_single_dpm_table {
	uint32_t count;
	struct smu7_dpm_level dpm_levels[MAX_REGULAR_DPM_NUMBER];
};

struct smu7_dpm_table {
	struct smu7_single_dpm_table sclk_table;
	struct smu7_single_dpm_table mclk_table;
	struct smu7_single_dpm_table pcie_speed_table;
	struct smu7_single_dpm_table vddc_table;
	struct smu7_single_dpm_table vddci_table;
	struct smu7_single_dpm_table mvdd_table;
};

struct smu7_odn_clock_voltage_dependency_table {
	uint32_t count;
	struct phm_ppt_v1_clock_voltage_dependency_record entries[MAX_REGULAR_DPM_NUMBER];
};

struct smu7_odn_dpm_table {
	struct phm_odn_clock_levels odn_core_clock_dpm_levels;
	struct phm_odn_clock_levels odn_memory_clock_dpm_levels;
	struct smu7_odn_clock_voltage_dependency_table vdd_dependency_on_sclk;
	struct smu7_odn_clock_voltage_dependency_table vdd_dependency_on_mclk;
	uint32_t odn_mclk_min_limit;
	uint32_t min_vddc;
	uint32_t max_vddc;
};

/*
 * struct smu7_hwmgr prefix (smu7_hwmgr.h:203-206) + a canary region standing
 * in for the members that follow odn_dpm_table (voting_rights_clients[8] ...).
 */
struct smu7_backend_like {
	struct smu7_dpm_table dpm_table;
	struct smu7_dpm_table golden_dpm_table;
	struct smu7_odn_dpm_table odn_dpm_table;
	uint32_t canary[64];	/* stands for everything after odn_dpm_table */
};

#define CANARY 0xDEADBEEFU
#define EINVAL 22

/* pp_debug.h equivalent (userspace) */
#define PP_ASSERT_WITH_CODE(cond, msg, code) do {	\
	if (!(cond)) {					\
		fprintf(stderr, "amdgpu: [powerplay] %s\n", msg);	\
		code;					\
	}						\
} while (0)

/* ---- VERBATIM: smu_helper.c:675-699 ---- */
int smu_get_voltage_dependency_table_ppt_v1(
			const struct phm_ppt_v1_clock_voltage_dependency_table *allowed_dep_table,
			struct phm_ppt_v1_clock_voltage_dependency_table *dep_table)
{
	uint8_t i = 0;
	PP_ASSERT_WITH_CODE((0 != allowed_dep_table->count),
				"Voltage Lookup Table empty",
				return -EINVAL);

	dep_table->count = allowed_dep_table->count;
	for (i=0; i<dep_table->count; i++) {
		dep_table->entries[i].clk = allowed_dep_table->entries[i].clk;
		dep_table->entries[i].vddInd = allowed_dep_table->entries[i].vddInd;
		dep_table->entries[i].vdd_offset = allowed_dep_table->entries[i].vdd_offset;
		dep_table->entries[i].vddc = allowed_dep_table->entries[i].vddc;
		dep_table->entries[i].vddgfx = allowed_dep_table->entries[i].vddgfx;
		dep_table->entries[i].vddci = allowed_dep_table->entries[i].vddci;
		dep_table->entries[i].mvdd = allowed_dep_table->entries[i].mvdd;
		dep_table->entries[i].phases = allowed_dep_table->entries[i].phases;
		dep_table->entries[i].cks_enable = allowed_dep_table->entries[i].cks_enable;
		dep_table->entries[i].cks_voffset = allowed_dep_table->entries[i].cks_voffset;
	}

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

#define UNUSED_EOL_MARKER 0

/* source "VBIOS-parsed" dependency table (heap, count = raw ucNumEntries) */
static struct phm_ppt_v1_clock_voltage_dependency_table *make_dep_table(unsigned count,
		unsigned distinct_clocks)
{
	struct phm_ppt_v1_clock_voltage_dependency_table *t;
	unsigned i;

	t = calloc(1, sizeof(*t) + sizeof(struct phm_ppt_v1_clock_voltage_dependency_record) * (count + 1));
	if (!t) { perror("calloc"); exit(1); }
	t->count = count;
	for (i = 0; i < count; i++) {
		/* monotonic runs of equal clock values: `distinct` levels total,
		 * so the DF-1166 dedup loop appends only `distinct` dpm_levels —
		 * attacker controls every field (VBIOS records) */
		t->entries[i].clk = 100000 + ((uint64_t)i * distinct_clocks / count) * 2500;
		t->entries[i].vddInd = (uint8_t)i;
		t->entries[i].vddciInd = (uint8_t)(i + 1);
		t->entries[i].mvddInd = (uint8_t)(i + 2);
		t->entries[i].vdd_offset = (uint16_t)(0x1100 + i);
		t->entries[i].vddc = (uint16_t)(900 + i);
		t->entries[i].vddgfx = (uint16_t)(950 + i);
		t->entries[i].vddci = (uint16_t)(1000 + i);
		t->entries[i].mvdd = (uint16_t)(1050 + i);
		t->entries[i].phases = 1;
		t->entries[i].cks_enable = (uint8_t)(i & 1);
		t->entries[i].cks_voffset = 0;
		t->entries[i].sclk_offset = 0x41410000u + i;
	}
	return t;
}

/* the DF-1166 site, verbatim dedup loop (smu7_hwmgr.c:784-810 essence) */
static void smu7_setup_dpm_tables_v1_leg(struct smu7_single_dpm_table *tab,
		const struct phm_ppt_v1_clock_voltage_dependency_table *dep)
{
	uint32_t i;

	tab->count = 0;
	for (i = 0; i < dep->count; i++) {
		if (i == 0 || tab->dpm_levels[tab->count - 1].value != dep->entries[i].clk) {
			tab->dpm_levels[tab->count].value = dep->entries[i].clk;
			tab->dpm_levels[tab->count].enabled = (i == 0) ? 1 : 0;
			tab->count++;
		}
	}
}

static unsigned count_smashed(const uint32_t *c, unsigned n)
{
	unsigned i, s = 0;
	for (i = 0; i < n; i++)
		if (c[i] != CANARY)
			s++;
	return s;
}

int main(int argc, char **argv)
{
	unsigned count = (argc > 1) ? (unsigned)atoi(argv[1]) : 16;
	unsigned distinct = (argc > 2) ? (unsigned)atoi(argv[2]) : 2;
	struct smu7_backend_like *be;
	struct phm_ppt_v1_clock_voltage_dependency_table *dep;
	size_t recsize = sizeof(struct phm_ppt_v1_clock_voltage_dependency_record);
	char *p_end, *p_entries8;

	printf("record size = %zu bytes, MAX entries = %d\n", recsize, MAX_REGULAR_DPM_NUMBER);

	be = calloc(1, sizeof(*be));
	for (size_t k = 0; k < 64; k++)
		be->canary[k] = CANARY;
	dep = make_dep_table(count, distinct);

	/* [1] known DF-1166 site first: dedup loop into dpm_levels[8] */
	smu7_setup_dpm_tables_v1_leg(&be->dpm_table.sclk_table, dep);
	printf("[1] smu7_setup_dpm_tables_v1 dedup: %u VBIOS entries -> %u distinct "
	       "dpm_levels (array holds %d) -> %s\n",
	       count, be->dpm_table.sclk_table.count, MAX_REGULAR_DPM_NUMBER,
	       be->dpm_table.sclk_table.count <= MAX_REGULAR_DPM_NUMBER
	           ? "IN BOUNDS (DF-1166 site does NOT fire for this input)"
	           : "OVERFLOW (DF-1166 would fire)");

	/* [2] the audited call: smu7_hwmgr.c:844-845 */
	smu_get_voltage_dependency_table_ppt_v1(dep,
	    (struct phm_ppt_v1_clock_voltage_dependency_table *)
	    &be->odn_dpm_table.vdd_dependency_on_sclk);

	p_entries8 = (char *)&be->odn_dpm_table.vdd_dependency_on_sclk.entries[MAX_REGULAR_DPM_NUMBER];
	p_end = (char *)(be + 1);

	printf("[2] odn copy wrote dep->count=%u records into entries[%d]\n",
	       be->odn_dpm_table.vdd_dependency_on_sclk.count, MAX_REGULAR_DPM_NUMBER);
	if (count > MAX_REGULAR_DPM_NUMBER) {
		size_t oob = (count - MAX_REGULAR_DPM_NUMBER) * recsize;
		size_t past_alloc = (p_entries8 + oob > p_end) ? (size_t)(p_entries8 + oob - p_end) : 0;
		printf("    OOB bytes past entries[%d] : %zu\n", MAX_REGULAR_DPM_NUMBER - 1, oob);
		printf("    ...of which past end of backend-like object (%zu bytes): %zu\n",
		       sizeof(*be), past_alloc);
	}
	printf("    vdd_dependency_on_mclk.count now = %u (was 0)\n",
	       be->odn_dpm_table.vdd_dependency_on_mclk.count);
	printf("    odn_mclk_min_limit now = 0x%08x (was 0)\n",
	       be->odn_dpm_table.odn_mclk_min_limit);
	printf("    min_vddc now = 0x%08x, max_vddc now = 0x%08x (were 0)\n",
	       be->odn_dpm_table.min_vddc, be->odn_dpm_table.max_vddc);

	{
		unsigned smashed = count_smashed(be->canary, 64);
		printf("    canary words after odn_dpm_table smashed: %u/64\n", smashed);
		if (count > MAX_REGULAR_DPM_NUMBER &&
		    (smashed ||
		     be->odn_dpm_table.vdd_dependency_on_mclk.count != 0 ||
		     be->odn_dpm_table.odn_mclk_min_limit != 0)) {
			printf("OOB-WRITE CONFIRMED: smu_get_voltage_dependency_table_ppt_v1 wrote "
			       "%zu attacker-controlled bytes out of bounds (vdd_dependency_on_mclk "
			       "and/or later members of struct smu7_hwmgr corrupted)\n",
			       (size_t)(count - MAX_REGULAR_DPM_NUMBER) * recsize);
			return 0;
		}
		if (count > MAX_REGULAR_DPM_NUMBER) {
			fprintf(stderr, "UNEXPECTED: overflow expected but no corruption observed\n");
			return 1;
		}
		printf("in-bounds (count <= 8): no overflow, as expected\n");
	}
	return 0;
}
