/*
 * DF-4000 — index-math demonstrator for the iceland (Topaz) SMC table
 * populate walks (sys/dev/drm/amd/powerplay/smumgr/iceland_smumgr.c).
 *
 * Provenance of the loop bounds (all attacker-controlled only through a
 * crafted GPU VBIOS PowerPlay table — hardware-gated, like DF-3908/3940):
 *
 *   ATOM PowerPlay dep table ucNumEntries (u8, unclamped)
 *     -> hwmgr->dyn_state.vddc_dependency_on_{s,m}clk->count
 *        (processpptables.c:391 get_clock_voltage_dependency_table)
 *     -> data->dpm_table.{s,m}clk_table.count
 *        (smu7_hwmgr.c:689-697 / 704-711 smu7_setup_dpm_tables_v0, no clamp;
 *         Topaz pinned to PP_TABLE_V0 at hwmgr.c:114-122)
 *     -> loop bounds in iceland_smumgr.c
 *
 * Sinks and their array bounds (smu71.h / smu71_discrete.h):
 *   smc_state_table.GraphicsLevel[SMU71_MAX_LEVELS_GRAPHICS = 8]
 *     iceland_populate_all_graphic_levels iceland_smumgr.c:980-990
 *       (plus [count-1] store at 997 and pcieDpmLevel writes at 1026-1028)
 *   smc_state_table.MemoryLevel[SMU71_MAX_LEVELS_MEMORY = 4]
 *     iceland_populate_all_memory_levels iceland_smumgr.c:1361-1369
 *       (plus [count-1] store at 1385)
 *   STACK object SMU71_Discrete_MCArbDramTimingTable:
 *     entries[SMU__NUM_SCLK_DPM_STATE=8][SMU__NUM_MCLK_DPM_LEVELS=4]
 *     iceland_program_memory_timing_parameters iceland_smumgr.c:1616-1632
 *   mc_regs.data[SMU71_DISCRETE_MC_REGISTER_ARRAY_SET_COUNT = 4]
 *     iceland_convert_mc_reg_table_to_smc iceland_smumgr.c:1761-1766
 *     (+ upload of sizeof(MCRegisterSet)*count at 1796-1799)
 *
 * The struct replicas below are verbatim from smu71_discrete.h so the
 * sizeof/offset arithmetic is the kernel's own.
 */
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#define SMU__NUM_SCLK_DPM_STATE 8
#define SMU__NUM_MCLK_DPM_LEVELS 4
#define SMU71_MAX_LEVELS_GRAPHICS SMU__NUM_SCLK_DPM_STATE
#define SMU71_MAX_LEVELS_MEMORY  SMU__NUM_MCLK_DPM_LEVELS
#define SMU71_DISCRETE_MC_REGISTER_ARRAY_SIZE 16
#define SMU71_DISCRETE_MC_REGISTER_ARRAY_SET_COUNT SMU71_MAX_LEVELS_MEMORY

/* --- verbatim struct replicas (smu71_discrete.h:47-74,110-149,167-181) --- */
struct SMU71_Discrete_GraphicsLevel {
	uint32_t MinVddc, MinVddcPhases, SclkFrequency;
	uint8_t pcieDpmLevel, DeepSleepDivId; uint16_t ActivityLevel;
	uint32_t CgSpllFuncCntl3, CgSpllFuncCntl4, SpllSpreadSpectrum,
		 SpllSpreadSpectrum2, CcPwrDynRm, CcPwrDynRm1;
	uint8_t SclkDid, DisplayWatermark, EnabledForActivity,
		EnabledForThrottle, UpHyst, DownHyst, VoltageDownHyst, PowerThrottle;
};
struct SMU71_Discrete_MemoryLevel {
	uint32_t MinVddc, MinVddcPhases, MinVddci, MinMvdd, MclkFrequency;
	uint8_t EdcReadEnable, EdcWriteEnable, RttEnable, StutterEnable,
		StrobeEnable, StrobeRatio, EnabledForThrottle, EnabledForActivity,
		UpHyst, DownHyst, VoltageDownHyst, padding;
	uint16_t ActivityLevel; uint8_t DisplayWatermark, padding1;
	uint32_t MpllFuncCntl, MpllFuncCntl_1, MpllFuncCntl_2, MpllAdFuncCntl,
		 MpllDqFuncCntl, MclkPwrmgtCntl, DllCntl, MpllSs1, MpllSs2;
};
struct SMU71_Discrete_MCArbDramTimingTableEntry {
	uint32_t McArbDramTiming, McArbDramTiming2;
	uint8_t McArbBurstTime; uint8_t padding[3];
};
struct SMU71_Discrete_MCArbDramTimingTable {
	struct SMU71_Discrete_MCArbDramTimingTableEntry
		entries[SMU__NUM_SCLK_DPM_STATE][SMU__NUM_MCLK_DPM_LEVELS];
};

