/*
 * DF-3740 harness: radeon_atom_get_mclk_range_table() overflow via uncapped num_entries
 * fill of mclk[MAX_AC_TIMING_ENTRIES=16] — destination is a KERNEL STACK LOCAL
 * (cypress_get_memory_clock_range_table(), cypress_dpm.c:1075-1089).
 *
 * Verbatim arithmetic from sys/dev/drm/radeon/radeon_atombios.c:3925-3946 (frev1/crev4):
 *   mclk_range_table->num_entries = (u8)((usModuleSize - offsetof(V4, asMemTiming)) / mts);
 *   for (i = 0; i < num_entries; i++) mclk[i] = format->ulClkRange;
 * NO bound of num_entries (u8 -> 0..255) against mclk[16] exists.
 *
 * Modes:
 *   default (heap model)  — counts the OOB writes precisely without crashing;
 *   "stack" (argv[1])     — performs the overflow on the harness's own stack frame
 *                           exactly like the kernel would -> crashes (SIGBUS/SIGSEGV).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>

typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32;

typedef struct { u32 ulClkRange; u16 usMRS, usEMRS; u8 t[14]; union { struct { u8 a, b; }; u16 u; }; }
	ATOM_MEMORY_TIMING_FORMAT;				/* atombios.h:6441-6469 == 24 bytes */
typedef struct {
	u32 ulChannelMapCfg; u16 usModuleSize, usPrivateReserved, usReserved;
	u8 ucExtMemoryID, ucMemoryType, ucChannelNum, ucChannelWidth, ucDensity, ucFlag,
	   ucMisc, ucVREFI, ucNPL_RT, ucPreamble, ucMemorySize, ucReserved[3];
	u16 usEMRS2Value, usEMRS3Value;
	u8 ucMemoryVenderID, ucRefreshRateFactor, ucReserved2[2];
	ATOM_MEMORY_TIMING_FORMAT asMemTiming[5];		/* atombios.h:6595-6627 */
} ATOM_VRAM_MODULE_V4;

#define MAX_AC_TIMING_ENTRIES 16				/* radeon_mode.h:642 */
struct atom_memory_clock_range_table {				/* radeon_mode.h:644-649 */
	u8 num_entries; u8 rsv[3]; u32 mclk[MAX_AC_TIMING_ENTRIES];
};

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

	printf("sizeof(ATOM_MEMORY_TIMING_FORMAT)=%zu offsetof(asMemTiming)=%zu\n",
	       sizeof(ATOM_MEMORY_TIMING_FORMAT), offsetof(ATOM_VRAM_MODULE_V4, asMemTiming));
	if (sizeof(ATOM_MEMORY_TIMING_FORMAT) != 24 || offsetof(ATOM_VRAM_MODULE_V4, asMemTiming) != 32) {
		fprintf(stderr, "layout replica mismatch\n"); return 2;
	}

	/* VBIOS: vram_module->usModuleSize = 0xFFF0 */
	u16 usModuleSize = 0xFFF0;
	size_t mem_timing_size = sizeof(ATOM_MEMORY_TIMING_FORMAT);	/* gddr5=false */

	unsigned long oob = 0;
	if (stack_mode) {
		/* perform the overflow on this frame, exactly as the kernel would */
		u32 bios_fmt[8192];
		for (unsigned i = 0; i < 8192; i++) bios_fmt[i] = 0x41410000u + i;
		ATOM_VRAM_MODULE_V4 *vram_module = (ATOM_VRAM_MODULE_V4 *)bios_fmt;
		vram_module->usModuleSize = usModuleSize;
		struct atom_memory_clock_range_table range_table;
		memset(&range_table, 0, sizeof(range_table));
		printf("stack mode: overflowing a %zu-byte stack struct...\n", sizeof(range_table));
		range_table.num_entries = (u8)((usModuleSize -
				offsetof(ATOM_VRAM_MODULE_V4, asMemTiming)) / mem_timing_size);
		ATOM_MEMORY_TIMING_FORMAT *format = &vram_module->asMemTiming[0];
		for (u8 i = 0; i < range_table.num_entries; i++) {
			range_table.mclk[i] = format->ulClkRange;	/* OOB for i >= 16 */
			format = (ATOM_MEMORY_TIMING_FORMAT *)((u8 *)format + mem_timing_size);
		}
		printf("unreachable: survived %u writes\n", range_table.num_entries); /* not reached */
		return 1;
	}

	/* heap model: oversized destination so we can COUNT what lands OOB */
	u32 model[4 + 1 + 256 * 4];				/* pre[] hdr+mclk[16] post[] */
	u32 *mclk = model + 4;					/* mclk[0..15] valid */
	u32 bios_fmt[8192];
	for (unsigned i = 0; i < 8192; i++) bios_fmt[i] = 0x41410000u + i;
	ATOM_VRAM_MODULE_V4 *vram_module = (ATOM_VRAM_MODULE_V4 *)bios_fmt;
	vram_module->usModuleSize = usModuleSize;

	/* === verbatim kernel arithmetic, radeon_atombios.c:3938-3946 === */
	u8 num_entries = (u8)((usModuleSize - offsetof(ATOM_VRAM_MODULE_V4, asMemTiming)) /
			mem_timing_size);
	ATOM_MEMORY_TIMING_FORMAT *format = &vram_module->asMemTiming[0];
	for (u8 i = 0; i < num_entries; i++) {
		mclk[i] = format->ulClkRange;			/* OOB write when i >= 16 */
		if (i >= MAX_AC_TIMING_ENTRIES) oob++;
		format = (ATOM_MEMORY_TIMING_FORMAT *)((u8 *)format + mem_timing_size);
	}
	/* === end verbatim === */

	printf("usModuleSize=0x%04x -> num_entries=%u ; mclk[] capacity=%d\n",
	       usModuleSize, num_entries, MAX_AC_TIMING_ENTRIES);
	printf("first OOB value (VBIOS u32 0x%08x) lands at struct offset %d — i.e. exactly "
	       "at the end of the %zu-byte struct; total overflow %lu bytes past it\n",
	       mclk[MAX_AC_TIMING_ENTRIES], 4 + 4 * MAX_AC_TIMING_ENTRIES,
	       sizeof(struct atom_memory_clock_range_table), oob * 4UL);
	printf("OOB u32 writes past mclk[16]: %lu (%lu bytes) of attacker-chosen VBIOS data "
	       "onto the caller kernel stack frame\n", oob, oob * 4UL);
	printf("note: run `./mclk_bin stack` to perform the overflow on the harness's own "
	       "stack (crashes, as the kernel would)\n");
	if (oob == 0) { printf("FAIL: no overflow\n"); return 1; }
	printf("REPRODUCED (harness): mclk[16] overflow via uncapped num_entries\n");
	return 0;
}
