DF-3407 / race_tun.c
/* * DF-3407 -- tun(4) softc lifetime race reproducer. * * Races cdev open()/close() on the /dev/tun autoclone device against * SIOCIFDESTROY/SIOCIFCREATE on the resulting tunN units. * * Window 1 (destroy-vs-close): tunclose() clears TUN_OPEN * (sys/net/tun/if_tun.c:317) and then runs a long unlocked body -- * ifq_purge_all, if_down, if_purgeaddrs_nolink, EVENTHANDLER, * rt_ifannouncemsg, funsetown, KNOTE (if_tun.c:320-347) -- before it * itself calls if_clone_destroy() (if_tun.c:352). A concurrent * SIOCIFDESTROY passes the TUN_OPEN gate in tun_clone_destroy() * (if_tun.c:419) the moment TUN_OPEN is cleared and frees the softc * (tundestroy -> kfree, if_tun.c:270) while tunclose is still using it. * * Window 2 (destroy-vs-open): tunopen() caches dev->si_drv1 (if_tun.c:286) * and then writes sc->tun_pid / sc->tun_flags |= TUN_OPEN * (if_tun.c:292-293) with no lock; a destroy that slips between the read * and the write operates on freed memory. * * Both sides require root (tunopen: caps_priv_check(SYSCAP_RESTRICTEDROOT), * if_tun.c:283; SIOCIFDESTROY: caps_priv_check, sys/net/if.c:2012-2016). * * Build: cc -O2 -o race_tun race_tun.c * Run: ./race_tun <seconds> (as root) */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/wait.h> #include <net/if.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> static int rtsock; static int tun_destroy(int unit) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tun%d", unit); return ioctl(rtsock, SIOCIFDESTROY, &ifr); } int main(int argc, char **argv) { long seconds = 60; time_t end; long opens = 0, destroy_ok = 0; pid_t child; if (argc > 1) seconds = atol(argv[1]); rtsock = socket(AF_ROUTE, SOCK_RAW, 0); if (rtsock < 0) { perror("AF_ROUTE socket"); return 1; } if (geteuid() != 0) { fprintf(stderr, "must run as root\n"); return 1; } child = fork(); if (child == 0) { /* destroyer: hammer SIOCIFDESTROY on the unit range */ for (;;) { int u; for (u = 0; u <= 32; ++u) { if (tun_destroy(u) == 0) destroy_ok++; } } /* NOTREACHED */ } /* opener/closer: autoclone open creates a unit, close destroys it */ end = time(NULL) + seconds; while (time(NULL) < end) { int fd = open("/dev/tun", O_RDWR); if (fd >= 0) { close(fd); opens++; } } kill(child, SIGKILL); wait(NULL); printf("done: %ld open/close cycles, %ld destroys won\n", opens, destroy_ok); return 0; } |