/*
 * DF-4774 — PATCHED TWIN (carries fix.diff bound check in the CGS construction);
 * every other byte identical to smu7_fw_upload_oob.c (baseline).
 * DF-4774 — verbatim-function harness (no AMD GPU on the audit guest;
 * methodology identical to DF-4665 / DF-3683 / DF-2096).
 *
 * Proves: sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c
 *   smu7_upload_smu_firmware_image()  (:481-500)
 *     -> smu7_upload_smc_firmware_data() (:461-478)
 * reads `ucode_size_bytes` dwords starting at
 *   fw->data + ucode_array_offset_bytes
 * where both fields come straight from the SMC firmware file header
 * (amdgpu_cgs.c:461-472) and the ONLY validation anywhere on the path is
 * amdgpu_ucode_validate()'s `fw->datasize == hdr->size_bytes`
 * (amdgpu_ucode.c:251-259) plus the SMC-SRAM length gate
 * `limit >= byte_count` (smu7_smumgr.c:465, limit = SMU7_SMC_SIZE = 0x20000).
 * Neither the base offset nor the length is ever checked against the actual
 * firmware blob allocation -> kernel heap OOB read (up to 128 KB from up to
 * ~4 GB past the blob), contents streamed dword-by-dword into SMC SRAM via
 * MMIO.  Reached at powerplay start_smu for Tonga/Fiji/Polaris/Topaz/VegaM
 * (polaris10_smumgr.c:209, tonga_smumgr.c:104, fiji, vegam, iceland).
 *
 * All function bodies below marked [VERBATIM] are copied 1:1 from the
 * cited lines; MMIO and firmware plumbing is mocked 1:1.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdbool.h>
#include <errno.h>

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

/* ---- 1:1 struct layouts -------------------------------------------------- */

/* sys/dev/drm/amd/amdgpu/amdgpu_ucode.h:26-37 */
struct common_firmware_header {
	u32 size_bytes;
	u32 header_size_bytes;
	u16 header_version_major;
	u16 header_version_minor;
	u16 ip_version_major;
	u16 ip_version_minor;
	u32 ucode_version;
	u32 ucode_size_bytes;
	u32 ucode_array_offset_bytes;
	u32 crc32;
};

/* amdgpu_ucode.h:47-50 */
struct smc_firmware_header_v1_0 {
	struct common_firmware_header header;
	u32 ucode_start_addr;
};

/* sys/sys/firmware.h (data/datasize semantics of request_firmware) */
struct firmware {
	const u8 *data;
	size_t datasize;
};

/* sys/dev/drm/amd/include/cgs_common.h:68-80 */
struct cgs_firmware_info {
	u16 version;
	u16 fw_version;
	u16 feature_version;
	u32 image_size;
	u64 mc_addr;
	u32 ucode_start_address;
	void *kptr;
	u8 is_kicker;
};

/* smu_ucode_xfer_vi.h:71-93 (not exercised, layout only) */
struct SMU_Entry { u16 id, version; u32 image_addr_high, image_addr_low; u32 meta_data_addr_high, meta_data_addr_low; u32 data_size_byte; u16 flags, num_register_entries; };

/* smu7_smumgr.h:32-53 */
struct smu7_buffer_entry {
	u32 data_size;
	u64 mc_addr;
	void *kaddr;
	void *handle;
};
struct smu7_smumgr {
	struct smu7_buffer_entry smu_buffer;
	struct smu7_buffer_entry header_buffer;
	struct SMU_DRAMData_TOC_dummy *toc;
	u32 soft_regs_start, dpm_table_start, mc_reg_table_start, fan_table_start;
	u32 arb_table_start, ulv_setting_starts;
	u8  security_hard_key;
	u32 acpi_optimization, avfs_btc_param;
};
struct SMU_DRAMData_TOC_dummy { u32 a, b; char e[1]; };

struct amdgpu_device { struct firmware pm_fw; };

struct pp_hwmgr {
	void *device;			/* cgs device */
	struct amdgpu_device *adev;
	void *smu_backend;
	u32 chip_id;
	bool not_vf;
	bool is_kicker;
	u32 smu_version;
};

/* ---- constants (smu7_smumgr.c:36, ucode ids) ------------------------------ */
#define SMU7_SMC_SIZE 0x20000
#define UCODE_ID_SMU     0
#define UCODE_ID_SMU_SK 14
enum cgs_ucode_id { CGS_UCODE_ID_SMU, CGS_UCODE_ID_SMU_SK, CGS_UCODE_ID_MAXIMUM };

/* mock MMIO register ids */
#define mmSMC_IND_INDEX_11 0xB0
#define mmSMC_IND_DATA_11  0xB4
#define mmSMC_IND_ACCESS_CNTL 0xB8

