/*
 * DF-3585 harness -- heap OOB write in si_get_svi2_voltage_table()
 *                    (sys/dev/drm/amd/amdgpu/si_dpm.c:4398-4417)
 *                    plus its second sink si_populate_smc_voltage_table()
 *                    (sys/dev/drm/amd/amdgpu/si_dpm.c:4499-4507)
 *
 * The DragonFly QEMU guest has no AMD GPU (virtio-only), so this harness
 * replicates the exact kernel data structures and the exact kernel loops
 * (copied verbatim, only renamed) and proves the out-of-bounds write
 * arithmetic that a crafted VBIOS drives on a real SI (Tahiti/Verde/...)
 * card during si_dpm_enable() -> si_construct_voltage_tables().
 *
 * Struct definitions copied verbatim from:
 *   sys/dev/drm/amd/amdgpu/amdgpu_atombios.h:119-133  (atom_voltage_table)
 *   sys/dev/drm/amd/amdgpu/sislands_smc.h:207-224      (SISLANDS_SMC_STATETABLE lowSMIO[32])
 *   sys/dev/drm/amd/amdgpu/amdgpu_dpm.h                (clock_voltage_dependency_table)
 * Loop bodies copied verbatim from:
 *   sys/dev/drm/amd/amdgpu/si_dpm.c:4407-4414  (si_get_svi2_voltage_table)
 *   sys/dev/drm/amd/amdgpu/si_dpm.c:4503-4506  (si_populate_smc_voltage_table)
 *
 * Three demonstrations:
 *   A) canary  : entries[] overflow corrupts everything after vddc_voltage_table
 *                inside the (single kalloc'd) si_power_info-style object,
 *                including vddci_voltage_table.count with a BIOS-chosen u16,
 *                and a ps_priv-style pointer slot.
 *   B) guard   : victim placed at end of a page followed by PROT_NONE ->
 *                SIGSEGV => the write leaves the object's mapping
 *                (kernel: crosses the end of the si_power_info heap object).
 *   C) lowSMIO : vddci SVI2 table with count=255 (mixed GPIO-VDDC/SVI2-VDDCI
 *                config) drives si_populate_smc_voltage_table() writes past
 *                lowSMIO[32] in the SMC statetable.
 *   D) fix     : the same loops with the proposed clamp -> no corruption
 *                (validates fix.diff at logic level).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>

/* ---- verbatim: 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: amdgpu_dpm.h struct amdgpu_clock_voltage_dependency_table ---- */
struct amdgpu_clock_voltage_dependency_entry { uint32_t clk; uint16_t v; };
struct clk_voltage_dep_table {
	uint32_t count;
	struct amdgpu_clock_voltage_dependency_entry *entries;
};

/* ---- verbatim layout of the tail of struct evergreen_power_info
 *      (si_dpm.h:668-679) that follows vddc_voltage_table in the single
 *      kzalloc'd struct si_power_info (si_dpm.c:7311) ---- */
struct eg_tail_replica {
	struct atom_voltage_table vddc_voltage_table;   /* the overflow SOURCE */
	struct atom_voltage_table vddci_voltage_table;  /* first victim */
	uint32_t     arb_registers[8];                  /* bootup_arb_registers */
	void        *requested_rps_ps_priv;             /* amdgpu_ps::ps_priv-style pointer slot */
	unsigned char further_heap[2048];               /* rest of si_power_info */
};

#define SI_DPM_C_4407_4414 1   /* marker: loop copied from si_dpm.c:4407-4414 */
/* verbatim rename of si_get_svi2_voltage_table() (si_dpm.c:4398-4417) */
static int si_get_svi2_voltage_table_rep(struct clk_voltage_dep_table *dep,
					 struct atom_voltage_table *voltage_table)
{
	uint32_t i;

	if (dep == NULL)
		return -1;

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

	voltage_table->count = dep->count;                 /* si_dpm.c:4410 -- BIOS u8, up to 255, NO CLAMP */
	for (i = 0; i < voltage_table->count; i++) {       /* si_dpm.c:4411 */
		voltage_table->entries[i].value = dep->entries[i].v;   /* si_dpm.c:4412 OOB WRITE for i>=32 */
		voltage_table->entries[i].smio_low = 0;                /* si_dpm.c:4413 OOB WRITE for i>=32 */
	}

	return 0;
}

/* verbatim rename of si_populate_smc_voltage_table() (si_dpm.c:4499-4507) */
struct statetable_rep {
	uint8_t  thermalProtectType, systemFlags, maxVDDCIndexInPPTable, extraFlags;
	uint32_t lowSMIO[32];            /* sislands_smc.h:215  SISLANDS_MAX_NO_VREG_STEPS = 32 */
	uint32_t tail[768];              /* rest of SISLANDS_SMC_STATETABLE (voltageMaskTable,
					  dpm2Params, initialState/ACPI/ULV/driverState, dpmLevels[15])
					  then papm_parm + fan fields to end of si_power_info */
};
static void si_populate_smc_voltage_table_rep(const struct atom_voltage_table *voltage_table,
					      struct statetable_rep *table)
{
	unsigned int i;

