CVE-2020-27932: iOS Kernel privesc with turnstiles
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.