xref: /xnu-11215.41.3/tests/reply_port_defense_client.c (revision 33de042d024d46de5ff4e89f2471de6608e37fa4)
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <pthread.h>
4 #include <unistd.h>
5 #include <mach/kern_return.h>
6 #include <mach/mach.h>
7 #include <mach/task.h>
8 #include <mach/thread_status.h>
9 #include <os/tsd.h>
10 #include <assert.h>
11 #include <sys/codesign.h>
12 #include <stdbool.h>
13 
14 #include "cs_helpers.h"
15 
16 #define MAX_TEST_NUM 10
17 
18 #if __arm64__
19 #define machine_thread_state_t          arm_thread_state64_t
20 #define EXCEPTION_THREAD_STATE          ARM_THREAD_STATE64
21 #define EXCEPTION_THREAD_STATE_COUNT    ARM_THREAD_STATE64_COUNT
22 #elif __x86_64__
23 #define machine_thread_state_t          x86_thread_state_t
24 #define EXCEPTION_THREAD_STATE          x86_THREAD_STATE
25 #define EXCEPTION_THREAD_STATE_COUNT    x86_THREAD_STATE_COUNT
26 #else
27 #error Unsupported architecture
28 #endif
29 
30 static mach_port_t
alloc_server_port(void)31 alloc_server_port(void)
32 {
33 	mach_port_t server_port = MACH_PORT_NULL;
34 	kern_return_t kr;
35 
36 	kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &server_port);
37 	assert(kr == 0);
38 
39 	kr = mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND);
40 	assert(kr == 0);
41 
42 	return server_port;
43 }
44 
45 static mach_port_t
alloc_provisional_reply_port()46 alloc_provisional_reply_port()
47 {
48 	kern_return_t kr;
49 	mach_port_t reply_port = MACH_PORT_NULL;
50 	mach_port_t task = mach_task_self();
51 
52 	mach_port_options_t opts = {
53 		.flags = MPO_PROVISIONAL_REPLY_PORT | MPO_INSERT_SEND_RIGHT,
54 	};
55 
56 	kr = mach_port_construct(mach_task_self(), &opts, 0, &reply_port);
57 	assert(kr == 0);
58 
59 	return reply_port;
60 }
61 
62 static mach_port_t
alloc_reply_port()63 alloc_reply_port()
64 {
65 	kern_return_t kr;
66 	mach_port_t reply_port = MACH_PORT_NULL;
67 	mach_port_t task = mach_task_self();
68 
69 	mach_port_options_t opts = {
70 		.flags = MPO_REPLY_PORT | MPO_INSERT_SEND_RIGHT,
71 	};
72 
73 	kr = mach_port_construct(mach_task_self(), &opts, 0, &reply_port);
74 	assert(kr == 0);
75 
76 	return reply_port;
77 }
78 
79 /* The rcv right of the port would be marked immovable. */
80 static void
test_immovable_receive_right(void)81 test_immovable_receive_right(void)
82 {
83 	kern_return_t kr;
84 	mach_port_t server_port = MACH_PORT_NULL, reply_port = MACH_PORT_NULL;
85 	struct {
86 		mach_msg_header_t header;
87 		mach_msg_body_t body;
88 		mach_msg_port_descriptor_t desc;
89 	} msg;
90 
91 	server_port = alloc_server_port();
92 	reply_port = alloc_reply_port();
93 
94 	msg.header.msgh_remote_port = server_port;
95 	msg.header.msgh_local_port = MACH_PORT_NULL;
96 	msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
97 	msg.header.msgh_size = sizeof msg;
98 
99 	msg.body.msgh_descriptor_count = 1;
100 
101 	msg.desc.name = reply_port;
102 	msg.desc.disposition = MACH_MSG_TYPE_MOVE_RECEIVE;
103 	msg.desc.type = MACH_MSG_PORT_DESCRIPTOR;
104 	kr = mach_msg_send(&msg.header);
105 
106 	printf("[reply_port_defense_client test_immovable_receive_right]: mach_msg2() returned %d\n", kr);
107 }
108 
109 /* The only way you could create a send once right is when you send the port in local port of a mach msg with MAKE_SEND_ONCE disposition. */
110 static void
test_make_send_once_right(void)111 test_make_send_once_right(void)
112 {
113 	kern_return_t kr;
114 	mach_port_t reply_port = alloc_reply_port();
115 	kr = mach_port_insert_right(mach_task_self(), reply_port, reply_port, MACH_MSG_TYPE_MAKE_SEND_ONCE);
116 	printf("[reply_port_defense_client test_make_send_once_right]: mach_port_insert_right() returned %d\n", kr);
117 }
118 
119 /* The send right of the port would only used for guarding a name in ipc space, it would not allow to send a message. */
120 static void
test_using_send_right(void)121 test_using_send_right(void)
122 {
123 	kern_return_t kr;
124 	mach_port_t reply_port = alloc_reply_port();
125 	struct {
126 		mach_msg_header_t header;
127 		mach_msg_body_t body;
128 	} msg;
129 
130 	msg.header.msgh_remote_port = reply_port;
131 	msg.header.msgh_local_port = MACH_PORT_NULL;
132 	msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
133 	msg.header.msgh_size = sizeof msg;
134 
135 	kr = mach_msg_send(&msg.header);
136 	printf("[reply_port_defense_client test_using_send_right]: mach_msg2() returned %d\n", kr);
137 }
138 
139 /* The send right of the port would only used for guarding a name in ipc space, it would not allowed to get moved. */
140 static void
test_move_send_right(void)141 test_move_send_right(void)
142 {
143 	kern_return_t kr;
144 	mach_port_t server_port = MACH_PORT_NULL, reply_port = MACH_PORT_NULL;
145 	struct {
146 		mach_msg_header_t header;
147 		mach_msg_body_t body;
148 		mach_msg_port_descriptor_t desc;
149 	} msg;
150 
151 	server_port = alloc_server_port();
152 	reply_port = alloc_reply_port();
153 
154 	msg.header.msgh_remote_port = server_port;
155 	msg.header.msgh_local_port = MACH_PORT_NULL;
156 	msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
157 	msg.header.msgh_size = sizeof msg;
158 
159 	msg.body.msgh_descriptor_count = 1;
160 
161 	msg.desc.name = reply_port;
162 	msg.desc.disposition = MACH_MSG_TYPE_MOVE_SEND;
163 	msg.desc.type = MACH_MSG_PORT_DESCRIPTOR;
164 
165 	kr = mach_msg_send(&msg.header);
166 	printf("[reply_port_defense_client test_move_send_right]: mach_msg2() returned %d\n", kr);
167 }
168 
169 static void
test_move_provisional_reply_port(void)170 test_move_provisional_reply_port(void)
171 {
172 	kern_return_t kr;
173 	mach_port_t server_port = MACH_PORT_NULL, reply_port = MACH_PORT_NULL;
174 	struct {
175 		mach_msg_header_t header;
176 		mach_msg_body_t body;
177 		mach_msg_port_descriptor_t desc;
178 	} msg;
179 
180 	server_port = alloc_server_port();
181 	reply_port = alloc_provisional_reply_port();
182 
183 	msg.header.msgh_remote_port = server_port;
184 	msg.header.msgh_local_port = MACH_PORT_NULL;
185 	msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
186 	msg.header.msgh_size = sizeof msg;
187 
188 	msg.body.msgh_descriptor_count = 1;
189 
190 	msg.desc.name = reply_port;
191 	msg.desc.disposition = MACH_MSG_TYPE_MOVE_RECEIVE;
192 	msg.desc.type = MACH_MSG_PORT_DESCRIPTOR;
193 
194 	kr = mach_msg_send(&msg.header);
195 
196 	printf("[reply_port_defense_client test_immovable_receive_right]: mach_msg2() returned %d\n", kr);
197 }
198 
199 static void
test_unentitled_thread_set_state(void)200 test_unentitled_thread_set_state(void)
201 {
202 	machine_thread_state_t ts;
203 	mach_msg_type_number_t count = MACHINE_THREAD_STATE_COUNT;
204 
205 	/* thread_set_state as a hardened binary should fail */
206 	kern_return_t kr = thread_get_state(mach_thread_self(), MACHINE_THREAD_STATE, (thread_state_t)&ts, &count);
207 
208 	kr = thread_set_state(mach_thread_self(), MACHINE_THREAD_STATE, (thread_state_t)&ts, count);
209 	assert(kr != KERN_SUCCESS);
210 	exit(-1); /* Should have crashed before here! */
211 }
212 
213 static void
unentitled_set_exception_ports_crash(void)214 unentitled_set_exception_ports_crash(void)
215 {
216 	mach_port_t exc_port = alloc_server_port();
217 
218 	/* thread_set_exception_ports as a hardened binary should fail */
219 	kern_return_t kr = thread_set_exception_ports(
220 		mach_thread_self(),
221 		EXC_MASK_ALL,
222 		exc_port,
223 		(exception_behavior_t)((unsigned int)EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES),
224 		EXCEPTION_THREAD_STATE);
225 
226 	/* thread_set_exception_ports is supposed to crash, unless the policy is turned off.
227 	 * Things that disable the policy: AMFI boot-args in use, SIP disabled,
228 	 * third party plugins in a process. The caller of this client will check
229 	 * whether the test crashed and correctly adhered to these policies.
230 	 */
231 	printf("thread_set_exception_ports did not crash\n");
232 }
233 
234 static void
unentitled_set_exception_ports_pass(void)235 unentitled_set_exception_ports_pass(void)
236 {
237 	mach_port_t exc_port = alloc_server_port();
238 
239 	/* thread_set_exception_ports with state *IDENTITY_PROTECTED should not fail */
240 	kern_return_t kr = thread_set_exception_ports(
241 		mach_thread_self(),
242 		EXC_MASK_ALL,
243 		exc_port,
244 		(exception_behavior_t)((unsigned int)EXCEPTION_STATE_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES),
245 		EXCEPTION_THREAD_STATE);
246 	assert(kr == 0);
247 
248 	kr = thread_set_exception_ports(
249 		mach_thread_self(),
250 		EXC_MASK_ALL,
251 		exc_port,
252 		(exception_behavior_t)((unsigned int)EXCEPTION_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES),
253 		EXCEPTION_THREAD_STATE);
254 	assert(kr == 0);
255 
256 	return;
257 }
258 
259 static void
exception_ports_crash(void)260 exception_ports_crash(void)
261 {
262 	kern_return_t kr;
263 	mach_port_t exc_port;
264 	mach_port_options_t opts = {
265 		.flags = MPO_INSERT_SEND_RIGHT | MPO_EXCEPTION_PORT,
266 	};
267 
268 	kr = mach_port_construct(mach_task_self(), &opts, 0ull, &exc_port);
269 	assert(kr == KERN_SUCCESS);
270 
271 	kr = task_register_hardened_exception_handler(current_task(),
272 	    0, EXC_MASK_BAD_ACCESS,
273 	    EXCEPTION_STATE_IDENTITY_PROTECTED, EXCEPTION_THREAD_STATE, exc_port);
274 
275 	kr = thread_set_exception_ports(
276 		mach_thread_self(),
277 		EXC_MASK_BAD_ACCESS,
278 		exc_port,
279 		(exception_behavior_t)((unsigned int)EXCEPTION_STATE_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES),
280 		EXCEPTION_THREAD_STATE);
281 
282 	printf("thread_set_exception_ports did not crash: %d\n", kr);
283 }
284 
285 static void
kobject_reply_port_defense(void)286 kobject_reply_port_defense(void)
287 {
288 	machine_thread_state_t ts;
289 	mach_msg_type_number_t count = MACHINE_THREAD_STATE_COUNT;
290 	mach_port_t port = MACH_PORT_NULL;
291 
292 	kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
293 	assert(kr == KERN_SUCCESS);
294 
295 	// make a kobject call
296 	kr = thread_get_state(mach_thread_self(), MACHINE_THREAD_STATE, (thread_state_t)&ts, &count);
297 	assert(kr == KERN_SUCCESS);
298 
299 	// set the MIG reply port to a "normal" port
300 	_os_tsd_set_direct(__TSD_MIG_REPLY, (void *)(uintptr_t)port);
301 
302 	kr = thread_get_state(mach_thread_self(), MACHINE_THREAD_STATE, (thread_state_t)&ts, &count);
303 
304 	printf("kobject call did not crash: %d\n", kr);
305 }
306 
307 int
main(int argc,char * argv[])308 main(int argc, char *argv[])
309 {
310 	uint32_t my_csflags = 0;
311 	bool thirdparty_hardened = !strcmp(argv[0], "./reply_port_defense_client_3P_hardened");
312 
313 	/* TODO add some sysctl which disabled platform binary bit here */
314 	if (my_csflags & CS_PLATFORM_BINARY == thirdparty_hardened) {
315 		printf("platform binary does not match expected\n");
316 		return -1;
317 	}
318 
319 
320 	void (*tests[MAX_TEST_NUM])(void) = {
321 		test_immovable_receive_right, /* 0 */
322 		test_make_send_once_right,
323 		test_using_send_right, /* 2 */
324 		test_move_send_right,
325 		test_move_provisional_reply_port, /* 4 */
326 		unentitled_set_exception_ports_crash,
327 		test_unentitled_thread_set_state, /* 6 */
328 		unentitled_set_exception_ports_pass,
329 		exception_ports_crash, /* 8 */
330 		kobject_reply_port_defense, /* 9 */
331 	};
332 
333 	if (argc < 2) {
334 		printf("[reply_port_defense_client]: Specify a test to run.");
335 		exit(-1);
336 	}
337 
338 	int test_num = atoi(argv[1]);
339 	printf("[reply_port_defense_client]: My Pid: %d Test num: %d third_party_hardened: %s\n",
340 	    getpid(), test_num, thirdparty_hardened ? "yes" : "no");
341 	fflush(stdout);
342 	if (test_num >= 0 && test_num < MAX_TEST_NUM) {
343 		(*tests[test_num])();
344 	} else {
345 		printf("[reply_port_defense_client]: Invalid test num. Exiting...\n");
346 		exit(-1);
347 	}
348 	printf("Child exiting cleanly!!\n");
349 	fflush(stdout);
350 	// return 0;
351 	exit(0);
352 }
353