/*
 * DF-1130 harness: radeon/si.c si_mc_load_microcode / si_cp_load_microcode /
 *                  si_rlc_resume OOB heap read via unchecked firmware
 *                  header offset/size fields.
 *
 * Userspace logic harness. radeon_ucode_validate (radeon_ucode.c:156-165) only
 * checks fw->datasize == hdr->size_bytes; it does NOT validate
 * ucode_array_offset_bytes + ucode_size_bytes <= datasize nor
 * io_debug_array_offset_bytes + io_debug_size_bytes <= datasize. So a crafted
 * firmware blob whose header's size_bytes matches the file size, but whose
 * ucode_array_offset_bytes points near the end, makes the loader read past the
 * firmware buffer into adjacent kernel heap.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

struct common_firmware_header {
	uint32_t size_bytes;                 /* whole-file size including header */
	uint32_t ucode_array_offset_bytes;   /* offset of ucode payload */
	uint32_t ucode_size_bytes;           /* ucode payload length  */
	uint8_t  pad[64 - 12];
};

struct mc_firmware_header {
	struct common_firmware_header header;
	uint32_t io_debug_array_offset_bytes;
	uint32_t io_debug_size_bytes;
};

/* Mirror of radeon_ucode_validate (radeon_ucode.c:156-165) -- only checks total size */
static int radeon_ucode_validate_buggy(uint8_t *fw, uint32_t datasize)
{
	struct common_firmware_header *hdr = (struct common_firmware_header *)fw;
	if (datasize == hdr->size_bytes)
		return 0;   /* passes -- but the offset/size are unchecked */
	return -1;
}

/* Mirror of si_mc_load_microcode (si.c:1574-1584) OOB calculation */
static int si_mc_load_oob_range(uint8_t *fw, uint32_t datasize,
                                uint32_t *uc_off, uint32_t *uc_size,
                                uint32_t *io_off, uint32_t *io_size)
{
	struct mc_firmware_header *hdr = (struct mc_firmware_header *)fw;
	/* buggy: NO check against datasize */
	*uc_off  = hdr->header.ucode_array_offset_bytes;
	*uc_size = hdr->header.ucode_size_bytes;
	*io_off  = hdr->io_debug_array_offset_bytes;
	*io_size = hdr->io_debug_size_bytes;
	return 0;
}

/* Fixed loader -- validates each region against datasize */
static int si_mc_load_checked(uint8_t *fw, uint32_t datasize,
                              uint32_t *uc_off, uint32_t *uc_size,
                              uint32_t *io_off, uint32_t *io_size)
{
	struct mc_firmware_header *hdr = (struct mc_firmware_header *)fw;
	*uc_off  = hdr->header.ucode_array_offset_bytes;
	*uc_size = hdr->header.ucode_size_bytes;
	*io_off  = hdr->io_debug_array_offset_bytes;
	*io_size = hdr->io_debug_size_bytes;
	if (*uc_off > datasize || *uc_size > datasize - *uc_off) return -1;
	if (*io_off > datasize || *io_size > datasize - *io_off) return -1;
	return 0;
}

int main(void)
{
	/* Build a "crafted" firmware blob:
	 *   - 1024 bytes total (so size_bytes == datasize passes validate)
	 *   - ucode_array_offset_bytes = 0xff0 (4080), ucode_size_bytes = 0x4000 (16384)
	 *   - For loops read 16384/4 = 4096 dwords starting at offset 4080.
	 * The first ~16 dwords are in-bounds; the next 4080 are OOB. */
	enum { DS = 1024 };
	uint8_t *fw = malloc(DS + 65536);
	memset(fw, 0xCC, DS + 65536);  /* OOB region reads back as 0xCC */
	struct mc_firmware_header *hdr = (struct mc_firmware_header *)fw;
	hdr->header.size_bytes = DS;
	hdr->header.ucode_array_offset_bytes = 0xff0;  /* near end of file */
	hdr->header.ucode_size_bytes = 0x4000;          /* but size is HUGE */
	hdr->io_debug_array_offset_bytes = 0;
	hdr->io_debug_size_bytes = 0;

	printf("=== DF-1130 harness: crafted SI firmware (datasize=%u) ===\n\n", DS);
	printf("    header.ucode_array_offset_bytes = 0x%x\n", hdr->header.ucode_array_offset_bytes);
	printf("    header.ucode_size_bytes        = 0x%x\n", hdr->header.ucode_size_bytes);
	printf("    => end of payload = 0x%x (vs datasize=0x%x) -> OOB by %u bytes\n\n",
	       hdr->header.ucode_array_offset_bytes + hdr->header.ucode_size_bytes,
	       DS, hdr->header.ucode_array_offset_bytes + hdr->header.ucode_size_bytes - DS);

	printf("[A] radeon_ucode_validate (radeon_ucode.c:156-165):\n");
	int rc = radeon_ucode_validate_buggy(fw, DS);
	printf("    rc=%d  (PASSES -- only checks size_bytes==datasize)\n\n", rc);

	uint32_t uc_off, uc_size, io_off, io_size;
	printf("[B] si_mc_load_microcode buggy (si.c:1574-1584):\n");
	si_mc_load_oob_range(fw, DS, &uc_off, &uc_size, &io_off, &io_size);
	uint32_t end = uc_off + uc_size;
	uint32_t oob = (end > DS) ? (end - DS) : 0;
	printf("    would loop reading u32 from fw+0x%x for %u iters\n",
	       uc_off, uc_size / 4);
	printf("    >>> In kernel: reads %u bytes past firmware buffer into adjacent heap <<<\n\n", oob);

	printf("[C] si_mc_load_microcode fixed (fix.diff):\n");
	rc = si_mc_load_checked(fw, DS, &uc_off, &uc_size, &io_off, &io_size);
	printf("    rc=%d  (rejected: ucode_array_offset_bytes + ucode_size_bytes > datasize)\n", rc);

	free(fw);
	return 0;
}
