/*
 * DF-3592 — si_ib_parse() lacks any packet-fits-in-IB validation; packet
 * handlers read up to ib[idx+6] on a count-0/short packet at the end of the
 * IB -> kernel OOB read past the exact-size sa_bo sub-allocation; and
 * PACKET3_WRITE_DATA with pkt->count < 2 underflows into a ~2^30-iteration
 * register-walk spin (per-CSioctl CPU stall).
 *
 * Statement-faithful logic harness of sys/dev/drm/radeon/si.c:
 *   - si_ib_parse()                    si.c:4722-4779
 *   - si_vm_packet3_gfx_check()        si.c:4516-4632 (entry + COPY_DATA +
 *     WRITE_DATA + COND_WRITE reads)
 *   - si_vm_reg_valid()                si.c:4397-4441
 * with ib->length_dw / packet headers exactly as a userspace RADEON_CS
 * ioctl (radeon_cs.c:599-653) would deliver them (dwords copied 1:1 into an
 * exact-size radeon_sa_bo sub-allocation, radeon_sa.c:190-212).
 *
 * No SI GPU on this guest (virtio-only), so the IB lives in an mmap'd page
 * ending at a PROT_NONE guard: the kernel-equivalent OOB read faults here
 * observably at the exact dword index the kernel would touch.
 *
 * Build:  cc -O -Wall -o ib_parse_oob ib_parse_oob.c
 * Fixed:  cc -O -Wall -DFIXED -o ib_parse_oob_fixed ib_parse_oob.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>

/* radeon_reg.h:3710-3713 */
#define RADEON_CP_PACKET_GET_TYPE(h)	(((h) >> 30) & 3)
#define RADEON_CP_PACKET_GET_COUNT(h)	(((h) >> 16) & 0x3FFF)
#define RADEON_CP_PACKET3_GET_OPCODE(h)	(((h) >> 8) & 0xFF)
#define RADEON_PACKET_TYPE3		3

/* sid.h:1636,1747 */
#define PACKET3_WRITE_DATA		0x37
#define PACKET3_COND_WRITE		0x45

struct radeon_cs_packet {
	unsigned	idx;
	unsigned	type;
	int		count;
	unsigned	one_reg_wr;
	unsigned	opcode;
};

struct radeon_ib {
	uint32_t	*ptr;
	uint32_t	length_dw;
};

static volatile unsigned oob_idx_marker;

/* si_vm_reg_valid(), si.c:4397-4441 (verbatim logic; config whitelist
 * elided to its default deny) */
static int si_vm_reg_valid(uint32_t reg)
{
	if (reg >= 0x28000)
		return 1;
	if (reg >= 0xB000 && reg < 0xC000)
		return 1;
	(void)reg;
	return 0;
}

static void segv_handler(int sig, siginfo_t *si, void *ctx)
{
	(void)sig; (void)ctx;
	printf("  SIGSEGV at %p\n", si->si_addr);
	printf("  OOB READ PROVEN: handler touched a dword beyond "
	       "ib->length_dw (marker idx=%u) — in-kernel this is a silent "
	       "read past the exact-size sa_bo sub-allocation\n",
	       oob_idx_marker);
	_exit(42);
}

/* --- si_vm_packet3_gfx_check() reading cases, si.c:4516-4632 --- */

static int gfx_COND_WRITE(uint32_t *ib, struct radeon_cs_packet *pkt)
{
	uint32_t idx = pkt->idx + 1;
	uint32_t idx_value = ib[idx];		/* si.c:4521 — 1 past header */
	if (idx_value & 0x100) {
		uint32_t reg = ib[idx + 5] * 4;	/* si.c:4595 — 6 past header */
		if (!si_vm_reg_valid(reg))
			return -1;
	}
	return 0;
}

