xref: /xnu-8792.41.9/tests/exc_helpers.c (revision 5c2921b07a2480ab43ec66f5b9e41cb872bc554f)
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_STATE
41 #define EXCEPTION_THREAD_STATE_COUNT    x86_THREAD_STATE_COUNT
42 #else
43 #error Unsupported architecture
44 #endif
45 
46 #define EXCEPTION_IDENTITY_PROTECTED 4
47 
48 /**
49  * mach_exc_server() is a MIG-generated function that verifies the message
50  * that was received is indeed a mach exception and then calls
51  * catch_mach_exception_raise_state() to handle the exception.
52  */
53 extern boolean_t mach_exc_server(mach_msg_header_t *, mach_msg_header_t *);
54 
55 extern kern_return_t
56 catch_mach_exception_raise(
57 	mach_port_t exception_port,
58 	mach_port_t thread,
59 	mach_port_t task,
60 	exception_type_t type,
61 	exception_data_t codes,
62 	mach_msg_type_number_t code_count);
63 
64 extern kern_return_t
65 catch_mach_exception_raise_identity_protected(
66 	__unused mach_port_t      exception_port,
67 	uint64_t                  thread_id,
68 	mach_port_t               task_id_token,
69 	exception_type_t          exception,
70 	mach_exception_data_t     codes,
71 	mach_msg_type_number_t    codeCnt);
72 
73 extern kern_return_t
74 catch_mach_exception_raise_backtrace(
75 	__unused mach_port_t exception_port,
76 	mach_port_t kcdata_object,
77 	exception_type_t exception,
78 	mach_exception_data_t codes,
79 	__unused mach_msg_type_number_t codeCnt);
80 
81 extern kern_return_t
82 catch_mach_exception_raise_state(
83 	mach_port_t exception_port,
84 	exception_type_t type,
85 	exception_data_t codes,
86 	mach_msg_type_number_t code_count,
87 	int *flavor,
88 	thread_state_t in_state,
89 	mach_msg_type_number_t in_state_count,
90 	thread_state_t out_state,
91 	mach_msg_type_number_t *out_state_count);
92 
93 extern kern_return_t
94 catch_mach_exception_raise_state_identity(
95 	mach_port_t exception_port,
96 	mach_port_t thread,
97 	mach_port_t task,
98 	exception_type_t type,
99 	exception_data_t codes,
100 	mach_msg_type_number_t code_count,
101 	int *flavor,
102 	thread_state_t in_state,
103 	mach_msg_type_number_t in_state_count,
104 	thread_state_t out_state,
105 	mach_msg_type_number_t *out_state_count);
106 
107 static exc_handler_callback_t exc_handler_callback;
108 static exc_handler_protected_callback_t exc_handler_protected_callback;
109 static exc_handler_backtrace_callback_t exc_handler_backtrace_callback;
110 
111 /**
112  * This has to be defined for linking purposes, but it's unused.
113  */
114 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)115 catch_mach_exception_raise(
116 	mach_port_t exception_port,
117 	mach_port_t thread,
118 	mach_port_t task,
119 	exception_type_t type,
120 	exception_data_t codes,
121 	mach_msg_type_number_t code_count)
122 {
123 #pragma unused(exception_port, thread, task, type, codes, code_count)
124 	T_FAIL("Triggered catch_mach_exception_raise() which shouldn't happen...");
125 	__builtin_unreachable();
126 }
127 
128 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)129 catch_mach_exception_raise_identity_protected(
130 	__unused mach_port_t      exception_port,
131 	uint64_t                  thread_id,
132 	mach_port_t               task_id_token,
133 	exception_type_t          exception,
134 	mach_exception_data_t     codes,
135 	mach_msg_type_number_t    codeCnt)
136 {
137 	T_LOG("Caught a mach exception!\n");
138 
139 	/* There should only be two code values. */
140 	T_QUIET; T_ASSERT_EQ(codeCnt, 2, "Two code values were provided with the mach exception");
141 
142 	/**
143 	 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
144 	 * when setting the exception port.
145 	 */
146 	mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
147 	T_LOG("Mach exception codes[0]: %#llx, codes[1]: %#llx\n", codes_64[0], codes_64[1]);
148 
149 	exc_handler_protected_callback(task_id_token, thread_id, exception, codes_64);
150 
151 	T_LOG("Assuming the thread state modification was done in the callback, skipping it");
152 
153 	/* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
154 	return KERN_SUCCESS;
155 }
156 
157 /**
158  * This has to be defined for linking purposes, but it's unused.
159  */
160 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)161 catch_mach_exception_raise_state(
162 	mach_port_t exception_port,
163 	exception_type_t type,
164 	exception_data_t codes,
165 	mach_msg_type_number_t code_count,
166 	int *flavor,
167 	thread_state_t in_state,
168 	mach_msg_type_number_t in_state_count,
169 	thread_state_t out_state,
170 	mach_msg_type_number_t *out_state_count)
171 {
172 #pragma unused(exception_port, type, codes, code_count, flavor, in_state, in_state_count, out_state, out_state_count)
173 	T_FAIL("Triggered catch_mach_exception_raise_state() which shouldn't happen...");
174 	__builtin_unreachable();
175 }
176 
177 /**
178  * Called by mach_exc_server() to handle the exception. This will call the
179  * test's exception-handler callback and will then modify
180  * the thread state to move to the next instruction.
181  */
182 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)183 catch_mach_exception_raise_state_identity(
184 	mach_port_t exception_port __unused,
185 	mach_port_t thread,
186 	mach_port_t task,
187 	exception_type_t type,
188 	exception_data_t codes,
189 	mach_msg_type_number_t code_count,
190 	int *flavor,
191 	thread_state_t in_state,
192 	mach_msg_type_number_t in_state_count,
193 	thread_state_t out_state,
194 	mach_msg_type_number_t *out_state_count)
195 {
196 	T_LOG("Caught a mach exception!\n");
197 
198 	/* There should only be two code values. */
199 	T_QUIET; T_ASSERT_EQ(code_count, 2, "Two code values were provided with the mach exception");
200 
201 	/**
202 	 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
203 	 * when setting the exception port.
204 	 */
205 	mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
206 	T_LOG("Mach exception codes[0]: %#llx, codes[1]: %#llx\n", codes_64[0], codes_64[1]);
207 
208 	/* Verify that we're receiving the expected thread state flavor. */
209 	T_QUIET; T_ASSERT_EQ(*flavor, EXCEPTION_THREAD_STATE, "The thread state flavor is EXCEPTION_THREAD_STATE");
210 	T_QUIET; T_ASSERT_EQ(in_state_count, EXCEPTION_THREAD_STATE_COUNT, "The thread state count is EXCEPTION_THREAD_STATE_COUNT");
211 
212 	size_t advance_pc = exc_handler_callback(task, thread, type, codes_64);
213 
214 	/**
215 	 * Increment the PC by the requested amount so the thread doesn't cause
216 	 * another exception when it resumes.
217 	 */
218 	*out_state_count = in_state_count; /* size of state object in 32-bit words */
219 	memcpy((void*)out_state, (void*)in_state, in_state_count * 4);
220 
221 #if __arm64__
222 	arm_thread_state64_t *state = (arm_thread_state64_t*)(void *)out_state;
223 
224 	void *pc = (void*)(arm_thread_state64_get_pc(*state) + advance_pc);
225 	/* Have to sign the new PC value when pointer authentication is enabled. */
226 	pc = ptrauth_sign_unauthenticated(pc, ptrauth_key_function_pointer, 0);
227 	arm_thread_state64_set_pc_fptr(*state, pc);
228 #else
229 	(void)advance_pc;
230 	T_FAIL("catch_mach_exception_raise_state() not fully implemented on this architecture");
231 	__builtin_unreachable();
232 #endif
233 
234 	/* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
235 	return KERN_SUCCESS;
236 }
237 
238 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)239 catch_mach_exception_raise_backtrace(
240 	__unused mach_port_t exception_port,
241 	mach_port_t kcdata_object,
242 	exception_type_t exception,
243 	mach_exception_data_t codes,
244 	__unused mach_msg_type_number_t codeCnt)
245 {
246 	return exc_handler_backtrace_callback(kcdata_object, exception, codes);
247 }
248 
249 mach_port_t
create_exception_port(exception_mask_t exception_mask)250 create_exception_port(exception_mask_t exception_mask)
251 {
252 	return create_exception_port_behavior64(exception_mask, EXCEPTION_STATE_IDENTITY);
253 }
254 
255 mach_port_t
create_exception_port_behavior64(exception_mask_t exception_mask,exception_behavior_t behavior)256 create_exception_port_behavior64(exception_mask_t exception_mask, exception_behavior_t behavior)
257 {
258 	mach_port_t exc_port = MACH_PORT_NULL;
259 	mach_port_t task = mach_task_self();
260 	mach_port_t thread = mach_thread_self();
261 	kern_return_t kr = KERN_SUCCESS;
262 
263 	if (((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_STATE_IDENTITY &&
264 	    ((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_IDENTITY_PROTECTED) {
265 		T_FAIL("Passed behavior (%d) is not supported by exc_helpers.", behavior);
266 	}
267 
268 	behavior |= MACH_EXCEPTION_CODES;
269 
270 	/* Create the mach port the exception messages will be sent to. */
271 	kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &exc_port);
272 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Allocated mach exception port");
273 
274 	/**
275 	 * Insert a send right into the exception port that the kernel will use to
276 	 * send the exception thread the exception messages.
277 	 */
278 	kr = mach_port_insert_right(task, exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND);
279 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Inserted a SEND right into the exception port");
280 
281 	/* Tell the kernel what port to send exceptions to. */
282 	kr = thread_set_exception_ports(
283 		thread,
284 		exception_mask,
285 		exc_port,
286 		(exception_behavior_t)((unsigned int)behavior),
287 		EXCEPTION_THREAD_STATE);
288 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Set the exception port to my custom handler");
289 
290 	return exc_port;
291 }
292 
293 struct thread_params {
294 	mach_port_t exc_port;
295 	bool run_once;
296 };
297 
298 /**
299  * Thread to handle the mach exception.
300  *
301  * @param arg The exception port to wait for a message on.
302  */
303 static void *
exc_server_thread(void * arg)304 exc_server_thread(void *arg)
305 {
306 	struct thread_params *params = arg;
307 	mach_port_t exc_port = params->exc_port;
308 	bool run_once = params->run_once;
309 	free(params);
310 
311 	/**
312 	 * mach_msg_server_once is a helper function provided by libsyscall that
313 	 * handles creating mach messages, blocks waiting for a message on the
314 	 * exception port, calls mach_exc_server() to handle the exception, and
315 	 * sends a reply based on the return value of mach_exc_server().
316 	 */
317 #define MACH_MSG_REPLY_SIZE 4096
318 	kern_return_t kr;
319 	if (run_once) {
320 		kr = mach_msg_server_once(mach_exc_server, MACH_MSG_REPLY_SIZE, exc_port, 0);
321 	} else {
322 		kr = mach_msg_server(mach_exc_server, MACH_MSG_REPLY_SIZE, exc_port, 0);
323 	}
324 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Received mach exception message");
325 
326 	pthread_exit((void*)0);
327 	__builtin_unreachable();
328 }
329 
330 static void
_run_exception_handler(mach_port_t exc_port,void * preferred_callback,void * callback,bool run_once,exception_behavior_t behavior)331 _run_exception_handler(mach_port_t exc_port, void *preferred_callback, void *callback, bool run_once, exception_behavior_t behavior)
332 {
333 	if (behavior & MACH_EXCEPTION_BACKTRACE_PREFERRED) {
334 		T_QUIET; T_ASSERT_NE(NULL, preferred_callback, "Require a preferred callback");
335 		exc_handler_backtrace_callback = (exc_handler_backtrace_callback_t)preferred_callback;
336 	}
337 
338 	behavior &= ~MACH_EXCEPTION_MASK;
339 
340 	switch (behavior) {
341 	case EXCEPTION_STATE_IDENTITY:
342 		exc_handler_callback = (exc_handler_callback_t)callback;
343 		break;
344 	case EXCEPTION_IDENTITY_PROTECTED:
345 		exc_handler_protected_callback = (exc_handler_protected_callback_t)callback;
346 		break;
347 	default:
348 		T_FAIL("Unsupported behavior");
349 		break;
350 	}
351 
352 	pthread_t exc_thread;
353 
354 	/* Spawn the exception server's thread. */
355 	struct thread_params *params = malloc(sizeof(*params));
356 	params->exc_port = exc_port;
357 	params->run_once = run_once;
358 	int err = pthread_create(&exc_thread, (pthread_attr_t*)0, exc_server_thread, params);
359 	T_QUIET; T_ASSERT_POSIX_ZERO(err, "Spawned exception server thread");
360 
361 	/* No need to wait for the exception server to be joined when it exits. */
362 	pthread_detach(exc_thread);
363 }
364 
365 void
run_exception_handler(mach_port_t exc_port,exc_handler_callback_t callback)366 run_exception_handler(mach_port_t exc_port, exc_handler_callback_t callback)
367 {
368 	run_exception_handler_behavior64(exc_port, NULL, (void *)callback, EXCEPTION_STATE_IDENTITY);
369 }
370 
371 void
run_exception_handler_behavior64(mach_port_t exc_port,void * preferred_callback,void * callback,exception_behavior_t behavior)372 run_exception_handler_behavior64(mach_port_t exc_port, void *preferred_callback,
373     void *callback, exception_behavior_t behavior)
374 {
375 	if (((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_STATE_IDENTITY &&
376 	    ((unsigned int)behavior & ~MACH_EXCEPTION_MASK) != EXCEPTION_IDENTITY_PROTECTED) {
377 		T_FAIL("Passed behavior (%d) is not supported by exc_helpers.", behavior);
378 	}
379 
380 	_run_exception_handler(exc_port, (void *)preferred_callback, (void *)callback, true, behavior);
381 }
382 
383 void
repeat_exception_handler(mach_port_t exc_port,exc_handler_callback_t callback)384 repeat_exception_handler(mach_port_t exc_port, exc_handler_callback_t callback)
385 {
386 	_run_exception_handler(exc_port, NULL, (void *)callback, false, EXCEPTION_STATE_IDENTITY);
387 }
388