/* ---- mock device: capture the MMIO stream exactly as the SMC sees it ------ */
#define SMC_SRAM_SIZE 0x40000
#define SINK_MAX 4096
struct mock_smc {
	u32 sram[SMC_SRAM_SIZE / 4];
	u32 index;
	u32 autoincr;
	u64 data_writes;		/* dwords streamed to DATA_11 */
	u64 oob_window_writes;		/* writes with index >= SMC_SRAM_SIZE */
	u32 sink[SINK_MAX];		/* first dwords captured for inspection */
	u32 sink_n;
};
static struct mock_smc smc;

static void cgs_write_register(void *dev, u32 reg, u32 val)
{
	(void)dev;
	if (reg == mmSMC_IND_INDEX_11) {
		smc.index = val;
	} else if (reg == mmSMC_IND_DATA_11) {
		if (smc.index >= SMC_SRAM_SIZE)
			smc.oob_window_writes++;
		else
			smc.sram[smc.index / 4] = val;
		if (smc.autoincr)
			smc.index += 4;
		if (smc.sink_n < SINK_MAX)
			smc.sink[smc.sink_n++] = val;
		smc.data_writes++;
	} else if (reg == mmSMC_IND_ACCESS_CNTL) {
		smc.autoincr = (val & 1);	/* AUTO_INCREMENT_IND_11 bit */
	}
}

/* pp_debug.h:37-43 [VERBATIM semantics] */
#define pr_warn printf
#define PP_ASSERT_WITH_CODE(cond, msg, code)	\
	do {					\
		if (!(cond)) {			\
			pr_warn("%s\n", msg);	\
			code;			\
		}				\
	} while (0)

/* PHM_WRITE_FIELD(device, SMC_IND_ACCESS_CNTL, AUTO_INCREMENT_IND_11, x):
 * plain field write in the harness (flag captured above) */
static void smc_access_cntl(int on)
{
	cgs_write_register(NULL, mmSMC_IND_ACCESS_CNTL, on ? 1 : 0);
}

/* ---- endian / errno shims (x86 LE host) ----------------------------------- */
#define le32_to_cpu_(x) (x)
#define EINVAL_ 22

/* ---- amdgpu_ucode.c:251-259 [VERBATIM] ------------------------------------ */
static int amdgpu_ucode_validate(const struct firmware *fw)
{
	const struct common_firmware_header *hdr =
		(const struct common_firmware_header *)fw->data;

	if (fw->datasize == le32_to_cpu_(hdr->size_bytes))
		return 0;

	return -EINVAL_;
}

/* ---- amdgpu_cgs.c:437-472, SMU branch [VERBATIM math] --------------------- */
static int cgs_get_firmware_info_smu(struct amdgpu_device *adev,
				     struct cgs_firmware_info *info)
{
	int err;
	u32 ucode_size;
	u32 ucode_start_address;
	const u8 *src;
	const struct smc_firmware_header_v1_0 *hdr;

	/* request_firmware() mocked: adev->pm_fw holds the loaded file */

	err = amdgpu_ucode_validate(&adev->pm_fw);
	if (err) {
		printf("[cgs] amdgpu_ucode_validate rejected the blob (err=%d)\n", err);
		return err;
	}

	hdr = (const struct smc_firmware_header_v1_0 *)adev->pm_fw.data;
	ucode_size = hdr->header.ucode_size_bytes;
	ucode_start_address = hdr->ucode_start_addr;
	/* ---- fix.diff: bound the payload against the actual firmware blob ---- */
	if (hdr->header.ucode_array_offset_bytes > adev->pm_fw.datasize ||
	    ucode_size > adev->pm_fw.datasize - hdr->header.ucode_array_offset_bytes) {
		printf("[cgs] FIX: SMU firmware header offset/size out of blob bounds -> -EINVAL\n");
		return -22;
	}
	/* ---------------------------------------------------------------------- */
	src = (const uint8_t *)(adev->pm_fw.data +
	       hdr->header.ucode_array_offset_bytes);

	info->version = 0x1234;
	info->image_size = ucode_size;
	info->ucode_start_address = ucode_start_address;
	info->kptr = (void *)src;
	info->is_kicker = 0;
	return 0;
}

static enum cgs_ucode_id smu7_convert_fw_type_to_cgs_(u32 fw_type)
{
	return fw_type == UCODE_ID_SMU ? CGS_UCODE_ID_SMU : CGS_UCODE_ID_SMU_SK;
}