static int gfx_WRITE_DATA(uint32_t *ib, struct radeon_cs_packet *pkt)
{
	uint32_t idx = pkt->idx + 1;
	uint32_t idx_value = ib[idx];
	uint32_t start_reg, i;
	if ((idx_value & 0xf00) == 0) {
		start_reg = ib[idx + 1] * 4;	/* si.c:4580 */
		if (idx_value & 0x10000) {
			if (!si_vm_reg_valid(start_reg))
				return -1;
		} else {
			/* si.c:4585: for (i = 0; i < (pkt->count - 2); i++)
			 * count < 2 underflows: (int)(0-2) as unsigned bound */
			printf("  WRITE_DATA register walk: count=%d -> "
			       "loop bound (pkt->count - 2) = %u\n",
			       pkt->count, (uint32_t)(pkt->count - 2));
#ifdef FIXED
			/* fix.diff: unsigned-safe bound */
			if (pkt->count <= 2)
				printf("FIXED: unsigned-safe walk bound -> "
				       "0 iterations (no spin)\n");
			for (i = 0; i + 2 < (uint32_t)pkt->count; i++) {
#else
			for (i = 0; i < (uint32_t)(pkt->count - 2); i++) {
#endif
				uint32_t reg = start_reg + (4 * i);
				if (!si_vm_reg_valid(reg)) {
					printf("  walk exited after %u "
					       "iterations (reg 0x%x invalid)\n",
					       i, reg);
					return -1;
				}
				if (i >= 5 * 1000 * 1000) {
					printf("  STILL SPINNING after "
					       "5,000,000 iterations; kernel "
					       "walk would continue to ~2^30 "
					       "before reg wraps invalid "
					       "(seconds of ioctl CPU)\n");
					return 0;
				}
			}
		}
	}
	return 0;
}

/* si_ib_parse(), si.c:4722-4779 */
static int si_ib_parse(struct radeon_ib *ib)
{
	int ret = 0;
	uint32_t idx = 0, i;
	struct radeon_cs_packet pkt;

	do {
		pkt.idx = idx;
		pkt.type = RADEON_CP_PACKET_GET_TYPE(ib->ptr[idx]);
		pkt.count = RADEON_CP_PACKET_GET_COUNT(ib->ptr[idx]);
		pkt.one_reg_wr = 0;
		switch (pkt.type) {
		case 0:
			printf("Packet0 not allowed!\n");
			ret = -1;
			break;
		case 2:
			idx += 1;
			break;
		case RADEON_PACKET_TYPE3:
			pkt.opcode = RADEON_CP_PACKET3_GET_OPCODE(ib->ptr[idx]);
#ifdef FIXED
			/* fix.diff: the whole packet must fit in the IB
			 * (mirrors radeon_cs.c:768 for the non-VM path) */
			if (pkt.count + 2 > ib->length_dw - idx) {
				printf("FIXED: packet (idx %u, count %d) "
				       "exceeds ib length %u — rejected\n",
				       idx, pkt.count, ib->length_dw);
				ret = -1;
				break;
			}
#endif
			if (pkt.opcode == PACKET3_COND_WRITE) {
				oob_idx_marker = pkt.idx;
				ret = gfx_COND_WRITE(ib->ptr, &pkt);
			} else if (pkt.opcode == PACKET3_WRITE_DATA) {
				ret = gfx_WRITE_DATA(ib->ptr, &pkt);
			} else {
				printf("  (opcode 0x%02x allowed-case, no "
				       "handler reads)\n", pkt.opcode);
			}
			idx += pkt.count + 2;
			break;
		default:
			printf("Unknown packet type %d !\n", pkt.type);
			ret = -1;
			break;
		}
		if (ret) {
			for (i = 0; i < ib->length_dw; i++)
				printf("\t0x%08x%s\n", ib->ptr[i],
				       i == idx ? " <---" : "");
			break;
		}
	} while (idx < ib->length_dw);

	return ret;
}

int main(void)
{
	struct sigaction sa;
	long pagesz = sysconf(_SC_PAGESIZE);
	uint32_t *ibmem;
	struct radeon_ib ib;
	int r;

	setvbuf(stdout, NULL, _IONBF, 0);

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = segv_handler;
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	/* ---- scenario A: WRITE_DATA count=0 register-walk underflow ---- */
	printf("== DF-3592 scenario A: WRITE_DATA pkt->count=0 -> "
	       "(count - 2) underflow spin ==\n");
	ibmem = mmap(NULL, pagesz * 2, PROT_READ | PROT_WRITE,
		     MAP_PRIVATE | MAP_ANON, -1, 0);
	if (ibmem == MAP_FAILED) { perror("mmap"); return 1; }
	ib.ptr = (uint32_t *)((char *)ibmem + 8);	/* mid-page, 4 dwords */
	ib.length_dw = 4;
	ib.ptr[0] = (3u << 30) | (0u << 16) | (0x37u << 8);	/* hdr */
	ib.ptr[1] = 0x00000000;	/* idx_value: dst=register, !inc */
	ib.ptr[2] = 0x0000A000;	/* start_reg = 0xA000*4 = 0x28000 (valid) */
	r = si_ib_parse(&ib);
	printf("  scenario A parse ret=%d\n\n", r);

	/* ---- scenario B: trailing COND_WRITE header, length_dw=1 ---- */
	printf("== DF-3592 scenario B: trailing TYPE3 COND_WRITE header, "
	       "length_dw=1 ==\n");
	ibmem = mmap(NULL, pagesz * 2, PROT_READ | PROT_WRITE,
		     MAP_PRIVATE | MAP_ANON, -1, 0);
	if (ibmem == MAP_FAILED) { perror("mmap"); return 1; }
	if (mprotect((char *)ibmem + pagesz, pagesz, PROT_NONE) != 0) {
		perror("mprotect"); return 1;
	}
	/* exact-size sub-allocation analogue: 4 bytes ending at the guard
	 * (kernel: sa_bo->eoffset = soffset + size, radeon_sa.c:205) */
	ib.ptr = (uint32_t *)((char *)ibmem + pagesz - 4);
	ib.length_dw = 1;
	ib.ptr[0] = (3u << 30) | (0u << 16) | (0x45u << 8);
	printf("  ib->length_dw=1, header=0x%08x (type3, count=0, "
	       "COND_WRITE)\n", ib.ptr[0]);
	printf("  gfx_check entry reads ib[1]; COND_WRITE reads ib[6] "
	       "(si.c:4595) — both past the 4-byte allocation\n");

	r = si_ib_parse(&ib);
#ifdef FIXED
	printf("FIXED build: scenario B parse ret=%d (rejected before any "
	       "handler read)\n", r);
	return (r != 0) ? 0 : 1;
#else
	printf("baseline: scenario B parse ret=%d — returned without fault?! "
	       "(in-kernel the read would be a silent heap overread)\n", r);
	return 1;
#endif
}
