/*
 * DF-3887 harness — sumo_construct_vid_mapping_table OOB heap write.
 *
 * Mirrors sys/dev/drm/amd/amdgpu/kv_dpm.c:159-191 with 1:1 struct layouts
 * (kv_dpm.h + atombios.h ATOM_AVAILABLE_SCLK_LIST), driven by a crafted
 * VBIOS sAvail_SCLK[] where usVoltageIndex (USHORT, 0..65535) >= 4.
 *
 * Modes:
 *   ./t          accounting: runs the verbatim loop against an mmap'd shadow
 *                kv_power_info and reports exactly which byte offsets outside
 *                the 16-byte entries[4] array were overwritten.
 *   ./t crash    executes the verbatim write with usVoltageIndex=65535 against
 *                a single-page mapping -> wild write at +262140 bytes -> SIGSEGV
 *                (kernel equivalent: page fault in kernel mode -> panic).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <sys/mman.h>

/* ---- 1:1 kernel struct layouts (kv_dpm.h) ---- */
#define SUMO_MAX_HARDWARE_POWERLEVELS 5
#define SUMO_MAX_NUMBER_VOLTAGES     4

struct sumo_vid_mapping_entry {
	uint16_t vid_2bit;
	uint16_t vid_7bit;
};

struct sumo_vid_mapping_table {
	uint32_t num_entries;
	struct sumo_vid_mapping_entry entries[SUMO_MAX_NUMBER_VOLTAGES];
};

/* ---- 1:1 VBIOS struct (atombios.h:5705) ---- */
typedef struct _ATOM_AVAILABLE_SCLK_LIST {
	uint32_t ulSupportedSCLK;   /* ULONG   */
	uint16_t usVoltageIndex;    /* USHORT  */
	uint16_t usVoltageID;       /* USHORT  */
} ATOM_AVAILABLE_SCLK_LIST;

/* ---- kernel kv_power_info shell: only the prefix that matters, then a
 *      tail region standing in for the rest of the ~1.3KB heap object and
 *      the heap beyond it. */
struct kv_power_info_prefix {
	uint32_t at[SUMO_MAX_HARDWARE_POWERLEVELS];   /* 20 */
	uint32_t voltage_drop_t;                      /* 4  */
	struct {
		uint32_t bootup_uma_clk, bootup_sclk, dentist_vco_freq, nb_dpm_enable;
		uint32_t nbp_memory_clock[4], nbp_n_clock[4];
		uint16_t bootup_nb_voltage_index;
		uint8_t  htc_tmp_lmt, htc_hyst_lmt;
		uint32_t num_max_dpm_entries;
		struct { uint32_t sclk_frequency; uint16_t vid_2bit, rsv; }
			sv_entries[SUMO_MAX_HARDWARE_POWERLEVELS];
		struct sumo_vid_mapping_table vid_mapping_table;  /* <-- victim */
	} sys_info;
};

/* ---- verbatim kv_dpm.c:159-191 (loop body unchanged) ---- */
static void sumo_construct_vid_mapping_table(struct sumo_vid_mapping_table *vid_mapping_table,
					     ATOM_AVAILABLE_SCLK_LIST *table)
{
	uint32_t i, j;

	for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) {
		if (table[i].ulSupportedSCLK != 0) {
			vid_mapping_table->entries[table[i].usVoltageIndex].vid_7bit =
				table[i].usVoltageID;
			vid_mapping_table->entries[table[i].usVoltageIndex].vid_2bit =
				table[i].usVoltageIndex;
		}
	}

	for (i = 0; i < SUMO_MAX_NUMBER_VOLTAGES; i++) {
		if (vid_mapping_table->entries[i].vid_7bit == 0) {
			for (j = i + 1; j < SUMO_MAX_NUMBER_VOLTAGES; j++) {
				if (vid_mapping_table->entries[j].vid_7bit != 0) {
					vid_mapping_table->entries[i] =
						vid_mapping_table->entries[j];
					vid_mapping_table->entries[j].vid_7bit = 0;
					break;
				}
			}
			if (j == SUMO_MAX_NUMBER_VOLTAGES)
				break;
		}
	}
	vid_mapping_table->num_entries = i;
}