	for (i = 0; i < voltage_table->count; i++)          /* si_dpm.c:4505 -- count up to 255 */
		table->lowSMIO[i] |= voltage_table->entries[i].smio_low;  /* si_dpm.c:4506 OOB R-M-W for i>=32 */
}

/* ---- fix from fix.diff (validated in part D) ---- */
static int si_get_svi2_voltage_table_fixed(struct clk_voltage_dep_table *dep,
					   struct atom_voltage_table *voltage_table)
{
	uint32_t i;

	if (dep == NULL)
		return -1;
	if (dep->count == 0 || dep->count > MAX_VOLTAGE_ENTRIES)   /* THE FIX: clamp */
		return -1;

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

/* dependency table as built by amdgpu_parse_clk_voltage_dep_table()
 * (amdgpu_dpm.c:282-305): count = BIOS ucNumEntries (raw u8), entries
 * kcalloc'd to exactly that count. We simulate the worst legal u8: 255. */
static struct clk_voltage_dep_table *bios_dep_table(uint32_t n)
{
	struct clk_voltage_dep_table *t = calloc(1, sizeof(*t));
	uint32_t i;
	t->count = n;
	t->entries = calloc(n, sizeof(struct amdgpu_clock_voltage_dependency_entry));
	for (i = 0; i < n; i++) {
		t->entries[i].clk = 10000 + 100 * i;
		t->entries[i].v   = (uint16_t)(0x1000 + i);  /* BIOS-chosen u16 voltage */
	}
	return t;
}

static const uint64_t CANARY = 0xDEADBEEFCAFEF00DULL;
static int corrupted_bytes(const unsigned char *zone, size_t n)
{
	int c = 0; size_t i;
	for (i = 0; i < n; i++) if (zone[i] != ((unsigned char *)&CANARY)[i % 8]) c++;
	return c;
}

int main(void)
{
	int rc = 0;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("== DF-3585 harness: si_get_svi2_voltage_table heap OOB write ==\n");
	printf("kernel: sys/dev/drm/amd/amdgpu/si_dpm.c:4410-4414, guest: no AMD GPU, logic replica\n\n");

	/* ---------------- Part A: intra-object corruption (pristine diff) --------- */
	{
		struct clk_voltage_dep_table *dep = bios_dep_table(255);
		struct eg_tail_replica *vic  = calloc(1, sizeof(struct eg_tail_replica));
		struct eg_tail_replica *ref  = calloc(1, sizeof(struct eg_tail_replica));
		const unsigned char *entries_end;

		printf("[A] dep->count = %u (BIOS u8 from VddcDependencyOnMCLK.ucNumEntries)\n", dep->count);
		printf("[A] entries[] capacity = %d (MAX_VOLTAGE_ENTRIES, amdgpu_atombios.h:119)\n", MAX_VOLTAGE_ENTRIES);
		si_get_svi2_voltage_table_rep(dep, &vic->vddc_voltage_table);

		printf("[A] vddc_voltage_table.count now = %u (already > capacity)\n", vic->vddc_voltage_table.count);
		printf("[A] first OOB slot i=32 lands at vddci_voltage_table.count -> now %u (BIOS-chosen u16 0x%04x)\n",
		       vic->vddci_voltage_table.count, vic->vddci_voltage_table.count);
		printf("[A] vddci_voltage_table.mask_low -> now 0x%x (zeroed by smio_low=0 store)\n",
		       vic->vddci_voltage_table.mask_low);
		entries_end = (const unsigned char *)&vic->vddc_voltage_table + 12 + 32 * 8;
		{
			const unsigned char *v = entries_end, *r = (const unsigned char *)ref + (entries_end - (unsigned char *)vic);
			const unsigned char *vend = (unsigned char *)vic + sizeof(*vic);
			size_t diff = 0, i;
			for (i = 0; v + i < vend; i++)
				if (v[i] != r[i]) diff++;
			printf("[A] bytes of the heap object rewritten past entries[32] = %zu (of %zu until object end)\n",
			       diff, (size_t)(vend - entries_end));
			if (diff > 0) { printf("[A] RESULT: OOB WRITE CONFIRMED (heap object corrupted)\n"); rc |= 1; }
		}
		free(vic); free(ref); free(dep->entries); free(dep);
	}

	/* ---------------- Part B: write leaves the allocation (guard page) -------- */
	{
		pid_t pid = fork();
		if (pid == 0) {
			struct clk_voltage_dep_table *dep = bios_dep_table(255);
			long pg = sysconf(_SC_PAGESIZE);
			unsigned char *two = mmap(NULL, pg * 2, PROT_READ | PROT_WRITE,
						  MAP_PRIVATE | MAP_ANON, -1, 0);
			mprotect(two + pg, pg, PROT_NONE);          /* guard: first byte past page 1 */
			/* place victim so that entries[] ENDs exactly at the page boundary */
			size_t vt_size = sizeof(struct atom_voltage_table);
			struct atom_voltage_table *vic =
				(struct atom_voltage_table *)(two + pg - vt_size);
			memset(vic, 0, vt_size);
			printf("[B] child: victim ends at page boundary %p, guard at %p\n", (void *)(two + pg), (void *)(two + pg));
			fflush(stdout);
			si_get_svi2_voltage_table_rep(dep, vic);   /* must fault */
			printf("[B] child: NO FAULT -- overflow did NOT leave the page???\n");
			fflush(stdout); _exit(0);
		} else {
			int st; waitpid(pid, &st, 0);
			if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) {
				printf("[B] child killed by SIGSEGV => the i>=32 stores leave the object mapping\n");
				printf("[B] RESULT: OOB WRITE PAST ALLOCATION CONFIRMED (kernel: past si_power_info kalloc end when table sits at object end)\n");
				rc |= 2;
			} else {
				printf("[B] child exited without SIGSEGV (unexpected)\n");
			}
		}
	}

