/*
 * DF-4405 — amdgpu_uvd_sw_init/amdgpu_uvd_resume trust unvalidated
 *           ucode_size_bytes / ucode_array_offset_bytes (DF-1946 family)
 *
 * Target: sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:195-202 (validate call),
 *         sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:250-253 (bo_size PAGE_ALIGN wrap),
 *         sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:403-411 (resume memcpy_toio /
 *         memset_io with underflowed length).
 * Root of family: amdgpu_ucode_validate() only checks fw->datasize ==
 * hdr->size_bytes (sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251-260 = DF-1946);
 * the per-field consumers were never bounds-checked in this file.
 *
 * This program replicates the exact u32 arithmetic of both kernel sites for a
 * synthetic firmware header that PASSES amdgpu_ucode_validate
 * (size_bytes == datasize) while carrying ucode_array_offset_bytes = 0x1000
 * and ucode_size_bytes = 0xFFFFFFF8, and prints the lengths the kernel would
 * use for the VRAM copy / memset. Deterministic; runs anywhere.
 *
 * Build: cc -O2 -o fw_hdr_overflow fw_hdr_overflow.c
 * Run:   ./fw_hdr_overflow
 */

#include <stdio.h>
#include <stdint.h>

/* replicate DFly amdgpu GPU page align on 4K pages (64-bit unsigned long) */
#define AMDGPU_GPU_PAGE_ALIGN(x) (((x) + 4095UL) & ~4095UL)

struct cfh {
	uint32_t ucode_array_offset_bytes;
	uint32_t ucode_size_bytes;
	uint32_t size_bytes;
};

/* the ONLY validation the kernel performs (amdgpu_ucode.c:251-260) */
static int amdgpu_ucode_validate_replica(size_t fw_datasize, uint32_t hdr_size_bytes)
{
	return fw_datasize == hdr_size_bytes ? 0 : -1;
}

int main(void)
{
	const size_t  fw_datasize = 0x8000;      /* a 32 KiB firmware blob   */
	struct cfh    hdr = {
		.ucode_array_offset_bytes = 0x1000,
		.ucode_size_bytes         = 0xFFFFFFF8u,
		.size_bytes               = 0x8000,  /* == datasize: PASSES */
	};
	uint32_t ucode_size = hdr.ucode_size_bytes;
	uint32_t offset     = hdr.ucode_array_offset_bytes;
	unsigned long bo_size, size;

	printf("== DF-4405: amdgpu_uvd fw-header arithmetic replica ==\n\n");
	printf("firmware blob datasize        = 0x%zx\n", fw_datasize);
	printf("hdr->size_bytes               = 0x%x\n", hdr.size_bytes);
	printf("amdgpu_ucode_validate()       = %d (0 == ACCEPTED)\n\n",
	       amdgpu_ucode_validate_replica(fw_datasize, hdr.size_bytes));

	printf("hdr->ucode_array_offset_bytes = 0x%x\n", offset);
	printf("hdr->ucode_size_bytes         = 0x%x\n\n", ucode_size);

	/* --- amdgpu_uvd.c:250-253 (sw_init, non-PSP load) --- */
	bo_size = (200 * 1024) + (256 * 1024);   /* STACK + HEAP, amdgpu_uvd.h:29-30 */
	printf("[sw_init ] bo_size += AMDGPU_GPU_PAGE_ALIGN("
	       "ucode_size_bytes + 8)\n");
	printf("          u32 add first: 0xFFFFFFF8 + 8 = 0x%x  (wraps to 0)\n",
	       (uint32_t)(ucode_size + 8u));
	printf("          PAGE_ALIGN(0) = 0x%lx\n",
	       AMDGPU_GPU_PAGE_ALIGN((unsigned long)(uint32_t)(ucode_size + 8u)));
	printf("          => bo_size = 0x%lx (200K+256K): allocated WITHOUT any"
	       " firmware space\n\n", bo_size);

	/* --- amdgpu_uvd.c:403-411 (resume, saved_bo == NULL) --- */
	size = bo_size;
	printf("[resume  ] memcpy_toio(cpu_addr, fw->data + 0x%x, 0x%x)\n",
	       offset, ucode_size);
	printf("          kernel reads 0x%x (%.2f GiB) bytes starting"
	       " 0x%x into a 0x%zx-byte blob\n",
	       ucode_size, ucode_size / 1073741824.0, offset, fw_datasize);
	printf("          OOB READ  = 0x%x - (0x%zx - 0x%x) = %u bytes"
	       " past the firmware buffer\n\n",
	       ucode_size, fw_datasize, offset,
	       (unsigned)(ucode_size - (uint32_t)(fw_datasize - offset)));
	printf("          then 'size -= ucode_size_bytes':"
	       " 0x%lx - 0x%x -> 0x%x (u32 underflow)\n",
	       size, ucode_size, (uint32_t)(size - ucode_size));
	printf("          memset_io(ptr, 0, 0x%x): %lu bytes past the"
	       " 0x%lx-byte bo end\n\n",
	       (uint32_t)(size - ucode_size),
	       (unsigned long)((uint32_t)(size - ucode_size)) - size, size);

	printf("Both lengths derive solely from unvalidated header fields; the"
	       " only gate, amdgpu_ucode_validate(), checked a different field"
	       " (size_bytes). Preconditions: attacker-controlled firmware file"
	       " (root-installed, /boot/modules/amdgpufw_*_uvd) on AMD hardware"
	       " -> Low severity, DF-1946 family instance in amdgpu_uvd.c.\n");
	return 0;
}
