/*
 * DF-1167 — userspace harness for smu7_setup_dpm_tables_v0 NULL-deref + OOB read.
 *
 * Reconstructs the relevant struct layouts from
 *   sys/dev/drm/amd/powerplay/inc/hwmgr.h
 * and replays the loop at
 *   sys/dev/drm/amd/powerplay/hwmgr/smu7_hwmgr.c:716-721
 * to demonstrate the kernel-mode NULL deref and OOB read in userspace.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>

/* --- mirror hwmgr.h struct layouts ----------------------------------- */

struct phm_clock_voltage_dependency_record {
	uint32_t clk;
	uint32_t v;
};

struct phm_clock_voltage_dependency_table {
	uint32_t count;
	struct phm_clock_voltage_dependency_record entries[1];
};

union phm_cac_leakage_record {
	struct {
		uint16_t Vddc;
		uint32_t Leakage;
	};
	struct {
		uint16_t Vddc1;
		uint16_t Vddc2;
		uint16_t Vddc3;
	};
};

struct phm_cac_leakage_table {
	uint32_t count;
	union phm_cac_leakage_record entries[1];
};

/* --- crash-catcher so the harness can keep running after the SIGSEGV -- */

static sigjmp_buf jb;
static volatile int last_sig = 0;
static void handler(int s, siginfo_t *si, void *uc) {
	(void)uc;
	last_sig = s;
	siglongjmp(jb, s);
}

/* Reproduces smu7_hwmgr.c:716-721 with caller-supplied pointers/counts.
 * Returns the value the buggy kernel line would have read, or crashes. */
static void repro_loop(struct phm_clock_voltage_dependency_table *sclk,
		       struct phm_clock_voltage_dependency_table *mclk,
		       struct phm_cac_leakage_table *std_v,
		       uint32_t *out_vddc, uint32_t *out_leakage, uint32_t i)
{
	/* smu7_hwmgr.c:717 */ out_vddc[i]   = mclk->entries[i].v;
	/* smu7_hwmgr.c:718 */ out_leakage[i] = std_v->entries[i].Leakage;
}

int main(void)
{
	struct sigaction sa;
	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS,  &sa, NULL);

	/* Allocate a normal sclk table with 3 entries (matches the
	 * mandatory PP_ASSERT_WITH_CODE checks at smu7_hwmgr.c:677,679). */
	struct phm_clock_voltage_dependency_table *sclk =
		malloc(sizeof(*sclk) + 2 * sizeof(sclk->entries[0]));
	sclk->count = 3;
	for (uint32_t i = 0; i < 3; i++) {
		sclk->entries[i].clk = 1000 + i * 100;
		sclk->entries[i].v   = 1100 + i * 10;
	}

	struct phm_clock_voltage_dependency_table *mclk =
		malloc(sizeof(*mclk) + 2 * sizeof(mclk->entries[0]));
	mclk->count = 3;
	for (uint32_t i = 0; i < 3; i++) {
		mclk->entries[i].clk = 1000 + i * 100;
		mclk->entries[i].v   = 1100 + i * 10;
	}

	uint32_t out_vddc[16]   = {0};
	uint32_t out_leakage[16] = {0};

	/* ------------------------------------------------------------------
	 * CASE 1 — cac_leakage_table == NULL  (processpptables.c:1470 path
	 *           when usCACLeakageTableOffset == 0). Kernel: NULL-deref.
	 * ------------------------------------------------------------------ */
	printf("[case 1] std_voltage_table=NULL, sclk_count=%u -> "
	       "reproducing kernel loop (smu7_hwmgr.c:716-721)...\n",
	       sclk->count);
	fflush(stdout);

	last_sig = 0;
	if (sigsetjmp(jb, 1) == 0) {
		repro_loop(sclk, mclk, /*std_v=*/NULL, out_vddc, out_leakage, 0);
		printf("  (no crash -- unexpected)\n");
	} else {
		printf("  BUG: dereferenced std_voltage_table (NULL) at i=0\n");
		printf("  harness: caught signal %d (SIGSEGV/SIGBUS) -- "
		       "kernel equivalent: NULL-deref panic in "
		       "smu7_setup_dpm_tables_v0\n", last_sig);
	}

	/* ------------------------------------------------------------------
	 * CASE 2 — cac_leakage_table->count < sclk->count. Kernel: OOB read.
	 * ------------------------------------------------------------------ */
	struct phm_cac_leakage_table *short_std =
		malloc(sizeof(*short_std) + 0 * sizeof(short_std->entries[0]));
	short_std->count = 1;
	short_std->entries[0].Leakage = 0xCAFEBABE;

	/* Force the loop bound (sclk->count) past short_std->count.
	 * In the kernel, the loop at smu7_hwmgr.c:716 runs i in
	 * [0, allowed_vdd_sclk_table->count).  We make sclk->count=8 but
	 * std_voltage_table only has 1 entry. */
	sclk->count = 8;
	printf("\n[case 2] std_voltage_table->count=%u, sclk_count=%u -> "
	       "OOB read past std_voltage_table->entries[]\n",
	       short_std->count, sclk->count);

	uint32_t leaked_any = 0;
	for (uint32_t i = 0; i < sclk->count; i++) {
		last_sig = 0;
		if (sigsetjmp(jb, 1) == 0) {
			repro_loop(sclk, mclk, short_std,
				   out_vddc, out_leakage, i);
			if (i >= short_std->count) {
				printf("  i=%u: BUG OOB read std_voltage_table->"
				       "entries[%u].Leakage = 0x%08x "
				       "(past allocated count=%u)\n",
				       i, i, out_leakage[i], short_std->count);
				leaked_any = 1;
			}
		} else {
			printf("  i=%u: OOB access faulted (signal %d) -- "
			       "kernel equivalent: OOB read past kmalloc'd "
			       "cac_leakage_table\n", i, last_sig);
			leaked_any = 1;
			/* In the kernel the slab allocation is larger than
			 * the requested size, so the read would succeed for
			 * several entries before hitting an unmapped page.
			 * We stop the userspace harness at the first fault. */
			break;
		}
	}
	if (!leaked_any)
		printf("  (no OOB -- unexpected)\n");

	printf("\nDF-1167 harness: both bug classes demonstrated.\n");
	free(sclk); free(mclk); free(short_std);
	return 0;
}