	/* ---------------- Part C: lowSMIO[32] sink (vddci SVI2, mixed config) -----
	 * si_populate_smc_voltage_table() does lowSMIO[i] |= entries[i].smio_low;
	 * for an SVI2-built table every smio_low is 0, so the operand is 0 -- but
	 * the ACCESS itself is still OOB for i>=32 (read of entries[i] far past
	 * the table, read-modify-write of lowSMIO[i] far past the statetable,
	 * which in the kernel runs off the end of the si_power_info allocation
	 * for i >= ~154). We prove the OOB access with a guard page at
	 * &lowSMIO[32]. */
	{
		pid_t pid = fork();
		if (pid == 0) {
			struct clk_voltage_dep_table *dep = bios_dep_table(255);
			unsigned char *vblob = calloc(1, sizeof(struct atom_voltage_table) + 4096);
			struct atom_voltage_table *vddci_svi2 = (struct atom_voltage_table *)vblob;
			long pg = sysconf(_SC_PAGESIZE);
			unsigned char *two = mmap(NULL, pg * 2, PROT_READ | PROT_WRITE,
						  MAP_PRIVATE | MAP_ANON, -1, 0);
			struct statetable_rep *st;
			mprotect(two + pg, pg, PROT_NONE);
			/* position statetable so lowSMIO[] ends exactly at the guard page */
			st = (struct statetable_rep *)(two + pg - 4 - 32 * 4);
			memset((void *)st, 0, 4 + 32 * 4);
			si_get_svi2_voltage_table_rep(dep, vddci_svi2);
			printf("[C] child: lowSMIO ends at %p, guard page at %p, count=%u\n",
			       (void *)((char *)st + 4 + 128), (void *)(two + pg), vddci_svi2->count);
			fflush(stdout);
			si_populate_smc_voltage_table_rep(vddci_svi2, st);   /* must fault at i=32 */
			printf("[C] child: NO FAULT -- sink did not go OOB???\n");
			fflush(stdout); _exit(0);
		} else {
			int st;
			waitpid(pid, &st, 0);
			if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) {
				printf("[C] child killed by SIGSEGV at lowSMIO[32] => si_dpm.c:4505-4506 sink is OOB\n");
				printf("[C] RESULT: SECOND SINK CONFIRMED (lowSMIO OOB read-modify-write; kernel object-end overrun for count>~154)\n");
				rc |= 4;
			} else {
				printf("[C] child exited without SIGSEGV (unexpected)\n");
			}
		}
	}

	/* ---------------- Part D: fix validation ---------------------------------- */
	{
		struct clk_voltage_dep_table *dep = bios_dep_table(255);
		size_t zone_off = sizeof(struct eg_tail_replica);
		unsigned char *blob = calloc(1, sizeof(struct eg_tail_replica) + 4096);
		struct eg_tail_replica *vic = (struct eg_tail_replica *)blob;
		unsigned char *zone = blob + zone_off;
		size_t i;
		for (i = 0; i < 4096; i++) zone[i] = ((unsigned char *)&CANARY)[i % 8];

		int r = si_get_svi2_voltage_table_fixed(dep, &vic->vddc_voltage_table);
		int cb = corrupted_bytes(zone, 4096);
		printf("[D] with fix.diff clamp: ret=%d (rejected oversized BIOS table), canary bytes hit=%d, vddc.count=%u\n",
		       r, cb, vic->vddc_voltage_table.count);
		if (r != 0 && cb == 0) printf("[D] RESULT: FIX VALIDATED at harness level (no OOB writes)\n");
		else { printf("[D] RESULT: FIX FAILED\n"); rc |= 8; }
		free(blob); free(dep->entries); free(dep);
	}

	printf("\nsummary: A=%d B=%d C=%d (1=confirmed) D_fix_ok=%d\n",
	       (rc & 1) ? 1 : 0, (rc & 2) ? 1 : 0, (rc & 4) ? 1 : 0, (rc & 8) ? 0 : 1);
	return (rc & 7) ? 0 : 1;  /* harness "succeeds" when it demonstrated the bug */
}
