DragonFlyBSD Kernel Audit
DF-2815 / churn.c
← back to finding ↓ download raw
/*
 * DF-2815 - kern_shutdown.c dumper state race churner (no in-loop I/O).
 *
 * Spams DIOCGKERNELDUMP (set/clear kernel dump device) against a disk
 * slice with no synchronization against dumpsys().  set_dumper()
 * (sys/kern/kern_shutdown.c:948) does an unlocked EBUSY check-then-write
 * of the global `dumper' struct; set_dumper(NULL) bzeros it.  dumpsys()
 * (kern_shutdown.c:980) checks dumper.dumper then hands &dumper (the
 * global itself, not a snapshot) to md_dumpsys(), which re-reads
 * di->priv on every dump write (minidump_machdep.c:95/143/348/427).
 *
 * A clear landing after dumpsys() has started the dump leaves
 * di->priv == NULL -> dev_ddump(NULL,...) -> dev_needmplock()
 * (kern_device.c:119) dereferences NULL->si_ops -> kernel page fault
 * while dumping.
 *
 * IMPORTANT: no file/console writes inside the churn loop -- the root
 * filesystem is force-unmounted seconds before the dump and any write
 * would block the churn threads forever (observed with an earlier
 * heartbeat-logging variant: churn stops at unmount time and the dump
 * completes cleanly).
 *
 * modes:
 *   0 - clear only
 *   1 - alternate set/clear every ioctl
 *   3 - mostly set, clear every CLEARMASK ioctls (dump starts, then dies)
 *
 * usage: churn <device> <seconds> <mode> <nthreads>
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/diskslice.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#define CLEARMASK 0x3F		/* clear every 64th ioctl in mode 3 */

static int fd;
static int confd = -1;
static int mode = 3;
static volatile unsigned long n_ok, n_err, n_set, n_clr;
static volatile int stop;

static void *
churn(void *x __unused)
{
	u_int u;
	unsigned long i = 0;
	static const char marker[] = "c";

	while (!stop) {
		switch (mode) {
		case 0:
			u = 0;
			break;
		case 1:
			u = (i & 1) ? 0 : 1;
			break;
		case 3:
		default:
			u = ((i & CLEARMASK) == 0) ? 0 : 1;
			break;
		}
		if (ioctl(fd, DIOCGKERNELDUMP, &u) == 0) {
			n_ok++;
			if (u)
				n_set++;
			else
				n_clr++;
		} else {
			n_err++;
		}
		i++;
	}
	return (NULL);
}

static void *
ticker(void *x __unused)
{
	/* no-op: console markers contaminate the experiment once the console
	 * enters polled mode during dumpsys; liveness is instead proven by
	 * the kproc-wait phase markers in the diagnostic variant */
	while (!stop)
		usleep(1000000);
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t tid[64], ttid;
	int nthreads = 2;
	int secs = 60;
	int i;

	if (argc < 2) {
		fprintf(stderr, "usage: %s <device> [secs] [mode] [nthreads]\n",
		    argv[0]);
		exit(1);
	}
	if (argc > 2)
		secs = atoi(argv[2]);
	if (argc > 3)
		mode = atoi(argv[3]);
	if (argc > 4)
		nthreads = atoi(argv[4]);
	if (nthreads > 64)
		nthreads = 64;

	fd = open(argv[1], O_RDONLY);
	if (fd < 0) {
		perror(argv[1]);
		exit(1);
	}
	confd = open("/dev/console", O_WRONLY | O_NONBLOCK);
	pthread_create(&ttid, NULL, ticker, NULL);
	for (i = 0; i < nthreads; i++)
		pthread_create(&tid[i], NULL, churn, NULL);
	sleep(secs);
	stop = 1;
	for (i = 0; i < nthreads; i++)
		pthread_join(tid[i], NULL);
	printf("churn: mode=%d ok=%lu err=%lu set=%lu clr=%lu\n",
	    mode, n_ok, n_err, n_set, n_clr);
	return (0);
}