DF-3433 / stf_race.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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | /* * DF-3433 - if_stf teardown races the lock-free netisr encaptab walk. * * Twin of DF-3425 (gif), stf instance: * - encap4_input() (ip_encap.c:131-241) walks the global encaptab list * with NO lock on netisr0 for every inbound IPv4 proto-41 packet, * calling ep->func(m,off,proto,ep->arg) for each matching entry. * - if_stf registers exactly one such entry at MOD_LOAD: * encap_attach_func(AF_INET, IPPROTO_IPV6, stf_encapcheck, ..., sc) * (if_stf.c:178-184) * - kldunload if_stf -> stfmodevent(MOD_UNLOAD) (if_stf.c:210-224) has NO * in-use guard and NO serialization against netisr0: encap_detach() does * LIST_REMOVE + kfree(ep) (ip_encap.c:411-426), then the softc itself is * kfree'd (if_stf.c:222-223). A concurrent walk that already fetched the * stf ep (or is about to call through it) dereferences freed memory and * calls into unloaded module text (stf_encapcheck reads * sc->sc_if.if_flags at if_stf.c:250 on the freed softc). * * Harness: 30 statically configured gif tunnels lengthen the walk (gif * entries attach with proto == -1 so their func is called for proto 41 too, * in_gif.c:378), 4 flood threads push proto-41 packets to 127.0.0.1 through * raw sockets (kernel builds the outer header, in_proto.c:235-238 -> * encap4_input), one bounce thread cycles kldload/kldunload of if_stf. * * Run as root. Expected on hit: Fatal trap 12 in stf_encapcheck / the * encap4_input walk / freed module text. */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/sockio.h> #include <net/if.h> #include <netinet/in.h> #include <netinet/in_var.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #define NSTATIC 30 /* gif0..gif29 walk lengtheners */ static int cmdsock; static volatile unsigned long n_flood, n_cyc, n_err; static volatile int stop; static void mk_sin(struct sockaddr_in *sin, uint32_t a) { memset(sin, 0, sizeof(*sin)); sin->sin_family = AF_INET; sin->sin_len = sizeof(struct sockaddr_in); sin->sin_addr.s_addr = htonl(a); } static int gif_create(const char *name) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); return ioctl(cmdsock, SIOCIFCREATE, &ifr); } static int gif_set_tunnel(const char *name, uint32_t src, uint32_t dst) { struct in_aliasreq ifra; memset(&ifra, 0, sizeof(ifra)); strlcpy(ifra.ifra_name, name, sizeof(ifra.ifra_name)); mk_sin(&ifra.ifra_addr, src); mk_sin(&ifra.ifra_dstaddr, dst); return ioctl(cmdsock, SIOCSIFPHYADDR, &ifra); } static void * flood(void *arg) { struct sockaddr_in to; unsigned char pkt[36]; /* inner dummy ipv6-ish payload */ /* kernel prepends outer IPv4 header w/ proto = IPPROTO_IPV6 (41) */ int s = socket(AF_INET, SOCK_RAW, IPPROTO_IPV6); if (s < 0) { perror("raw socket"); return NULL; } memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_len = sizeof(to); to.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 -> lo0 */ memset(pkt, 0x45, sizeof(pkt)); while (!stop) { if (sendto(s, pkt, sizeof(pkt), 0, (struct sockaddr *)&to, sizeof(to)) > 0) n_flood++; else n_err++; } close(s); return NULL; } static void * bouncer(void *arg) { /* stateless: unload (which only succeeds when loaded), then reload */ while (!stop) { if (system("kldunload if_stf") == 0) { n_cyc++; if (system("kldload if_stf") != 0) n_err++; } else n_err++; } system("kldunload if_stf"); return NULL; } int main(int argc, char **argv) { pthread_t th[5]; char name[32]; int i, secs = argc > 1 ? atoi(argv[1]) : 240; uint32_t base; cmdsock = socket(AF_INET, SOCK_DGRAM, 0); if (cmdsock < 0) { perror("dgram socket"); return 1; } /* walk lengtheners: gif0..gif29 with distinct endpoints * (gif may be compiled into the kernel; kldload is best-effort) */ if (system("kldload if_gif") != 0) fprintf(stderr, "kldload if_gif: %s (continuing; " "pseudo-device may be compiled in)\n", "best-effort"); for (i = 0, base = 0x0a000000; i < NSTATIC; i++, base += 0x202) { snprintf(name, sizeof(name), "gif%d", i); if (gif_create(name) == 0) gif_set_tunnel(name, base + 1, base + 2); } /* the victim: stf ep sits at the list head (LIST_INSERT_HEAD) */ if (system("kldload if_stf") != 0) { perror("kldload if_stf"); return 1; } for (i = 0; i < 4; i++) pthread_create(&th[i], NULL, flood, NULL); pthread_create(&th[4], NULL, bouncer, NULL); printf("racing: %d static gifs, if_stf load/unload bounce, " "4 proto-41 floods\n", NSTATIC); for (i = 0; i < secs; i += 10) { sleep(10); printf("t+%ds: flood=%lu unload_cyc=%lu err=%lu\n", i + 10, n_flood, n_cyc, n_err); fflush(stdout); } stop = 1; for (i = 0; i < 5; i++) pthread_join(th[i], NULL); printf("done (no panic): flood=%lu unload_cyc=%lu err=%lu\n", n_flood, n_cyc, n_err); return 0; } |