Exploit-DB #39378
Description: Apple Mac OSX / iOS - NECP System Control Socket Packet Parsing Kernel Code Execution Integer Overflow
Type: dos
Platform: multiple
Date: 2016-01-28
Author: Google Security Research

Exploit:
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=543

NKE control sockets are documented here: https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/NKEConceptual/control/control.html

By default there are actually a bunch of these providers; they are however all only accessible to root. Nevertheless, on iOS and now (thanks to SIP)
OS X this is a real security boundary.

necp control sockets are implemented in necp.c. The messages themselves consist of a simple header followed by type-length-value entries.
The type field is a single byte and the length is a size_t (ie 8 bytes.)

by sending a packed with an id of NECP_PACKET_TYPE_POLICY_ADD we can reach the following loop:

  // Read policy conditions
  for (cursor = necp_packet_find_tlv(packet, offset, NECP_TLV_POLICY_CONDITION, &error, 0);
    cursor >= 0;
    cursor = necp_packet_find_tlv(packet, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) {
    size_t condition_size = 0;
    necp_packet_get_tlv_at_offset(packet, cursor, 0, NULL, &condition_size);

    if (condition_size > 0) {
      conditions_array_size += (sizeof(u_int8_t) + sizeof(size_t) + condition_size);
    }
  }

The necp_packet_{find|get}_* functions cope gracefully if the final tlv is waaay bigger than the actual message (like 2^64-1 ;) )

This means that we can overflow conditions_array_size to anything we want very easily. In this PoC the packet contains three policy conditions:

one of length 1; one of length 1024 and one of length 2^64-1051;

later conditions_array_size is used as the size of a memory allocation:

  MALLOC(conditions_array, u_int8_t *, conditions_array_size, M_NECP, M_WAITOK);

There is then a memory copying loop operating on the undersized array:

  conditions_array_cursor = 0;
  for (cursor = necp_packet_find_tlv(packet, offset, NECP_TLV_POLICY_CONDITION, &error, 0);
    cursor >= 0;
    cursor = necp_packet_find_tlv(packet, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) {
    u_int8_t condition_type = NECP_TLV_POLICY_CONDITION;
    size_t condition_size = 0;
    necp_packet_get_tlv_at_offset(packet, cursor, 0, NULL, &condition_size);
    if (condition_size > 0 && condition_size <= (conditions_array_size - conditions_array_cursor)) {   <-- (a)
      // Add type
      memcpy((conditions_array + conditions_array_cursor), &condition_type, sizeof(condition_type));
      conditions_array_cursor += sizeof(condition_type);

      // Add length
      memcpy((conditions_array + conditions_array_cursor), &condition_size, sizeof(condition_size));
      conditions_array_cursor += sizeof(condition_size);

      // Add value
      necp_packet_get_tlv_at_offset(packet, cursor, condition_size, (conditions_array + conditions_array_cursor), NULL);  <-- (b)

There is actually an extra check at (a); this is why we need the first policy_condition of size one (so that the second time through the
loop (conditions_array_size[1] - conditions_array_cursor[9]) will underflow allowing us to reach the necp_packet_get_tlv_at_offset call which will
then copy the second 1024 byte policy.

By contstructing the policy like this we can choose both the allocation size and the overflow amount, a nice primitive for an iOS kernel exploit :)

this will crash in weird ways due to the rather small overflow; you can mess with the PoC to make it crash more obviously! But just run this PoC a bunch
of times and you'll crash :)

Tested on MacBookAir 5,2 w/ OS X 10.10.5 (14F27)
*/

// ianbeer

/*
iOS and OS X kernel code execution due to integer overflow in NECP system control socket packet parsing

NKE control sockets are documented here: https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/NKEConceptual/control/control.html

By default there are actually a bunch of these providers; they are however all only accessible to root. Nevertheless, on iOS and now (thanks to SIP)
OS X this is a real security boundary.

necp control sockets are implemented in necp.c. The messages themselves consist of a simple header followed by type-length-value entries.
The type field is a single byte and the length is a size_t (ie 8 bytes.)

by sending a packed with an id of NECP_PACKET_TYPE_POLICY_ADD we can reach the following loop:

  // Read policy conditions
  for (cursor = necp_packet_find_tlv(packet, offset, NECP_TLV_POLICY_CONDITION, &error, 0);
    cursor >= 0;
    cursor = necp_packet_find_tlv(packet, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) {
    size_t condition_size = 0;
    necp_packet_get_tlv_at_offset(packet, cursor, 0, NULL, &condition_size);

    if (condition_size > 0) {
      conditions_array_size += (sizeof(u_int8_t) + sizeof(size_t) + condition_size);
    }
  }

The necp_packet_{find|get}_* functions cope gracefully if the final tlv is waaay bigger than the actual message (like 2^64-1 ;) )

This means that we can overflow conditions_array_size to anything we want very easily. In this PoC the packet contains three policy conditions:

one of length 1; one of length 1024 and one of length 2^64-1051;

later conditions_array_size is used as the size of a memory allocation:

  MALLOC(conditions_array, u_int8_t *, conditions_array_size, M_NECP, M_WAITOK);

There is then a memory copying loop operating on the undersized array:

  conditions_array_cursor = 0;
  for (cursor = necp_packet_find_tlv(packet, offset, NECP_TLV_POLICY_CONDITION, &error, 0);
    cursor >= 0;
    cursor = necp_packet_find_tlv(packet, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) {
    u_int8_t condition_type = NECP_TLV_POLICY_CONDITION;
    size_t condition_size = 0;
    necp_packet_get_tlv_at_offset(packet, cursor, 0, NULL, &condition_size);
    if (condition_size > 0 && condition_size <= (conditions_array_size - conditions_array_cursor)) {   <-- (a)
      // Add type
      memcpy((conditions_array + conditions_array_cursor), &condition_type, s