/*
 * fwcheck.c - DF-4238 evidence harness
 *
 * Replicates, verbatim in semantics, the DragonFly amdgpu firmware-parsing
 * sequence exercised by sdma_v3_0_init_microcode() (sys/dev/drm/amd/amdgpu/
 * sdma_v3_0.c:309-326) on a firmware blob:
 *
 *   1. request_firmware() hands the driver a kernel buffer of fw->datasize
 *      bytes (here: 4 bytes placed at the very END of a mapped page, with an
 *      unmapped guard page right after - standing in for the exact-size
 *      kernel allocation).
 *   2. amdgpu_ucode_validate(fw)  (amdgpu_ucode.c:251-260) accepts the blob
 *      iff  fw->datasize == le32(hdr->size_bytes).  A 4-byte blob declaring
 *      size_bytes = 4 PASSES - there is no minimum-size or cross-field
 *      validation (root finding DF-1946).
 *   3. sdma_v3_0.c then reads:
 *        hdr->header.ucode_version        (common_firmware_header + 16)
 *        hdr->ucode_feature_version       (sdma v1_0 header  + 32)
 *        header->ucode_size_bytes         (common header      + 24)
 *      and folds ucode_size_bytes into adev->firmware.fw_size.
 *
 * With datasize == 4 every one of those reads runs past the end of the
 * buffer; against the guard page the first read (offset 16) faults with
 * SIGSEGV - proof of the out-of-bounds access without needing ASan.
 * Struct layouts mirror amdgpu_ucode.h:26-37 and :120-126.
 *
 * Threat model: the firmware file is root-installed (DragonFly amdgpufw_*
 * module firmware), which is why DF-1598/DF-1553/DF-1451 are Low.  This
 * harness proves the parsing primitive only - the kernel path itself is
 * hardware-gated (Tonga/Polaris/Fiji/Carrizo/Stoney SDMA engine).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>

/* make the proof readable even when we die on the guard page */
#define P(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0)

struct common_firmware_header {		/* amdgpu_ucode.h:26-37 */
	uint32_t size_bytes;
	uint32_t header_size_bytes;
	uint16_t header_version_major, header_version_minor;
	uint16_t ip_version_major, ip_version_minor;
	uint32_t ucode_version;		/* offset 16 */
	uint32_t ucode_size_bytes;	/* offset 24 */
	uint32_t ucode_array_offset_bytes;
	uint32_t crc32;
};

struct sdma_firmware_header_v1_0 {	/* amdgpu_ucode.h:120-126 */
	struct common_firmware_header header;
	uint32_t ucode_feature_version;	/* offset 32 */
	uint32_t ucode_change_version;
	uint32_t jt_offset;
	uint32_t jt_size;
};

struct firmware {			/* the fields sdma_v3_0.c uses */
	const uint8_t *data;
	size_t datasize;
};

/* amdgpu_ucode.c:251-260, verbatim (little-endian host) */
static int amdgpu_ucode_validate(const struct firmware *fw)
{
	const struct common_firmware_header *hdr =
		(const struct common_firmware_header *)fw->data;

	if (fw->datasize == hdr->size_bytes)
		return 0;
	return -1;
}

int main(void)
{
	long pg = sysconf(_SC_PAGESIZE);
	uint8_t *map = mmap(NULL, pg * 2, PROT_READ | PROT_WRITE,
			    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (map == MAP_FAILED) {
		perror("mmap");
		return 1;
	}
	if (mprotect(map + pg, pg, PROT_NONE) != 0) {
		perror("mprotect");
		return 1;
	}

	/* 4-byte firmware blob at the very end of the mapped page */
	uint8_t *blob = map + pg - 4;
	uint32_t four = 4;
	memcpy(blob, &four, 4);		/* hdr->size_bytes = 4 */

	struct firmware fw = { blob, 4 };
	int rc = amdgpu_ucode_validate(&fw);
	P("amdgpu_ucode_validate(4-byte blob) = %d (0 == accepted)\n", rc);
	if (rc != 0) {
		P("VALIDATE REJECTED - harness premise broken\n");
		return 1;
	}
	P("validate passed: no minimum-size check (DF-1946 root), "
	       "sdma_v3_0.c proceeds to read sub-header fields\n");

	/* sdma_v3_0.c:315-317 */
	const struct sdma_firmware_header_v1_0 *hdr =
		(const struct sdma_firmware_header_v1_0 *)fw.data;
	uint32_t fw_version      = hdr->header.ucode_version;	 /* OOB read @+16 */
	P("survived ucode_version read (page not guarded there?)\n");

	uint32_t feature_version = hdr->ucode_feature_version;	 /* OOB read @+32 */
	P("survived ucode_feature_version read\n");

	/* sdma_v3_0.c:324-326 */
	uint32_t fw_size_add = (hdr->header.ucode_size_bytes + 4095) & ~4095u;
	P("kernel consumed: fw_version=%08x feature_version=%08x "
	       "fw_size += %u\n", fw_version, feature_version, fw_size_add);

	P("NOT REACHED: one of the reads above faults into the guard "
	       "page == heap out-of-bounds read in the kernel equivalent\n");
	return 0;
}
