Exploit-DB #46248
Description: iOS/macOS - 'task_swap_mach_voucher()' Use-After-Free
Type: dos
Platform: multiple
Date: 2019-01-25
Author: Google Security Research

Exploit:
/*
 * voucher_swap-poc.c
 * Brandon Azad
 */
#if 0
iOS/macOS: task_swap_mach_voucher() does not respect MIG semantics leading to use-after-free

The dangers of not obeying MIG semantics have been well documented: see issues 926 (CVE-2016-7612),
954 (CVE-2016-7633), 1417 (CVE-2017-13861, async_wake), 1520 (CVE-2018-4139), 1529 (CVE-2018-4206),
and 1629 (no CVE), as well as CVE-2018-4280 (blanket). However, despite numerous fixes and
mitigations, MIG issues persist and offer incredibly powerful exploit primitives. Part of the
problem is that MIG semantics are complicated and unintuitive and do not align well with the
kernel's abstractions.

Consider the MIG routine task_swap_mach_voucher():

	routine task_swap_mach_voucher(
			task		: task_t;
			new_voucher	: ipc_voucher_t;
		inout	old_voucher	: ipc_voucher_t);

Here's the (placeholder) implementation:

	kern_return_t
	task_swap_mach_voucher(
		task_t			task,
		ipc_voucher_t		new_voucher,
		ipc_voucher_t		*in_out_old_voucher)
	{
		if (TASK_NULL == task)
			return KERN_INVALID_TASK;

		*in_out_old_voucher = new_voucher;
		return KERN_SUCCESS;
	}

The correctness of this implementation depends on exactly how MIG ownership semantics are defined
for each of these parameters.

When dealing with Mach ports and out-of-line memory, ownership follows the traditional rules (the
ones violated by the bugs above):

1. All Mach ports (except the first) passed as input parameters are owned by the service routine if
   and only if the service routine returns success. If the service routine returns failure then MIG
   will deallocate the ports.

2. All out-of-line memory regions passed as input parameters are owned by the service routine if
   and only if the service routine returns success. If the service routine returns failure then MIG
   will deallocate all out-of-line memory.

But this is only part of the picture. There are more rules for other types of objects:

3. All objects with defined MIG translations that are passed as input-only parameters are borrowed
   by the service routine. For reference-counted objects, this means that the service routine is
   not given a reference, and hence a reference must be added if the service routine intends to
   keep the object around.

4. All objects with defined MIG translations that are returned in output parameters must be owned
   by the output parameter. For reference-counted objects, this means that output parameters
   consume a reference on the object.

And most unintuitive of all:

5. All objects with defined MIG translations that are passed as input in input-output parameters
   are owned (not borrowed!) by the service routine. This means that the service routine must
   consume the input object's reference.

Having defined MIG translations means that there is an automatic conversion defined between the
object type and its Mach port representation. A task port is one example of such a type: you can
convert a task port to the underlying task object using convert_port_to_task(), and you can convert
a task to its corresponding port using convert_task_to_port().

Getting back to Mach vouchers, this is the MIG definition of ipc_voucher_t:

	type ipc_voucher_t = mach_port_t
			intran: ipc_voucher_t convert_port_to_voucher(mach_port_t)
			outtran: mach_port_t convert_voucher_to_port(ipc_voucher_t)
			destructor: ipc_voucher_release(ipc_voucher_t)
		        ;

This definition means that MIG will automatically convert the voucher port input parameters to
ipc_voucher_t objects using convert_port_to_voucher(), convert the ipc_voucher_t output parameters
into ports using convert_voucher_to_port(), and discard any extra references using
ipc_voucher_release(). Note that convert_port_to_voucher() produces a voucher reference without
consuming a port reference, while convert_voucher_to_port() consumes a voucher reference and
produces a port reference.

To confirm our understanding of the MIG semantics outlined above, we can look at the function
_Xtask_swap_mach_voucher(), which is generated by MIG during the build process:

	mig_internal novalue _Xtask_swap_mach_voucher
		(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP)
	{
	...
		kern_return_t RetCode;
		task_t task;
		ipc_voucher_t new_voucher;
		ipc_voucher_t old_voucher;
	...
		task = convert_port_to_task(In0P->Head.msgh_request_port);

		new_voucher = convert_port_to_voucher(In0P->new_voucher.name);

		old_voucher = convert_port_to_voucher(In0P->old_voucher.name);

		RetCode = task_swap_mach_voucher(task, new_voucher, &old_voucher);

		ipc_voucher_release(new_voucher);

		task_deallocate(task);

		if (RetCode != KERN_SUCCESS) {
			MIG_RETURN_ERROR(OutP, RetCode);
		}
	...
		if (IP_VALID((ipc_port_t)In0P->old_voucher.name))
			ipc_port_release_send((ipc_port_t)In0P->old_voucher.name);

		if (IP_VALID((ipc_port_t)In0P->new_voucher.name))
			ipc_port_release_send((ipc_port_t)In0P->new_voucher.name);
	...
		OutP->old_voucher.name = (mach_port_t)convert_voucher_to_port(old_voucher);

		OutP->Head.msgh_bits |= MACH_MSGH_BITS_COMPLEX;
		OutP->Head.msgh_size = (mach_msg_size_t)(sizeof(Reply));
		OutP->msgh_body.msgh_descriptor_count = 1;
	}

Tracing where each of the references are going, we can deduce that:

1. The new_voucher parameter is deallocated with ipc_voucher_release() after invoking the service
   routine, so it is not owned by task_swap_mach_voucher(). In other words,
   task_swap_mach_voucher() is not given a reference on new_voucher.

2. The old_voucher parameter has a reference on it before it gets overwritten by
   task_swap_mach_voucher(), which means task_swap_mach_voucher() is being given a reference on the
   input value of old_voucher.

3. The value returned by task_swap_mach_voucher() in old_voucher is passed to
   convert_voucher_to_port(), which consumes a reference on the voucher. Thus,
   task_swap