/* ---- smu7_smumgr.c:461-478 [VERBATIM] ------------------------------------- */
static int smu7_upload_smc_firmware_data(struct pp_hwmgr *hwmgr, u32 length, u32 *src, u32 limit)
{
	u32 byte_count = length;

	PP_ASSERT_WITH_CODE((limit >= byte_count), "SMC address is beyond the SMC RAM area.", return -22);

	cgs_write_register(hwmgr->device, mmSMC_IND_INDEX_11, 0x20000);
	smc_access_cntl(1);

	for (; byte_count >= 4; byte_count -= 4)
		cgs_write_register(hwmgr->device, mmSMC_IND_DATA_11, *src++);

	smc_access_cntl(0);

	PP_ASSERT_WITH_CODE((0 == byte_count), "SMC size must be divisible by 4.", return -22);

	return 0;
}

/* ---- smu7_smumgr.c:481-500 [VERBATIM] ------------------------------------- */
static int smu7_upload_smu_firmware_image(struct pp_hwmgr *hwmgr)
{
	int result = 0;
	struct smu7_smumgr *smu_data = (struct smu7_smumgr *)(hwmgr->smu_backend);
	struct cgs_firmware_info info = {0};

	if (smu_data->security_hard_key == 1)
		cgs_get_firmware_info_smu(hwmgr->adev, &info);
	else
		cgs_get_firmware_info_smu(hwmgr->adev, &info);	/* SMU_SK twin */

	hwmgr->is_kicker = info.is_kicker;
	hwmgr->smu_version = info.version;
	result = smu7_upload_smc_firmware_data(hwmgr, info.image_size, (u32 *)info.kptr, SMU7_SMC_SIZE);

	return result;
}

/* ---- fixture: firmware blob + guard pages + canary region ----------------- */
#define PAGE 4096
#define CANARY_PAGES 8

static u8 *blob_base;		/* allocation start == fw->data */
static size_t blob_size;	/* file size (fw->datasize) */
static u8 *canary_start;
static size_t canary_bytes;