int main(void)
{
	size_t gl = sizeof(struct SMU71_Discrete_GraphicsLevel);
	size_t ml = sizeof(struct SMU71_Discrete_MemoryLevel);
	size_t ar = sizeof(struct SMU71_Discrete_MCArbDramTimingTable);
	unsigned sclk_count = 12;   /* crafted VBIOS ucNumEntries (u8, 1..255) */
	unsigned mclk_count = 6;
	unsigned i, j, oob;

	printf("sizeof: GraphicsLevel=%zu MemoryLevel=%zu MCArbDramTimingTable"
	       "=%zu (stack) MCRegisterSet.data entries=%d\n",
	       gl, ml, ar, SMU71_DISCRETE_MC_REGISTER_ARRAY_SET_COUNT);
	printf("crafted VBIOS: sclk dep ucNumEntries=%u mclk dep ucNumEntries=%u"
	       " (both pass unclamped through processpptables.c:391 and"
	       " smu7_hwmgr.c:689-711)\n\n", sclk_count, mclk_count);

	/* iceland_populate_all_graphic_levels: for (i = 0; i < count; i++)
	 *     GraphicsLevel[i] = ...;   array bound: 8 */
	oob = 0;
	for (i = 0; i < sclk_count; i++) {
		if (i >= SMU71_MAX_LEVELS_GRAPHICS) {
			oob++;
			if (oob == 1)
				printf("GraphicsLevel[%u]: +%zu bytes past the"
				       " GraphicsLevel[8] array", i,
				       (i - SMU71_MAX_LEVELS_GRAPHICS) * gl + gl);
		}
	}
	printf("  -> %u/%u GraphicsLevel writes OOB (max +%zu bytes past the"
	       " array end, walking MemoryACPILevel/MemoryLevel/LinkLevel/"
	       "ACPILevel/Smio and beyond inside smc_state_table)\n",
	       oob, sclk_count,
	       (sclk_count - SMU71_MAX_LEVELS_GRAPHICS) * gl);
	printf("  -> GraphicsLevel[count-1] store at :997 indexes [%u]\n",
	       sclk_count - 1);

	/* iceland_populate_all_memory_levels: MemoryLevel[i], array bound: 4 */
	oob = 0;
	for (i = 0; i < mclk_count; i++) {
		if (i >= SMU71_MAX_LEVELS_MEMORY) {
			oob++;
			if (oob == 1)
				printf("MemoryLevel[%u]: +%zu bytes past the"
				       " MemoryLevel[4] array", i,
				       (i - SMU71_MAX_LEVELS_MEMORY) * ml + ml);
		}
	}
	printf("  -> %u/%u MemoryLevel writes OOB (max +%zu bytes past the"
	       " array end, walking LinkLevel/ACPILevel/...; smc_state_table is"
	       " followed by power_tune_table/ulv_setting/mc_regs/mc_reg_table"
	       " inside the kzalloc'd iceland_smumgr)\n",
	       oob, mclk_count,
	       (mclk_count - SMU71_MAX_LEVELS_MEMORY) * ml);
	printf("  -> MemoryLevel[count-1] store at :1385 indexes [%u]\n",
	       mclk_count - 1);

	/* iceland_program_memory_timing_parameters: STACK
	 *     arb_regs.entries[i][j],  i < sclk_count, j < mclk_count */
	oob = 0;
	for (i = 0; i < sclk_count; i++) {
		for (j = 0; j < mclk_count; j++) {
			size_t lin = (size_t)i * SMU__NUM_MCLK_DPM_LEVELS + j;
			if (lin >= SMU__NUM_SCLK_DPM_STATE * SMU__NUM_MCLK_DPM_LEVELS) {
				oob++;
				if (oob == 1)
					printf("arb_regs.entries[%u][%u]: linear entry %zu"
					       " of 32 -> +%zu bytes past the %zu-byte"
					       " STACK object", i, j, lin,
					       (lin - 31) *
					       sizeof(struct SMU71_Discrete_MCArbDramTimingTableEntry),
					       ar);
			}
		}
	}
	printf("  -> %u of %u stack-table writes OOB (max linear entry %zu,"
	       " i.e. +%zu bytes past the stack object)\n",
	       oob, sclk_count * mclk_count,
	       (size_t)(sclk_count - 1) * SMU__NUM_MCLK_DPM_LEVELS + (mclk_count - 1),
	       ((size_t)(sclk_count - 1) * SMU__NUM_MCLK_DPM_LEVELS + (mclk_count - 1) - 31) *
	       sizeof(struct SMU71_Discrete_MCArbDramTimingTableEntry));

	/* iceland_convert_mc_reg_table_to_smc: mc_regs->data[i], bound 4 */
	oob = 0;
	for (i = 0; i < mclk_count; i++) {
		if (i >= SMU71_DISCRETE_MC_REGISTER_ARRAY_SET_COUNT)
			oob++;
	}
	printf("  -> %u/%u mc_regs.data[] writes OOB (past data[4] into the"
	       " rest of SMU71_Discrete_MCRegisters / iceland_smumgr), plus"
	       " smu7_copy_bytes_to_smc of sizeof(MCRegisterSet)*%u at :1798"
	       " reads past mc_regs\n",
	       oob, mclk_count, mclk_count);

	/* and note the upstream construction already overflows its own array:
	 * dpm_levels[MAX_REGULAR_DPM_NUMBER = 8] (smu7_hwmgr.h:100) */
	printf("\nupstream: smu7_setup_dpm_tables_v0 also writes"
	       " dpm_levels[%u..%u] past dpm_levels[8]"
	       " (smu7_hwmgr.c:689-711; DF-1166)\n",
	       SMU71_MAX_LEVELS_GRAPHICS, sclk_count - 1);
	printf("DONE (source-certain; kernel fault itself hardware-gated -"
	       " needs a Topaz/ICELAND GPU with the crafted VBIOS)\n");
	return 0;
}
