Apple iOS/macOS Kernel - Memory Disclosure Due to Lack of Bounds Checking in netagent Socket Option Handling

EDB-ID: 42055
Author: Google Security Research
Published: 2017-05-23
CVE: N/A
Type: Dos
Platform: Multiple
Aliases: N/A
Advisory/Source: Link
Tags: N/A
Vulnerable App: N/A

 Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1140 

netagent_ctl_setopt is the setsockopt handler for netagent control sockets. Options of type
NETAGENT_OPTION_TYPE_REGISTER are handled by netagent_handle_register_setopt. Here's the code:

static errno_t
netagent_handle_register_setopt(struct netagent_session *session, u_int8_t *payload,
u_int32_t payload_length)
{
int data_size = 0;
struct netagent_wrapper *new_wrapper = NULL;
u_int32_t response_error = 0;
struct netagent *register_netagent = (struct netagent *)(void *)payload; <----------- (a)

if (session == NULL) {
NETAGENTLOG0(LOG_ERR, "Failed to find session");
response_error = EINVAL;
goto done;
}

if (payload == NULL) {
NETAGENTLOG0(LOG_ERR, "No payload received");
response_error = EINVAL;
goto done;
}

if (session->wrapper != NULL) {
NETAGENTLOG0(LOG_ERR, "Session already has a registered agent");
response_error = EINVAL;
goto done;
}

if (payload_length < sizeof(struct netagent)) { <----------- (b)
NETAGENTLOG(LOG_ERR, "Register message size too small for agent: (%d < %d)",
payload_length, sizeof(struct netagent));
response_error = EINVAL;
goto done;
}

data_size = register_netagent->netagent_data_size;
if (data_size < 0 || data_size > NETAGENT_MAX_DATA_SIZE) { <----------- (c)
NETAGENTLOG(LOG_ERR, "Register message size could not be read, data_size %d",
data_size);
response_error = EINVAL;
goto done;
}

MALLOC(new_wrapper, struct netagent_wrapper *, sizeof(*new_wrapper) + data_size, M_NETAGENT, M_WAITOK);
if (new_wrapper == NULL) {
NETAGENTLOG0(LOG_ERR, "Failed to allocate agent");
response_error = ENOMEM;
goto done;
}

memset(new_wrapper, 0, sizeof(*new_wrapper) + data_size);
memcpy(&new_wrapper->netagent, register_netagent, sizeof(struct netagent) + data_size); <------------ (d)

response_error = netagent_handle_register_inner(session, new_wrapper);
if (response_error != 0) {
FREE(new_wrapper, M_NETAGENT);
goto done;
}

NETAGENTLOG0(LOG_DEBUG, "Registered new agent");
netagent_post_event(new_wrapper->netagent.netagent_uuid, KEV_NETAGENT_REGISTERED, TRUE);

done:
return response_error;
}


The payload and payload_length arguments are the socket option buffer which has be copied in to the kernel.

At (a) this is cast to a struct netagent and at (b) it's checked whether the payload is big enough to contain this structure.
Then at (c) an int read from the buffer is compared against a lower and upper bound and then used at (d) as the size of
data to copy from inside the payload buffer. It's not checked that the payload buffer is actually big enough to contain
data_size bytes of data though.

This oob data can then be retreived by userspace via the SIOCGIFAGENTDATA64 ioctl. This poc will dump 4k of kernel heap.

Tested on MacOS 10.12.3 (16D32) on MacBookPro10,1
*/

// ianbeer
#if 0
iOS/MacOS kernel memory disclosure due to lack of bounds checking in netagent socket option handling

netagent_ctl_setopt is the setsockopt handler for netagent control sockets. Options of type
NETAGENT_OPTION_TYPE_REGISTER are handled by netagent_handle_register_setopt. Here's the code:

static errno_t
netagent_handle_register_setopt(struct netagent_session *session, u_int8_t *payload,
u_int32_t payload_length)
{
int data_size = 0;
struct netagent_wrapper *new_wrapper = NULL;
u_int32_t response_error = 0;
struct netagent *register_netagent = (struct netagent *)(void *)payload; <----------- (a)

if (session == NULL) {
NETAGENTLOG0(LOG_ERR, "Failed to find session");
response_error = EINVAL;
goto done;
}

if (payload == NULL) {
NETAGENTLOG0(LOG_ERR, "No payload received");
response_error = EINVAL;
goto done;
}

if (session->wrapper != NULL) {
NETAGENTLOG0(LOG_ERR, "Session already has a registered agent");
response_error = EINVAL;
goto done;
}

if (payload_length < sizeof(struct netagent)) { <----------- (b)
NETAGENTLOG(LOG_ERR, "Register message size too small for agent: (%d < %d)",
payload_length, sizeof(struct netagent));
response_error = EINVAL;
goto done;
}

data_size = register_netagent->netagent_data_size;
if (data_size < 0 || data_size > NETAGENT_MAX_DATA_SIZE) { <----------- (c)
NETAGENTLOG(LOG_ERR, "Register message size could not be read, data_size %d",
data_size);
response_error = EINVAL;
goto done;
}

MALLOC(new_wrapper, struct netagent_wrapper *, sizeof(*new_wrapper) + data_size, M_NETAGENT, M_WAITOK);
if (new_wrapper == NULL) {
NETAGENTLOG0(LOG_ERR, "Failed to allocate agent");
response_error = ENOMEM;
goto done;
}

memset(new_wrapper, 0, sizeof(*new_wrapper) + data_size);
memcpy(&new_wrapper->netagent, register_netagent, sizeof(struct netagent) + data_size); <------------ (d)

response_error = netagent_handle_register_inner(session, new_wrapper);
if (response_error != 0) {
FREE(new_wrapper, M_NETAGENT);
goto done;
}

NETAGENTLOG0(LOG_DEBUG, "Registered new agent");
netagent_post_event(new_wrapper->netagent.netagent_uuid, KEV_NETAGENT_REGISTERED, TRUE);

done:
return response_error;
}


The payload and payload_length arguments are the socket option buffer which has be copied in to the kernel.

At (a) this is cast to a struct netagent and at (b) it's checked whether the payload is big enough to contain this structure.
Then at (c) an int read from the buffer is compared against a lower and upper bound and then used at (d) as the size of
data to copy from inside the payload buffer. It's not checked that the payload buffer is actually big enough to contain
data_size bytes of data though.

This oob data can then be retreived by userspace via the SIOCGIFAGENTDATA64 ioctl. This poc will dump 4k of kernel heap.

Tested on MacOS 10.12.3 (16D32) on MacBookPro10,1
Related Posts