static void fixture_create(size_t fwsize)
{
	size_t tot = ((fwsize + PAGE - 1) / PAGE) * PAGE + CANARY_PAGES * PAGE + PAGE;
	u8 *m = mmap(NULL, tot, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
	if (m == MAP_FAILED) { perror("mmap"); exit(1); }
	blob_base = m;
	blob_size = fwsize;
	canary_start = m + ((fwsize + PAGE - 1) / PAGE) * PAGE;
	canary_bytes = CANARY_PAGES * PAGE;
	/* canary dwords: 0xCCnnnnnn, unique per dword */
	u32 *c = (u32 *)canary_start;
	for (size_t i = 0; i < canary_bytes / 4; i++)
		c[i] = 0xCC000000u | (u32)(i & 0xFFFFFF);
	mprotect(canary_start, canary_bytes, PROT_READ);
	/* final guard page: fault on any read past the canary region */
	mprotect(m + tot - PAGE, PAGE, PROT_NONE);
}

static void fixture_header(u32 off, u32 size)
{
	struct smc_firmware_header_v1_0 *hdr = (struct smc_firmware_header_v1_0 *)blob_base;
	memset(hdr, 0, sizeof(*hdr));
	hdr->header.size_bytes = blob_size;		/* passes amdgpu_ucode_validate */
	hdr->header.header_size_bytes = sizeof(*hdr);
	hdr->header.ucode_version = 0xdeadbee0;
	hdr->header.ucode_size_bytes = size;
	hdr->header.ucode_array_offset_bytes = off;
	hdr->ucode_start_addr = 0;
}

static int canary_hits_in_sink(void)
{
	int hits = 0;
	for (u32 i = 0; i < smc.sink_n; i++)
		if ((smc.sink[i] >> 24) == 0xCC)
			hits++;
	return hits;
}

static struct pp_hwmgr hwmgr;
static struct smu7_smumgr backend;
static struct amdgpu_device adev;

static void setup(size_t fwsize, u32 off, u32 size)
{
	memset(&smc, 0, sizeof(smc));
	fixture_create(fwsize);
	fixture_header(off, size);
	memset(&backend, 0, sizeof(backend));
	backend.security_hard_key = 1;
	memset(&adev, 0, sizeof(adev));
	adev.pm_fw.data = blob_base;
	adev.pm_fw.datasize = blob_size;
	memset(&hwmgr, 0, sizeof(hwmgr));
	hwmgr.device = (void *)0x1;
	hwmgr.adev = &adev;
	hwmgr.smu_backend = &backend;
	hwmgr.chip_id = 0x50;	/* > CHIP_TOPAZ */
	hwmgr.not_vf = 1;
}

/* run one scenario in-process (expected not to fault).
 * child exit codes: 11 = OOB-READ CONFIRMED (upload ok + canary bytes in sink),
 *                   12 = anomaly (upload failed or no leak) */
static int run_scenario(size_t fwsize, u32 off, u32 size)
{
	pid_t pid = fork();
	if (pid == 0) {
		setup(fwsize, off, size);
		int r = smu7_upload_smu_firmware_image(&hwmgr);
		printf("  upload result            : %d (%s)\n", r,
		       r == 0 ? "0 == success, firmware 'uploaded'" : "rejected");
		printf("  dwords streamed to SMC   : %llu\n",
		       (unsigned long long)smc.data_writes);
		printf("  canary bytes in SMC sink : %d\n", canary_hits_in_sink());
		printf("  sink sample (first 8 dwords):");
		for (int q = 0; q < 8 && q < (int)smc.sink_n; q++)
			printf(" %08x", smc.sink[q]);
		printf("\n");
		fflush(stdout);
		if (r == 0 && canary_hits_in_sink() > 0)
			_exit(11);
		if (r == 0 && canary_hits_in_sink() == 0)
			_exit(12);
		_exit(12);
	}
	int st; waitpid(pid, &st, 0);
	return st;
}

/* run one scenario expected to SIGSEGV on the wild read */
static int run_crash_scenario(size_t fwsize, u32 off, u32 size)
{
	pid_t pid = fork();
	if (pid == 0) {
		setup(fwsize, off, size);
		smu7_upload_smu_firmware_image(&hwmgr);
		_exit(0);	/* not reached if it faults */
	}
	int st; waitpid(pid, &st, 0);
	return st;
}

int main(void)
{
	int fail = 0;
	setvbuf(stdout, NULL, _IONBF, 0);

	printf("== DF-4774 verbatim harness: smu7_smumgr.c:461-500 / amdgpu_cgs.c:437-472 ==\n\n");

	printf("[1] legit header (off=0x400, size=0x800, blob=0x1000): baseline sanity\n");
	{
		pid_t pid = fork();
		if (pid == 0) {
			setup(0x1000, 0x400, 0x800);
			int r = smu7_upload_smu_firmware_image(&hwmgr);
			printf("  upload result          : %d\n", r);
			printf("  dwords streamed        : %llu\n", (unsigned long long)smc.data_writes);
			printf("  OOB-window SMC writes  : %llu\n", (unsigned long long)smc.oob_window_writes);
			printf("  canary hits in sink    : %d (expect 0)\n", canary_hits_in_sink());
			fflush(stdout);
			_exit(0);
		}
		int st; waitpid(pid, &st, 0);
		printf("  child exit             : %d\n\n", WEXITSTATUS(st));
	}

	printf("[2] adjacent-leak: off = end of blob (into canary page), size=0x1000\n");
	{
		int st = run_scenario(0x1000, 0x1000, 0x1000);
		if (WIFSIGNALED(st)) { printf("  unexpected signal %d\n\n", WTERMSIG(st)); fail++; }
		else if (WEXITSTATUS(st) == 12) { printf("  FIX VALIDATED: upload rejected / no canary bytes reached the sink\n\n"); }
		else { printf("  !! STILL LEAKS (exit %d)\n\n", WEXITSTATUS(st)); fail++; }
	}

	printf("[3] far-wild base: off = 0x40000000 (1 GB past blob), size = 0x20000 (max the limit gate allows)\n");
	{
		int st = run_crash_scenario(0x1000, 0x40000000, 0x20000);
		if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV)
			{ printf("  !! STILL FAULTS -- fix ineffective\n\n"); fail++; }
		else { printf("  FIX VALIDATED: wild-base upload rejected before any read (no fault)\n\n"); }
	}

	printf("[4] u32 near-wrap base: off = 0xFFFFF000, size = 0x1000\n");
	{
		int st = run_crash_scenario(0x1000, 0xFFFFF000, 0x1000);
		if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV)
			{ printf("  !! STILL FAULTS -- fix ineffective\n\n"); fail++; }
		else { printf("  FIX VALIDATED: near-wrap base rejected before any read\n\n"); }
	}

	printf("[5] the ONLY gate: size just above SMU7_SMC_SIZE is rejected, size below is not\n");
	{
		pid_t pid = fork();
		if (pid == 0) {
			setup(0x1000, 0x40000000, 0x20004);	/* > limit, wild base */
			int r = smu7_upload_smu_firmware_image(&hwmgr);
			printf("  upload result for size>0x20000 with wild base: %d (rejected by length gate only)\n", r);
			fflush(stdout);
			_exit(0);
		}
		int st; waitpid(pid, &st, 0);
		printf("  child exit             : %d\n\n", WEXITSTATUS(st));
	}

	printf("== summary ==\n");
	printf("craft SMC fw file (root-placed) with size_bytes==filesize, ucode_array_offset_bytes\n");
	printf("pointing past the allocation, ucode_size_bytes<=0x20000 -> kernel reads OOB.\n");
	return fail;
}
