Ian Beer, Project Zero (Originally posted on Project Zero blog 2021-02-04)

The Basics

Disclosure or Patch Date: 5 November 2020

Product: Apple iOS

Advisory: https://support.apple.com/en-us/HT211929

Affected Versions: iOS 14.1 and previous

First Patched Version: iOS 14.2

Issue/Bug Report: https://bugs.chromium.org/p/project-zero/issues/detail?id=2107

Patch CL: N/A

Bug-Introducing CL: N/A

Reporter(s): Ian Beer of Google Project Zero

The Code

Proof-of-concept: https://bugs.chromium.org/p/project-zero/issues/detail?id=2107

Exploit sample: N/A

Did you have access to the exploit sample when doing the analysis? Yes

The Vulnerability

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.

Patch analysis: N/A

Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.):

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:

The Exploit

(The terms exploit primitive, exploit strategy, exploit technique, and exploit flow are defined here.)

Exploit strategy (or strategies): Still under analysis.

Exploit flow: Still under analysis.

Known cases of the same exploit flow: Still under analysis.

Part of an exploit chain? This vulnerability was used as a part of an iOS exploit chain. It was used after the Safari RCE (CVE-2020-27930) and the kernel memory disclosure (CVE-2020-27950).

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why): 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.

0-day detection methods

Other References