CVE-2020-27932: iOS Kernel privesc with turnstiles

Posted by Ian Beer, Project Zero (2021-02-04)

Disclosure or Patch Date: 5 November 2020

Product: Apple iOS


Affected Versions: iOS 14.1 and previous

First Patched Version: iOS 14.2

Issue/Bug Report:

Patch CL: N/A

Bug-Introducing CL: N/A

Proof-of-Concept: N/A
Exploit Sample:

Access to the exploit sample? Yes

Reporter(s): Ian Beer of Google Project Zero

Bug Class: Type Confusion

Vulnerability Details: A kernel type confusion between an ipc_port pointer and a host_notify_entry pointer due to failure to account for the semantics of IKOT_HOST_NOTIFY ports in turnstile code.

IKOT_HOST_NOTIFY ports have slightly unusual semantics: the host_request_notification method allows userspace to take a regular, userspace-owned mach port and set the kobject field to point to a host_notify_entry object. The kobject field is actually a member of a fairly large union (kdata) leading to plenty of opportunities for type confusions if kernel code doesn't account for the semantics of IKOT_HOST_NOTIFY ports (i.e., if the kernel code doesn't know that userspace-owned ports can suddenly become IKOT_HOST_NOTIFY ports.)

union {

  ipc_kobject_t kobject;

  ipc_kobject_label_t kolabel;

  ipc_importance_task_t imp_task;

  ipc_port_t sync_inheritor_port;

  struct knote *sync_inheritor_knote;

  struct turnstile *sync_inheritor_ts;

} kdata;

There have been type confusions here before; for example a type confusion between imp_task and kobject (via the IKOT_HOST_NOTIFY trick) was fixed in MacOS 10.10.

In this case the turnstiles code added around iOS 12 added the sync_inheritor_port field to the kdata union to indicate the destination port to which a new type of port called a special reply port had been sent. Through some mach port gymnastics it was possible to get the kernel to read a pointer to a host_notify_entry as a ipc_port pointer (via sync_inheritor_port) and then cause an out of bounds write.

Specifically: send a mach message to a destination port and attach the thread_special_reply_port as the msgh_local_port with a SEND_ONCE disposition.

The trick to make something bad happen is to set the MACH_SEND_SYNC_OVERRIDE flag when sending that message. This allows you to change the ip_sync_link_state value away from PORT_SYNC_LINK_ANY.

After sending that message, convert the thread's special_reply_port to a IKOT_HOST_NOTIFY via host_request_notification.

Then attempt to receive a message on that special_reply_port and you'll hit the type-confusion when ipc_port_adjust_special_reply_port_locked reads special_reply_port->ip_sync_inheritor_port expecting a valid port pointer, but actually finds a host_notify_entry pointer because the special_reply_port was converted to a host_notify port.

Is the exploit method known? Analysis is incomplete at this time.
Exploit method: There are some neat tricks to turn this into an arbitrary read/write; I haven't completed that analysis phase yet.

How do you think you would have found this bug? This bug could be seen as a variant of earlier issues involving this union; it could have been found via manual review if you were familiar with those earlier issues. Actually figuring out the conditions required to cause an exploitable type confusion are non-trivial, and I wouldn't expect a fuzzer to find them.

(Historical/present/future) context of bug: 

Areas/approach for variant analysis: I would suggest auditing similar uses of complex unions.

Found variants: N/A

Structural improvements: The use of a union here seems unnecessary; the memory saving is negligible on modern systems. My suggestion would be to either add another memory to ipc_port which stores the current valid field of the kdata union, or break the union fields out into separate members.

Potential detection methods for similar 0-days:

Other references:

No comments:

Post a Comment