1 /*
2 * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29 #include "exc_helpers.h"
30
31 #include <darwintest.h>
32 #include <ptrauth.h>
33 #include <stdbool.h>
34 #include <stdlib.h>
35
36 #if __arm64__
37 #define EXCEPTION_THREAD_STATE ARM_THREAD_STATE64
38 #define EXCEPTION_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT
39 #elif __x86_64__
40 #define EXCEPTION_THREAD_STATE x86_THREAD_STATE64
41 #define EXCEPTION_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT
42 #else
43 #error Unsupported architecture
44 #endif
45
46 #define EXCEPTION_IDENTITY_PROTECTED 4
47
48 bool verbose_exc_helper = true;
49
50 #define LOG_VERBOSE(format, ...) \
51 do { \
52 if (verbose_exc_helper) { \
53 T_LOG(format, ##__VA_ARGS__); \
54 } \
55 } while (0)
56
57 /**
58 * mach_exc_server() is a MIG-generated function that verifies the message
59 * that was received is indeed a mach exception and then calls
60 * catch_mach_exception_raise_state() to handle the exception.
61 */
62 extern boolean_t mach_exc_server(mach_msg_header_t *, mach_msg_header_t *);
63
64 extern kern_return_t
65 catch_mach_exception_raise(
66 mach_port_t exception_port,
67 mach_port_t thread,
68 mach_port_t task,
69 exception_type_t type,
70 exception_data_t codes,
71 mach_msg_type_number_t code_count);
72
73 extern kern_return_t
74 catch_mach_exception_raise_identity_protected(
75 __unused mach_port_t exception_port,
76 uint64_t thread_id,
77 mach_port_t task_id_token,
78 exception_type_t exception,
79 mach_exception_data_t codes,
80 mach_msg_type_number_t codeCnt);
81
82 extern kern_return_t
83 catch_mach_exception_raise_backtrace(
84 __unused mach_port_t exception_port,
85 mach_port_t kcdata_object,
86 exception_type_t exception,
87 mach_exception_data_t codes,
88 __unused mach_msg_type_number_t codeCnt);
89
90 extern kern_return_t
91 catch_mach_exception_raise_state(
92 mach_port_t exception_port,
93 exception_type_t type,
94 exception_data_t codes,
95 mach_msg_type_number_t code_count,
96 int *flavor,
97 thread_state_t in_state,
98 mach_msg_type_number_t in_state_count,
99 thread_state_t out_state,
100 mach_msg_type_number_t *out_state_count);
101
102 extern kern_return_t
103 catch_mach_exception_raise_state_identity(
104 mach_port_t exception_port,
105 mach_port_t thread,
106 mach_port_t task,
107 exception_type_t type,
108 exception_data_t codes,
109 mach_msg_type_number_t code_count,
110 int *flavor,
111 thread_state_t in_state,
112 mach_msg_type_number_t in_state_count,
113 thread_state_t out_state,
114 mach_msg_type_number_t *out_state_count);
115
116 /* Thread-local storage for exception server threads. */
117
118 struct exc_handler_callbacks {
119 exc_handler_callback_t state_callback;
120 exc_handler_protected_callback_t protected_callback;
121 exc_handler_state_protected_callback_t state_protected_callback;
122 exc_handler_backtrace_callback_t backtrace_callback;
123 };
124
125 static __thread struct exc_handler_callbacks tls_callbacks;
126
127 /*
128 * Return the (ptrauth-stripped) PC from the
129 * thread state passed to an exception handler.
130 */
131 static uint64_t
get_exception_pc(thread_state_t in_state)132 get_exception_pc(thread_state_t in_state)
133 {
134 #if __arm64__
135 arm_thread_state64_t *state = (arm_thread_state64_t*)(void *)in_state;
136 return arm_thread_state64_get_pc(*state);
137 #elif __x86_64__
138 x86_thread_state64_t *state = (x86_thread_state64_t*)(void *)in_state;
139 return state->__rip;
140 #else
141 T_FAIL("unknown architecture");
142 __builtin_unreachable();
143 #endif
144 }
145
146 /*
147 * Increment the PC in thread state `out_state` by `advance_pc` bytes.
148 */
149 static void
advance_exception_pc(size_t advance_pc,thread_state_t out_state)150 advance_exception_pc(
151 size_t advance_pc,
152 thread_state_t out_state)
153 {
154 /* disallow the sentinel value used by the exception handlers */
155 assert(advance_pc != EXC_HELPER_HALT);
156
157 #if __arm64__
158 arm_thread_state64_t *state = (arm_thread_state64_t*)(void *)out_state;
159
160 void *pc = (void*)(arm_thread_state64_get_pc(*state) + advance_pc);
161 /* Have to sign the new PC value when pointer authentication is enabled. */
162 pc = ptrauth_sign_unauthenticated(pc, ptrauth_key_function_pointer, 0);
163 arm_thread_state64_set_pc_fptr(*state, pc);
164 #elif __x86_64__
165 x86_thread_state64_t *state = (x86_thread_state64_t*)(void *)out_state;
166 state->__rip += advance_pc;
167 #else
168 (void)advance_pc;
169 T_FAIL("unknown architecture");
170 __builtin_unreachable();
171 #endif
172 }
173
174 /**
175 * This has to be defined for linking purposes, but it's unused.
176 */
177 kern_return_t
catch_mach_exception_raise(mach_port_t exception_port,mach_port_t thread,mach_port_t task,exception_type_t type,exception_data_t codes,mach_msg_type_number_t code_count)178 catch_mach_exception_raise(
179 mach_port_t exception_port,
180 mach_port_t thread,
181 mach_port_t task,
182 exception_type_t type,
183 exception_data_t codes,
184 mach_msg_type_number_t code_count)
185 {
186 #pragma unused(exception_port, thread, task, type, codes, code_count)
187 T_FAIL("Triggered catch_mach_exception_raise() which shouldn't happen...");
188 __builtin_unreachable();
189 }
190
191 kern_return_t
catch_mach_exception_raise_state_identity_protected(mach_port_t exception_port __unused,uint64_t thread_id,mach_port_t task_id_token,exception_type_t type,exception_data_t codes,mach_msg_type_number_t code_count,int * flavor,thread_state_t in_state,mach_msg_type_number_t in_state_count,thread_state_t out_state,mach_msg_type_number_t * out_state_count)192 catch_mach_exception_raise_state_identity_protected(
193 mach_port_t exception_port __unused,
194 uint64_t thread_id,
195 mach_port_t task_id_token,
196 exception_type_t type,
197 exception_data_t codes,
198 mach_msg_type_number_t code_count,
199 int *flavor,
200 thread_state_t in_state,
201 mach_msg_type_number_t in_state_count,
202 thread_state_t out_state,
203 mach_msg_type_number_t *out_state_count)
204 {
205 LOG_VERBOSE("Caught a mach exception!\n");
206
207 /* There should only be two code values. */
208 T_QUIET; T_ASSERT_EQ(code_count, 2, "Two code values were provided with the mach exception");
209
210 /**
211 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
212 * when setting the exception port.
213 */
214 mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
215 LOG_VERBOSE("Mach exception type %d, codes[0]: %#llx, codes[1]: %#llx\n",
216 type, codes_64[0], codes_64[1]);
217
218 /* Verify that we're receiving the expected thread state flavor. */
219 T_QUIET; T_ASSERT_EQ(*flavor, EXCEPTION_THREAD_STATE, "The thread state flavor is EXCEPTION_THREAD_STATE");
220 T_QUIET; T_ASSERT_EQ(in_state_count, EXCEPTION_THREAD_STATE_COUNT, "The thread state count is EXCEPTION_THREAD_STATE_COUNT");
221
222 *out_state_count = in_state_count; /* size of state object in 32-bit words */
223 memcpy((void*)out_state, (void*)in_state, in_state_count * 4);
224
225 size_t advance_pc = tls_callbacks.state_protected_callback(
226 task_id_token, thread_id, type, codes_64, in_state,
227 in_state_count, out_state, out_state_count);
228
229 if (advance_pc == EXC_HELPER_HALT) {
230 /* Exception handler callback says we can't continue. */
231 LOG_VERBOSE("Halting after exception");
232 return KERN_FAILURE;
233 }
234
235 if (advance_pc != 0) {
236 T_FAIL("unimplemented PC change from EXCEPTION_STATE_IDENTITY_PROTECTED callback");
237 return KERN_FAILURE;
238 }
239
240 /* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
241 return KERN_SUCCESS;
242 }
243
244
245 kern_return_t
catch_mach_exception_raise_identity_protected(__unused mach_port_t exception_port,uint64_t thread_id,mach_port_t task_id_token,exception_type_t exception,mach_exception_data_t codes,mach_msg_type_number_t codeCnt)246 catch_mach_exception_raise_identity_protected(
247 __unused mach_port_t exception_port,
248 uint64_t thread_id,
249 mach_port_t task_id_token,
250 exception_type_t exception,
251 mach_exception_data_t codes,
252 mach_msg_type_number_t codeCnt)
253 {
254 LOG_VERBOSE("Caught a mach exception!\n");
255
256 /* There should only be two code values. */
257 T_QUIET; T_ASSERT_EQ(codeCnt, 2, "Two code values were provided with the mach exception");
258
259 /**
260 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
261 * when setting the exception port.
262 */
263 mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
264 LOG_VERBOSE("Mach exception type %d, codes[0]: %#llx, codes[1]: %#llx\n",
265 exception, codes_64[0], codes_64[1]);
266
267 size_t advance_pc = tls_callbacks.protected_callback(
268 task_id_token, thread_id, exception, codes_64);
269
270 if (advance_pc == EXC_HELPER_HALT) {
271 /* Exception handler callback says we can't continue. */
272 LOG_VERBOSE("Halting after exception");
273 return KERN_FAILURE;
274 }
275
276 if (advance_pc != 0) {
277 T_FAIL("unimplemented PC change from EXCEPTION_IDENTITY_PROTECTED callback");
278 return KERN_FAILURE;
279 }
280
281 /* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
282 return KERN_SUCCESS;
283 }
284
285 /**
286 * This has to be defined for linking purposes, but it's unused.
287 */
288 kern_return_t
catch_mach_exception_raise_state(mach_port_t exception_port,exception_type_t type,exception_data_t codes,mach_msg_type_number_t code_count,int * flavor,thread_state_t in_state,mach_msg_type_number_t in_state_count,thread_state_t out_state,mach_msg_type_number_t * out_state_count)289 catch_mach_exception_raise_state(
290 mach_port_t exception_port,
291 exception_type_t type,
292 exception_data_t codes,
293 mach_msg_type_number_t code_count,
294 int *flavor,
295 thread_state_t in_state,
296 mach_msg_type_number_t in_state_count,
297 thread_state_t out_state,
298 mach_msg_type_number_t *out_state_count)
299 {
300 #pragma unused(exception_port, type, codes, code_count, flavor, in_state, in_state_count, out_state, out_state_count)
301 T_FAIL("Triggered catch_mach_exception_raise_state() which shouldn't happen...");
302 __builtin_unreachable();
303 }
304
305 /**
306 * Called by mach_exc_server() to handle the exception. This will call the
307 * test's exception-handler callback and will then modify
308 * the thread state to move to the next instruction.
309 */
310 kern_return_t
catch_mach_exception_raise_state_identity(mach_port_t exception_port __unused,mach_port_t thread,mach_port_t task,exception_type_t type,exception_data_t codes,mach_msg_type_number_t code_count,int * flavor,thread_state_t in_state,mach_msg_type_number_t in_state_count,thread_state_t out_state,mach_msg_type_number_t * out_state_count)311 catch_mach_exception_raise_state_identity(
312 mach_port_t exception_port __unused,
313 mach_port_t thread,
314 mach_port_t task,
315 exception_type_t type,
316 exception_data_t codes,
317 mach_msg_type_number_t code_count,
318 int *flavor,
319 thread_state_t in_state,
320 mach_msg_type_number_t in_state_count,
321 thread_state_t out_state,
322 mach_msg_type_number_t *out_state_count)
323 {
324 LOG_VERBOSE("Caught a mach exception!\n");
325
326 /* There should only be two code values. */
327 T_QUIET; T_ASSERT_EQ(code_count, 2, "Two code values were provided with the mach exception");
328
329 /**
330 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
331 * when setting the exception port.
332 */
333 mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
334 LOG_VERBOSE("Mach exception type %d, codes[0]: %#llx, codes[1]: %#llx\n",
335 type, codes_64[0], codes_64[1]);
336
337 /* Verify that we're receiving the expected thread state flavor. */
338 T_QUIET; T_ASSERT_EQ(*flavor, EXCEPTION_THREAD_STATE, "The thread state flavor is EXCEPTION_THREAD_STATE");
339 T_QUIET; T_ASSERT_EQ(in_state_count, EXCEPTION_THREAD_STATE_COUNT, "The thread state count is EXCEPTION_THREAD_STATE_COUNT");
340
341 uint64_t exception_pc = get_exception_pc(in_state);
342
343 size_t advance_pc = tls_callbacks.state_callback(
344 task, thread, type, codes_64, exception_pc);
345
346 if (advance_pc == EXC_HELPER_HALT) {
347 /* Exception handler callback says we can't continue. */
348 LOG_VERBOSE("Halting after exception");
349 return KERN_FAILURE;
350 }
351
352 /**
353 * Copy in_state to out_state, then increment the PC by the requested
354 * amount so the thread doesn't cause another exception when it resumes.
355 */
356 *out_state_count = in_state_count; /* size of state object in 32-bit words */
357 memcpy((void*)out_state, (void*)in_state, in_state_count * 4);
358 assert(0 == memcmp(in_state, out_state, in_state_count * 4));
359 if (advance_pc != 0) {
360 advance_exception_pc(advance_pc, out_state);
361 LOG_VERBOSE("Continuing after exception at a new PC");
362 } else {
363 LOG_VERBOSE("Continuing after exception");
364 }
365
366 /* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
367 return KERN_SUCCESS;
368 }
369
370 kern_return_t
catch_mach_exception_raise_backtrace(__unused mach_port_t exception_port,mach_port_t kcdata_object,exception_type_t exception,mach_exception_data_t codes,__unused mach_msg_type_number_t codeCnt)371 catch_mach_exception_raise_backtrace(
372 __unused mach_port_t exception_port,
373 mach_port_t kcdata_object,
374 exception_type_t exception,
375 mach_exception_data_t codes,
376 __unused mach_msg_type_number_t codeCnt)
377 {
378 return tls_callbacks.backtrace_callback(kcdata_object, exception, codes);
379 }
380
381 mach_port_t
create_exception_port(exception_mask_t exception_mask)382 create_exception_port(exception_mask_t exception_mask)
383 {
384 return create_exception_port_behavior64(exception_mask, EXCEPTION_STATE_IDENTITY);
385 }
386
387 void
set_thread_exception_port(mach_port_t exc_port,exception_mask_t exception_mask)388 set_thread_exception_port(mach_port_t exc_port, exception_mask_t exception_mask)
389 {
390 set_thread_exception_port_behavior64(exc_port, exception_mask, EXCEPTION_STATE_IDENTITY);
391 }
392
393 void
set_thread_exception_port_behavior64(exception_port_t exc_port,exception_mask_t exception_mask,exception_behavior_t behavior)394 set_thread_exception_port_behavior64(exception_port_t exc_port, exception_mask_t exception_mask, exception_behavior_t behavior)
395 {
396 mach_port_t thread = mach_thread_self();
397 kern_return_t kr;
398
399 if (((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_STATE_IDENTITY &&
400 ((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_IDENTITY_PROTECTED) {
401 T_FAIL("Passed behavior (%d) is not supported by exc_helpers.", behavior);
402 }
403
404 behavior |= MACH_EXCEPTION_CODES;
405
406 /* Tell the kernel what port to send exceptions to. */
407 kr = thread_set_exception_ports(
408 thread,
409 exception_mask,
410 exc_port,
411 (exception_behavior_t)((unsigned int)behavior),
412 EXCEPTION_THREAD_STATE);
413 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Set the exception port to my custom handler");
414 }
415
416 mach_port_t
create_exception_port_behavior64(exception_mask_t exception_mask,exception_behavior_t behavior)417 create_exception_port_behavior64(exception_mask_t exception_mask, exception_behavior_t behavior)
418 {
419 mach_port_t exc_port = MACH_PORT_NULL;
420 mach_port_t task = mach_task_self();
421 kern_return_t kr = KERN_SUCCESS;
422
423 /* Create the mach port the exception messages will be sent to. */
424 kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &exc_port);
425 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Allocated mach exception port");
426
427 /**
428 * Insert a send right into the exception port that the kernel will use to
429 * send the exception thread the exception messages.
430 */
431 kr = mach_port_insert_right(task, exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND);
432 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Inserted a SEND right into the exception port");
433
434 set_thread_exception_port_behavior64(exc_port, exception_mask, behavior);
435 return exc_port;
436 }
437
438 struct thread_params {
439 mach_port_t exc_port;
440 bool run_once;
441
442 struct exc_handler_callbacks callbacks;
443 };
444
445 /**
446 * Thread to handle the mach exception.
447 *
448 * @param arg The exception port to wait for a message on.
449 */
450 static void *
exc_server_thread(void * arg)451 exc_server_thread(void *arg)
452 {
453 struct thread_params *params = arg;
454 mach_port_t exc_port = params->exc_port;
455 bool run_once = params->run_once;
456
457 /*
458 * Save callbacks to thread-local storage so the
459 * catch_mach_exception_raise_* functions can get them.
460 */
461 tls_callbacks = params->callbacks;
462
463 free(params);
464 params = NULL;
465
466 /**
467 * mach_msg_server_once is a helper function provided by libsyscall that
468 * handles creating mach messages, blocks waiting for a message on the
469 * exception port, calls mach_exc_server() to handle the exception, and
470 * sends a reply based on the return value of mach_exc_server().
471 */
472 #define MACH_MSG_REPLY_SIZE 4096
473 kern_return_t kr;
474 if (run_once) {
475 kr = mach_msg_server_once(mach_exc_server, MACH_MSG_REPLY_SIZE, exc_port, 0);
476 } else {
477 kr = mach_msg_server(mach_exc_server, MACH_MSG_REPLY_SIZE, exc_port, 0);
478 }
479 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Received mach exception message");
480
481 pthread_exit((void*)0);
482 __builtin_unreachable();
483 }
484
485 static void
_run_exception_handler(mach_port_t exc_port,void * preferred_callback,void * callback,bool run_once,exception_behavior_t behavior)486 _run_exception_handler(mach_port_t exc_port, void *preferred_callback, void *callback, bool run_once, exception_behavior_t behavior)
487 {
488 /* Set parameters for the exception server's thread. */
489 struct thread_params *params = calloc(1, sizeof(*params));
490 params->exc_port = exc_port;
491 params->run_once = run_once;
492
493 if (behavior & MACH_EXCEPTION_BACKTRACE_PREFERRED) {
494 T_QUIET; T_ASSERT_NE(NULL, preferred_callback, "Require a preferred callback");
495 params->callbacks.backtrace_callback = (exc_handler_backtrace_callback_t)preferred_callback;
496 }
497
498 behavior &= ~MACH_EXCEPTION_MASK;
499
500 switch (behavior) {
501 case EXCEPTION_STATE_IDENTITY:
502 params->callbacks.state_callback = (exc_handler_callback_t)callback;
503 break;
504 case EXCEPTION_STATE_IDENTITY_PROTECTED:
505 params->callbacks.state_protected_callback = (exc_handler_state_protected_callback_t)callback;
506 break;
507 case EXCEPTION_IDENTITY_PROTECTED:
508 params->callbacks.protected_callback = (exc_handler_protected_callback_t)callback;
509 break;
510 default:
511 T_FAIL("Unsupported behavior");
512 break;
513 }
514
515 /* Spawn the exception server's thread. */
516 pthread_t exc_thread;
517 int err = pthread_create(&exc_thread, (pthread_attr_t*)0, exc_server_thread, params);
518 T_QUIET; T_ASSERT_POSIX_ZERO(err, "Spawned exception server thread");
519
520 /* No need to wait for the exception server to be joined when it exits. */
521 pthread_detach(exc_thread);
522 }
523
524 void
run_exception_handler(mach_port_t exc_port,exc_handler_callback_t callback)525 run_exception_handler(mach_port_t exc_port, exc_handler_callback_t callback)
526 {
527 run_exception_handler_behavior64(exc_port, NULL, (void *)callback, EXCEPTION_STATE_IDENTITY, true);
528 }
529
530 void
run_exception_handler_behavior64(mach_port_t exc_port,void * preferred_callback,void * callback,exception_behavior_t behavior,bool run_once)531 run_exception_handler_behavior64(mach_port_t exc_port, void *preferred_callback,
532 void *callback, exception_behavior_t behavior, bool run_once)
533 {
534 if (((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_STATE_IDENTITY &&
535 ((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_IDENTITY_PROTECTED &&
536 ((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_STATE_IDENTITY_PROTECTED) {
537 T_FAIL("Passed behavior (%d) is not supported by exc_helpers.", behavior);
538 }
539
540 _run_exception_handler(exc_port, (void *)preferred_callback, (void *)callback, run_once, behavior);
541 }
542
543 void
repeat_exception_handler(mach_port_t exc_port,exc_handler_callback_t callback)544 repeat_exception_handler(mach_port_t exc_port, exc_handler_callback_t callback)
545 {
546 _run_exception_handler(exc_port, NULL, (void *)callback, false, EXCEPTION_STATE_IDENTITY);
547 }
548