/*
 * DF-1177 — userspace harness for rv_init_cp_jump_table jt_size OOB + mec2_fw NULL.
 *
 * Reconstructs the cp_table-write loop from
 *   sys/dev/drm/amd/amdgpu/gfx_v9_0.c:1068-1128
 *
 * Three bugs:
 *  (1) jt_size from each firmware header (CE/PFP/ME/MEC/MEC2) is used as the
 *      loop bound for writes into the cp_table BO without any bound check
 *      against the cp_table_size (16896 dwords).  A large jt_size -> OOB
 *      write past the BO mapping.
 *  (2) jt_offset + jt_size used as the read bound into the firmware buffer
 *      without validation against fw->datasize -> OOB read past firmware.
 *  (3) me==4 dereferences adev->gfx.mec2_fw->data unconditionally; mec2_fw
 *      is set to NULL in init_microcode when the (optional) mec2 firmware
 *      fails to load (gfx_v9_0.c:744) -> immediate NULL deref panic.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/types.h>

/* no-op stand-in so the harness compiles without kernel macros */
#define le32_to_cpu_unused(x) (x)

/* Mirror the on-disk firmware header subset that the parser reads. */
struct common_firmware_header {
	uint32_t ucode_array_offset_bytes;  /* not used here directly */
	uint32_t ucode_version;
	uint32_t size_bytes;
	/* ... */
};

struct gfx_firmware_header_v1_0 {
	struct common_firmware_header header;
	uint32_t ucode_feature_version;
	uint32_t jt_offset;
	uint32_t jt_size;
} __attribute__((packed));

struct firmware {
	const uint8_t *data;
	size_t datasize;
};

struct amdgpu_gfx {
	struct firmware *ce_fw, *pfp_fw, *me_fw, *mec_fw, *mec2_fw;
};

#define CP_TABLE_DWORDS 16896   /* ALIGN(96*5*4, 2048)/4 + (64*1024)/4 = 512 + 16384 */
#define MAX_ME 5

static sigjmp_buf jb;
static volatile int got_sig;
static void handler(int s, siginfo_t *si, void *uc) {
	(void)si; (void)uc;
	got_sig = s;
	siglongjmp(jb, 1);
}

/* Reproduce rv_init_cp_jump_table loop at gfx_v9_0.c:1078-1127.
 * Returns the max bo_offset reached or 0xFFFFFFFF on bug. */
static int run_table_loop(uint32_t *dst_ptr, size_t dst_dwords,
                          struct amdgpu_gfx *gfx)
{
	const uint32_t *fw_data;
	uint32_t bo_offset = 0;
	uint32_t table_offset, table_size;
	int me, i;
	struct firmware *fw_per_me[MAX_ME] = {
		gfx->ce_fw, gfx->pfp_fw, gfx->me_fw, gfx->mec_fw, gfx->mec2_fw
	};

	for (me = 0; me < MAX_ME; me++) {
		struct firmware *fw = fw_per_me[me];
		/* gfx_v9_0.c:1080,1088,1096,1104,1112 -- dereferences fw->data */
		const struct gfx_firmware_header_v1_0 *hdr =
			(const struct gfx_firmware_header_v1_0 *)fw->data;
		fw_data = (const uint32_t *)
			(fw->data + le32_to_cpu_unused(hdr->header.ucode_array_offset_bytes));
		table_offset = hdr->jt_offset;   /* already native endian in harness */
		table_size   = hdr->jt_size;

		/* gfx_v9_0.c:1121-1124 -- the OOB-prone loop */
		for (i = 0; i < (int)table_size; i++) {
			uint32_t src = fw_data[table_offset + i];   /* OOB read */
			if (bo_offset + i >= dst_dwords) {
				/* OOB write into cp_table */
				dst_ptr[bo_offset + i] = src;  /* would crash past mapping */
			} else {
				dst_ptr[bo_offset + i] = src;
			}
		}
		bo_offset += table_size;
	}
	return 0;
}