int main(int argc, char **argv)
{
	int crash = (argc > 1 && strcmp(argv[1], "crash") == 0);

	/* Crafted VBIOS: strictly-increasing SCLKs; voltage index out of range.
	 * Index 300 -> byte offset 300*4 = 1200 past entries[]: far outside the
	 * 16-byte array (and past the ~1.3KB kv_power_info heap object the
	 * kernel kzalloc'd), still a legal-looking USHORT to the parser. */
	ATOM_AVAILABLE_SCLK_LIST vbios[SUMO_MAX_HARDWARE_POWERLEVELS] = {
		{ .ulSupportedSCLK = 10000, .usVoltageIndex = 0,   .usVoltageID = 0x11 },
		{ .ulSupportedSCLK = 20000, .usVoltageIndex = 1,   .usVoltageID = 0x22 },
		{ .ulSupportedSCLK = 30000, .usVoltageIndex = 300, .usVoltageID = 0xBEEF },
		{ .ulSupportedSCLK = 40000, .usVoltageIndex = 301, .usVoltageID = 0xCAFE },
		{ .ulSupportedSCLK = 50000, .usVoltageIndex = 302, .usVoltageID = 0x1234 },
	};

	if (crash) {
		/* verbatim kernel access against a minimal mapping: usVoltageIndex
		 * 65535 -> write at entries + 65535*4 = +262140 -> unmapped */
		vbios[2].usVoltageIndex = 65535;
		vbios[3].ulSupportedSCLK = 0;   /* keep it to the single wild pair */
		vbios[4].ulSupportedSCLK = 0;

		uint8_t *page = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
				     MAP_ANON | MAP_PRIVATE, -1, 0);
		if (page == MAP_FAILED) { perror("mmap"); return 1; }
		struct kv_power_info_prefix *pi = (void *)page;

		printf("crash mode: entries at %p, writing vid_7bit at +65535*4 = %ld bytes past it\n",
		       (void *)&pi->sys_info.vid_mapping_table.entries,
		       (long)(65535L * 4));
		fflush(stdout);
		sumo_construct_vid_mapping_table(&pi->sys_info.vid_mapping_table, vbios);
		printf("NOT REACHED\n");
		return 0;
	}

	/* accounting mode: big shadow region, observe the corruption footprint */
	size_t region = 512 * 1024;
	uint8_t *shadow = mmap(NULL, region, PROT_READ | PROT_WRITE,
			       MAP_ANON | MAP_PRIVATE, -1, 0);
	if (shadow == MAP_FAILED) { perror("mmap"); return 1; }
	memset(shadow, 0, region);

	struct kv_power_info_prefix *pi = (void *)shadow;
	struct sumo_vid_mapping_table *vt = &pi->sys_info.vid_mapping_table;
	size_t entries_off = (uint8_t *)vt->entries - shadow;

	printf("entries[] at shadow+%zu, size %zu bytes (4 slots x 4 bytes)\n",
	       entries_off, sizeof(vt->entries));
	sumo_construct_vid_mapping_table(vt, vbios);

	/* find every byte that changed outside the entries[] array */
	int nbad = 0;
	for (size_t k = 0; k < region; k += 2) {
		uint16_t v;
		memcpy(&v, shadow + k, 2);
		if (v == 0)
			continue;
		if (k < entries_off || k >= entries_off + sizeof(vt->entries)) {
			printf("OOB WRITE: shadow+%zu (entries+%zd) = 0x%04x\n",
			       k, (ptrdiff_t)(k - entries_off), v);
			nbad++;
		} else {
			printf("in-bounds: entries+%zd = 0x%04x\n",
			       (ptrdiff_t)(k - entries_off), v);
		}
	}
	printf("num_entries after construct = %u\n", vt->num_entries);
	printf("%d OOB 16-bit writes landed OUTSIDE the %zu-byte entries array\n",
	       nbad, sizeof(vt->entries));
	printf("kernel reach: kv_dpm_init -> kv_parse_sys_info_table (kv_dpm.c:2613) at every KV boot\n");
	return 0;
}
