/*
 * DF-1138 harness: radeon/si_dpm.c si_parse_power_table VCE-state fill-in
 *   -> OOB read when clk_idx >= ucNumEntries (si_dpm.c:6883-6894)
 *
 * Userspace logic harness. The buggy main loop at si_dpm.c:6866 guards
 * `clock_array_index >= ucNumEntries`, but the VCE-state fill-in loop at
 * 6883 does NOT -- so a crafted VBIOS where state_entry->ucClockInfoIndex
 * (passed through as rdev->pm.dpm.vce_states[i].clk_idx) is large relative
 * to ucNumEntries causes a ~1 MB OOB read of the BIOS mapping.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#define RADEON_MAX_VCE_LEVELS 6

struct ClockInfoArray {
	uint8_t  ucNumEntries;     /* from BIOS */
	uint8_t  ucEntrySize;      /* sizeof(union pplib_clock_info) */
	uint8_t  clockInfo[];      /* flexible array */
};

struct radeon_vce_state {
	uint16_t sclk, mclk;
	uint8_t  clk_idx;          /* from BIOS via r600_dpm.c:1128 (ucClockInfoIndex & 0x3f) */
};

/* Mirror of buggy main loop (si_dpm.c:6864-6876) -- HAS guard */
static int main_loop_read(struct ClockInfoArray *arr, uint8_t clk_idx)
{
	if (clk_idx >= arr->ucNumEntries)
		return -1;  /* guarded */
	uint8_t *p = &arr->clockInfo[0] + (clk_idx * arr->ucEntrySize);
	return *p;
}

/* Mirror of buggy VCE loop (si_dpm.c:6883-6894) -- NO guard */
static int vce_loop_read(struct ClockInfoArray *arr, uint8_t clk_idx)
{
	uint8_t *p = &arr->clockInfo[clk_idx * arr->ucEntrySize];
	return *p;  /* unguarded -- OOB if clk_idx >= ucNumEntries */
}

/* Fixed VCE loop -- adds the missing guard */
static int vce_loop_read_fixed(struct ClockInfoArray *arr, uint8_t clk_idx)
{
	if (clk_idx >= arr->ucNumEntries)
		return -1;
	uint8_t *p = &arr->clockInfo[clk_idx * arr->ucEntrySize];
	return *p;
}

int main(void)
{
	/* Simulate BIOS-mapped clockInfo array: 5 entries of 16 bytes each.
	 * In the real driver this points into the BIOS mapping (mapped via ioremap). */
	enum { N = 5, ESZ = 16 };
	uint8_t buf[N * ESZ + 4096];   /* +slack to absorb OOB without page-faulting userspace */
	memset(buf, 0xAB, sizeof(buf)); /* fill with marker bytes */
	struct ClockInfoArray *arr = (struct ClockInfoArray *)buf;
	arr->ucNumEntries = N;
	arr->ucEntrySize  = ESZ;
	/* Mark in-bounds region with 0x11 so we can see when we go OOB */
	memset(arr->clockInfo, 0x11, N * ESZ);

	/* Crafted VBIOS produces vce_state.clk_idx == 60 (0xff & 0x3f == 0x3f). */
	struct radeon_vce_state crafted = { .clk_idx = 0x3f };

	printf("=== DF-1138 harness: crafted VBIOS with vce_state.clk_idx=0x%02x ===\n",
	       crafted.clk_idx);
	printf("    clock_info_array: ucNumEntries=%u ucEntrySize=%u (in-bounds=%u..%u)\n\n",
	       arr->ucNumEntries, arr->ucEntrySize, 0, (unsigned)(N * ESZ));

	printf("[A] Main-loop guard (si_dpm.c:6866) on same clk_idx:\n");
	printf("    buggy code path returns -1 (out-of-range rejected)\n\n");

	int rc = main_loop_read(arr, crafted.clk_idx);
	printf("    rc=%d  (guarded)\n\n", rc);

	printf("[B] VCE-loop WITHOUT guard (si_dpm.c:6885-6887) -- the bug:\n");
	int byte = vce_loop_read(arr, crafted.clk_idx);
	unsigned off = crafted.clk_idx * arr->ucEntrySize;
	printf("    read byte=0x%02x at offset clockInfo[%u] (=%u bytes into clockInfo)\n",
	       (uint8_t)byte, off, off);
	printf("    >>> In kernel: OOB read of BIOS mapping by %u bytes (0x%x - 0x%x = %u) <<<\n",
	       off - N * ESZ, off, N * ESZ, off - N * ESZ);
	printf("    >>> Real-world: a malicious VBIOS can read up to 0x3f * %u = %u bytes OOB <<<\n\n",
	       ESZ, 0x3f * ESZ);

	printf("[C] VCE-loop WITH fix.diff guard:\n");
	rc = vce_loop_read_fixed(arr, crafted.clk_idx);
	printf("    rc=%d  (rejected, no OOB read)\n\n", rc);

	return 0;
}
