/*
 * DF-3684 harness: verbatim reproduction of smu7_get_sclks()/smu7_get_mclks()
 * from sys/dev/drm/amd/powerplay/hwmgr/smu7_hwmgr.c:4613-4675, driven with a
 * VBIOS dependency count of 255, writing into a `struct amd_pp_clocks` laid
 * out exactly as in the kernel (dm_pp_interface.h:161-167: clock[16],
 * latency[16]) and placed on the stack of a caller modelled on
 * dm_pp_get_clock_levels_by_type() (amdgpu_dm_pp_smu.c:296-316, where
 * `struct amd_pp_clocks pp_clks = { 0 };` is a STACK variable followed by
 * `struct amd_pp_simple_clock_info validation_clks`).
 *
 * Expected: writes clock[16..254] and latency[16..254] => 956 bytes past each
 * 64-byte array, i.e. ~956 bytes past the end of the 132-byte struct on the
 * caller's stack frame (kernel stack buffer overflow with attacker-controlled
 * u32 clock values).
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>

#define MAX_NUM_CLOCKS 16

struct amd_pp_clocks {
	uint32_t count;
	uint32_t clock[MAX_NUM_CLOCKS];
	uint32_t latency[MAX_NUM_CLOCKS];
};

/* model of the phm_ppt_v1_clock_voltage_dependency_table (count + entries) */
struct dep_table {
	uint32_t count;
	struct { uint32_t clk; uint32_t vddci; uint32_t mvdd; } entries[255];
};

/* ---- verbatim loops (smu7_hwmgr.c:4625-4627 and 4662-4667) ---- */
static void smu7_get_sclks_body(struct amd_pp_clocks *clocks, struct dep_table *dep)
{
	int i;
	for (i = 0; i < (int)dep->count; i++)
		clocks->clock[i] = dep->entries[i].clk * 10;
	clocks->count = dep->count;
}

static void smu7_get_mclks_body(struct amd_pp_clocks *clocks, struct dep_table *dep)
{
	int i;
	for (i = 0; i < (int)dep->count; i++) {
		clocks->clock[i] = dep->entries[i].clk * 10;
		clocks->latency[i] = 35;	/* smu7_get_mem_latency() value */
	}
	clocks->count = dep->count;
}
/* ---- end verbatim ---- */

/* caller frame model: mirrors dm_pp_get_clock_levels_by_type()'s locals */
struct caller_frame {
	struct amd_pp_clocks pp_clks;
	struct { uint32_t engine_max_clock, memory_max_clock; } validation_clks;
	uint64_t canary;
	uint64_t retaddr_model;
};

/* Phase 1: same geometry, but the "frame" sits inside a padded heap block so
 * the overflow can be accounted safely (post[] is 2 KB: the overflow needs
 * ~956 bytes past the frame). */
static int accounting(struct dep_table *dep)
{
	struct { uint64_t pad[8]; struct caller_frame f; uint64_t post[300]; } s;
	int i, oob = 0;

	memset(&s, 0, sizeof(s));
	s.f.canary = 0x4141414141414141ULL;
	s.f.retaddr_model = 0xdeadbeef00000000ULL;
	for (i = 0; i < 8; i++)
		s.post[i] = 0xB0B0B0B0B0B0B0B0ULL;

	smu7_get_sclks_body(&s.f.pp_clks, dep);
	smu7_get_mclks_body(&s.f.pp_clks, dep);

	printf("sizeof(amd_pp_clocks)=%zu, dep->count=%u, max array elems=%d\n",
	       sizeof(struct amd_pp_clocks), dep->count, MAX_NUM_CLOCKS);
	printf("caller frame offsets: pp_clks@%zu validation_clks@%zu canary@%zu retaddr@%zu\n",
	       offsetof(struct caller_frame, pp_clks),
	       offsetof(struct caller_frame, validation_clks),
	       offsetof(struct caller_frame, canary),
	       offsetof(struct caller_frame, retaddr_model));

	for (i = MAX_NUM_CLOCKS; i < (int)dep->count; i++) {
		uint32_t expect = dep->entries[i].clk * 10;
		if (s.f.pp_clks.clock[i] == expect) {
			if (!oob)
				printf("first OOB clock[] index: %d (offset +%d bytes past array end)\n",
				       i, (i - MAX_NUM_CLOCKS) * 4);
			oob++;
		}
	}
	printf("OOB u32 writes past clock[16] and latency[16]: %d each (%d bytes each)\n",
	       oob, oob * 4);
	{
		char *end_of_struct = (char *)&s.f.pp_clks + sizeof(s.f.pp_clks);
		int past = (int)((char *)&s.f.pp_clks.clock[dep->count - 1] + 4 - end_of_struct);
		printf("bytes written past end of amd_pp_clocks struct: %d\n", past);
	}
	printf("validation_clks.engine_max_clock corrupted to: 0x%x\n",
	       s.f.validation_clks.engine_max_clock);
	printf("caller-frame canary: 0x%llx (expected 0x4141414141414141)\n",
	       (unsigned long long)s.f.canary);
	printf("return-address model slot: 0x%llx (expected 0xdeadbeef00000000)\n",
	       (unsigned long long)s.f.retaddr_model);
	return oob;
}

/* Phase 2: the real thing - pp_clks as a genuine stack local like
 * dm_pp_get_clock_levels_by_type(); expect the smash to kill the process. */
static int live_smash(struct dep_table *dep)
{
	struct caller_frame f;		/* ON THE STACK */
	volatile uint64_t marker = 0x5A5A5A5A5A5A5A5AULL;

	memset((void *)&f, 0, sizeof(f));
	printf("phase 2: overflowing a real stack-local amd_pp_clocks (marker=%llx)...\n",
	       (unsigned long long)marker);
	smu7_get_sclks_body(&f.pp_clks, dep);
	smu7_get_mclks_body(&f.pp_clks, dep);
	printf("UNEXPECTED: survived the stack smash\n");
	return 1;
}

int main(void)
{
	struct dep_table dep;
	int i;

	setbuf(stdout, NULL);	/* unbuffered: prints survive the stack smash */

	/* crafted VBIOS: 255 MCLK dependency entries */
	dep.count = 255;
	for (i = 0; i < 255; i++)
		dep.entries[i].clk = 100000 + i * 2500;	/* 10kHz units */

	if (accounting(&dep) < 200)
		return 1;
	printf("RESULT: STACK OVERFLOW CONFIRMED (phase 1 accounting) - clock values "
	       "written past amd_pp_clocks into adjacent caller stack locals; "
	       "phase 2 now performs the smash on a live stack frame\n");
	return live_smash(&dep);
}