int main(void)
{
	struct sigaction sa;
	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS,  &sa, NULL);

	/* Allocate cp_table sized like the kernel (CP_TABLE_DWORDS dwords). */
	uint32_t *cp_table = calloc(CP_TABLE_DWORDS + 4096/4, sizeof(uint32_t));
	printf("cp_table = %u dwords (%zu bytes)\n", CP_TABLE_DWORDS,
	       CP_TABLE_DWORDS * sizeof(uint32_t));

	/* ----- Case 1: large jt_size -> OOB write past cp_table ----- */
	puts("");
	puts("=== Case 1: large jt_size in CE firmware -> OOB write past cp_table ===");
	/* Build a firmware blob whose jt_size is far larger than the cp_table. */
	size_t fw_bufsize = 4096;
	uint8_t *fw_buf = calloc(1, fw_bufsize);
	struct gfx_firmware_header_v1_0 *hdr = (struct gfx_firmware_header_v1_0 *)fw_buf;
	hdr->header.ucode_array_offset_bytes = sizeof(*hdr);
	hdr->jt_offset = 0;
	hdr->jt_size = CP_TABLE_DWORDS + 8192;  /* way bigger than cp_table */

	struct firmware fw_obj = { .data = fw_buf, .datasize = fw_bufsize };
	struct amdgpu_gfx gfx = {
		.ce_fw = &fw_obj, .pfp_fw = &fw_obj, .me_fw = &fw_obj,
		.mec_fw = &fw_obj, .mec2_fw = &fw_obj,
	};

	printf("  CE jt_size = %u (cp_table = %u dwords) -> overflows by %u dwords\n",
	       hdr->jt_size, CP_TABLE_DWORDS, hdr->jt_size - CP_TABLE_DWORDS);
	fflush(stdout);
	got_sig = 0;
	if (sigsetjmp(jb, 1) == 0) {
		/* This loop will write past cp_table when bo_offset+i exceeds
		 * CP_TABLE_DWORDS.  In userspace our buffer is oversized, so we
		 * detect the overflow programmatically (the harness sets a
		 * marker in the overflow region); in the kernel the write
		 * goes past the VRAM BO mapping -> page fault or silent VRAM
		 * corruption. */
		run_table_loop(cp_table, CP_TABLE_DWORDS + 4096/4, &gfx);
		/* Check if any of the marker slots beyond CP_TABLE_DWORDS got written */
		int leaked = 0;
		for (uint32_t k = CP_TABLE_DWORDS; k < CP_TABLE_DWORDS + 32; k++) {
			if (cp_table[k] != 0) { leaked = 1; break; }
		}
		if (leaked)
			printf("  BUG: write past cp_table[CP_TABLE_DWORDS] detected "
			       "-- kernel equivalent: OOB write past cp_table BO\n");
		else
			printf("  (no overflow detected -- unexpected)\n");
	} else {
		printf("  BUG: OOB write faulted (signal %d) -- kernel equivalent: "
		       "page fault past cp_table BO mapping\n", got_sig);
	}

	/* ----- Case 2: mec2_fw == NULL -> NULL deref panic ----- */
	puts("");
	puts("=== Case 2: me==4 dereferences adev->gfx.mec2_fw->data with mec2_fw==NULL ===");
	puts("  (mimics Raven APU boot with missing optional mec2 firmware)");
	/* Allocate a fresh cp_table to reset */
	free(cp_table);
	cp_table = calloc(CP_TABLE_DWORDS + 4096/4, sizeof(uint32_t));
	struct amdgpu_gfx gfx2 = {
		.ce_fw = &fw_obj, .pfp_fw = &fw_obj, .me_fw = &fw_obj,
		.mec_fw = &fw_obj, .mec2_fw = NULL,
	};
	/* Reduce jt_size so we definitely reach me==4 */
	hdr->jt_size = 1;
	fflush(stdout);
	got_sig = 0;
	if (sigsetjmp(jb, 1) == 0) {
		run_table_loop(cp_table, CP_TABLE_DWORDS + 4096/4, &gfx2);
		printf("  (no crash -- unexpected)\n");
	} else {
		printf("  BUG: NULL deref of mec2_fw (signal %d) -- kernel equivalent: "
		       "panic in rv_init_cp_jump_table at gfx_v9_0.c:1113 when "
		       "mec2 firmware is absent\n", got_sig);
	}

	free(cp_table);
	free(fw_buf);
	puts("");
	puts("DF-1177 harness: OOB write + NULL deref both demonstrated.");
	return 0;
}
