xref: /xnu-11215.41.3/tests/ipc/kernel_signed_pac_thread_state.c (revision 33de042d024d46de5ff4e89f2471de6608e37fa4)
1 /*
2  * Copyright (c) 2021 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 <darwintest.h>
30 #include <ptrauth.h>
31 #include <stdbool.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <mach/mach.h>
35 #include <mach/exception.h>
36 #include <mach/thread_status.h>
37 #include <sys/types.h>
38 #include <sys/sysctl.h>
39 #include <sys/code_signing.h>
40 #include <TargetConditionals.h>
41 #include <mach/semaphore.h>
42 
43 #if __arm64__
44 #define EXCEPTION_THREAD_STATE          ARM_THREAD_STATE64
45 #define EXCEPTION_THREAD_STATE_COUNT    ARM_THREAD_STATE64_COUNT
46 #elif __arm__
47 #define EXCEPTION_THREAD_STATE          ARM_THREAD_STATE
48 #define EXCEPTION_THREAD_STATE_COUNT    ARM_THREAD_STATE_COUNT
49 #elif __x86_64__
50 #define EXCEPTION_THREAD_STATE          x86_THREAD_STATE
51 #define EXCEPTION_THREAD_STATE_COUNT    x86_THREAD_STATE_COUNT
52 #else
53 #error Unsupported architecture
54 #endif
55 
56 T_GLOBAL_META(
57 	T_META_NAMESPACE("xnu.ipc"),
58 	T_META_RADAR_COMPONENT_NAME("xnu"),
59 	T_META_RADAR_COMPONENT_VERSION("IPC"),
60 	T_META_RUN_CONCURRENTLY(true),
61 	T_META_TAG_VM_PREFERRED);
62 
63 /**
64  * mach_exc_server() is a MIG-generated function that verifies the message
65  * that was received is indeed a mach exception and then calls
66  * catch_mach_exception_raise_state() to handle the exception.
67  */
68 extern boolean_t mach_exc_server(mach_msg_header_t *, mach_msg_header_t *);
69 
70 extern kern_return_t
71 catch_mach_exception_raise(
72 	mach_port_t exception_port,
73 	mach_port_t thread,
74 	mach_port_t task,
75 	exception_type_t type,
76 	exception_data_t codes,
77 	mach_msg_type_number_t code_count);
78 
79 extern kern_return_t
80 catch_mach_exception_raise_state(
81 	mach_port_t exception_port,
82 	exception_type_t type,
83 	exception_data_t codes,
84 	mach_msg_type_number_t code_count,
85 	int *flavor,
86 	thread_state_t in_state,
87 	mach_msg_type_number_t in_state_count,
88 	thread_state_t out_state,
89 	mach_msg_type_number_t *out_state_count);
90 
91 extern kern_return_t
92 catch_mach_exception_raise_state_identity(
93 	mach_port_t exception_port,
94 	mach_port_t thread,
95 	mach_port_t task,
96 	exception_type_t type,
97 	exception_data_t codes,
98 	mach_msg_type_number_t code_count,
99 	int *flavor,
100 	thread_state_t in_state,
101 	mach_msg_type_number_t in_state_count,
102 	thread_state_t out_state,
103 	mach_msg_type_number_t *out_state_count);
104 
105 extern kern_return_t
106 catch_mach_exception_raise_identity_protected(
107 	__unused mach_port_t      exception_port,
108 	uint64_t                  thread_id,
109 	mach_port_t               task_id_token,
110 	exception_type_t          exception,
111 	mach_exception_data_t     codes,
112 	mach_msg_type_number_t    codeCnt);
113 
114 /**
115  * This has to be defined for linking purposes, but it's unused.
116  */
117 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)118 catch_mach_exception_raise(
119 	mach_port_t exception_port,
120 	mach_port_t thread,
121 	mach_port_t task,
122 	exception_type_t type,
123 	exception_data_t codes,
124 	mach_msg_type_number_t code_count)
125 {
126 #pragma unused(exception_port, thread, task, type, codes, code_count)
127 	T_FAIL("Triggered catch_mach_exception_raise() which shouldn't happen...");
128 	__builtin_unreachable();
129 }
130 
131 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)132 catch_mach_exception_raise_identity_protected(
133 	__unused mach_port_t      exception_port,
134 	uint64_t                  thread_id,
135 	mach_port_t               task_id_token,
136 	exception_type_t          exception,
137 	mach_exception_data_t     codes,
138 	mach_msg_type_number_t    codeCnt)
139 {
140 #pragma unused(exception_port, thread_id, task_id_token, exception, codes, codeCnt)
141 	T_FAIL("Triggered catch_mach_exception_raise_identity_protected() which shouldn't happen...");
142 	__builtin_unreachable();
143 }
144 
145 /**
146  * This has to be defined for linking purposes, but it's unused.
147  */
148 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)149 catch_mach_exception_raise_state(
150 	mach_port_t exception_port,
151 	exception_type_t type,
152 	exception_data_t codes,
153 	mach_msg_type_number_t code_count,
154 	int *flavor,
155 	thread_state_t in_state,
156 	mach_msg_type_number_t in_state_count,
157 	thread_state_t out_state,
158 	mach_msg_type_number_t *out_state_count)
159 {
160 #pragma unused(exception_port, type, codes, code_count, flavor, in_state, in_state_count, out_state, out_state_count)
161 	T_FAIL("Triggered catch_mach_exception_raise_state() which shouldn't happen...");
162 	__builtin_unreachable();
163 }
164 
165 static int exception_count = 0;
166 static int reset_diversifier = 0;
167 static semaphore_t semaphore;
168 
169 /*
170  * Since the test needs to change the opaque field in
171  * thread struct, the test redefines the thread struct
172  * here. This is just for test purposes, this should not
173  * be done anywhere else.
174  */
175 struct test_user_thread_state_64 {
176 	__uint64_t __x[29];     /* General purpose registers x0-x28 */
177 	void*      __opaque_fp; /* Frame pointer x29 */
178 	void*      __opaque_lr; /* Link register x30 */
179 	void*      __opaque_sp; /* Stack pointer x31 */
180 	void*      __opaque_pc; /* Program counter */
181 	__uint32_t __cpsr;      /* Current program status register */
182 	__uint32_t __opaque_flags; /* Flags describing structure format */
183 };
184 #define __TEST_USER_THREAD_STATE64_FLAGS_KERNEL_SIGNED_PC 0x4
185 
186 /**
187  * Called by mach_exc_server() to handle the exception.
188  * The first time this is called, it will modify the pc
189  * but keep the kernel signed bit. Next time this is called
190  * it will modify the pc and remove the kernel signed bit.
191  */
192 kern_return_t
catch_mach_exception_raise_state_identity(mach_port_t exception_port __unused,mach_port_t thread __unused,mach_port_t task __unused,exception_type_t type __unused,exception_data_t codes __unused,mach_msg_type_number_t code_count __unused,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)193 catch_mach_exception_raise_state_identity(
194 	mach_port_t exception_port __unused,
195 	mach_port_t thread __unused,
196 	mach_port_t task __unused,
197 	exception_type_t type __unused,
198 	exception_data_t codes __unused,
199 	mach_msg_type_number_t code_count __unused,
200 	int *flavor,
201 	thread_state_t in_state,
202 	mach_msg_type_number_t in_state_count,
203 	thread_state_t out_state,
204 	mach_msg_type_number_t *out_state_count)
205 {
206 	T_LOG("Caught a mach exception %d!\n", type);
207 	exception_count++;
208 
209 	/* There should only be two code values. */
210 	T_QUIET; T_ASSERT_EQ(code_count, 2, "Two code values were provided with the mach exception");
211 
212 	/**
213 	 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
214 	 * when setting the exception port.
215 	 */
216 	mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
217 	T_LOG("Mach exception codes[0]: %#llx, codes[1]: %#llx\n", codes_64[0], codes_64[1]);
218 
219 	if (type == EXC_CRASH) {
220 		T_LOG("Received a crash notification, signaling main thread and returning\n");
221 		T_ASSERT_MACH_SUCCESS(semaphore_signal(semaphore), "semaphore_signal");
222 		return KERN_SUCCESS;
223 	}
224 
225 	/* Verify that we're receiving the expected thread state flavor. */
226 	T_QUIET; T_ASSERT_EQ(*flavor, EXCEPTION_THREAD_STATE, "The thread state flavor is EXCEPTION_THREAD_STATE");
227 	T_QUIET; T_ASSERT_EQ(in_state_count, EXCEPTION_THREAD_STATE_COUNT, "The thread state count is EXCEPTION_THREAD_STATE_COUNT");
228 
229 	/**
230 	 * Increment the PC by the 4 so the thread doesn't cause
231 	 * another exception when it resumes.
232 	 */
233 	*out_state_count = in_state_count; /* size of state object in 32-bit words */
234 	memcpy((void*)out_state, (void*)in_state, in_state_count * 4);
235 
236 #if __arm64__
237 	arm_thread_state64_t *state = (arm_thread_state64_t*)(void *)out_state;
238 	struct test_user_thread_state_64 *test_state = (struct test_user_thread_state_64 *)(void *)out_state;
239 	uint32_t userland_diversifier = test_state->__opaque_flags & 0xff000000;
240 
241 	void *pc = (void*)(arm_thread_state64_get_pc(*state) + 4);
242 	/* Have to sign the new PC value when pointer authentication is enabled. */
243 	T_LOG("Userland diversifier for thread state is 0x%x\n", userland_diversifier);
244 	T_QUIET; T_ASSERT_NE(userland_diversifier, 0, "Userland diversifier is non zero");
245 
246 	pc = ptrauth_sign_unauthenticated(pc, ptrauth_key_function_pointer, 0);
247 	arm_thread_state64_set_pc_fptr(*state, pc);
248 
249 	/* Use the set and get lr, fp and sp function to make sure it compiles */
250 	arm_thread_state64_set_lr_fptr(*state, arm_thread_state64_get_lr_fptr(*state));
251 	arm_thread_state64_set_sp(*state, arm_thread_state64_get_sp(*state));
252 	arm_thread_state64_set_fp(*state, arm_thread_state64_get_fp(*state));
253 #endif
254 
255 	if (reset_diversifier == 0) {
256 		if (exception_count == 1) {
257 #if __arm64__
258 			/* Set the kernel signed bit, so kernel ignores the new PC */
259 			test_state->__opaque_flags |= __TEST_USER_THREAD_STATE64_FLAGS_KERNEL_SIGNED_PC;
260 			T_LOG("Set the kernel signed flag on the thread state");
261 #else
262 			T_LOG("Not on arm64, Not doing anything");
263 #endif
264 		} else if (exception_count == 2) {
265 			T_LOG("Not clearing the kernel signed bit, this should be the last exception");
266 		} else {
267 			T_FAIL("Received more than 2 exceptions, failing the test");
268 		}
269 	} else {
270 		if (exception_count == 1) {
271 #if __arm64__
272 			/* Set the user diversifier to zero and resign the pc */
273 			test_state->__opaque_flags &= 0x00ffffff;
274 			arm_thread_state64_set_pc_fptr(*state, pc);
275 			T_LOG("Set the diversifier to zero and signed the pc, this should crash on return");
276 #else
277 			T_LOG("Not on arm64, Not doing anything");
278 #endif
279 		} else {
280 			T_FAIL("Received more than 2 exceptions, failing the test");
281 		}
282 	}
283 
284 	/* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
285 	return KERN_SUCCESS;
286 }
287 
288 static mach_port_t
create_exception_port_behavior64(exception_mask_t exception_mask,exception_behavior_t behavior)289 create_exception_port_behavior64(exception_mask_t exception_mask, exception_behavior_t behavior)
290 {
291 	mach_port_t exc_port = MACH_PORT_NULL;
292 	mach_port_t task = mach_task_self();
293 	kern_return_t kr = KERN_SUCCESS;
294 
295 	if (behavior != EXCEPTION_STATE_IDENTITY && behavior != EXCEPTION_IDENTITY_PROTECTED) {
296 		T_FAIL("Currently only EXCEPTION_STATE_IDENTITY and EXCEPTION_IDENTITY_PROTECTED are implemented");
297 	}
298 
299 	/* Create the mach port the exception messages will be sent to. */
300 	kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &exc_port);
301 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Allocated mach exception port");
302 
303 	/**
304 	 * Insert a send right into the exception port that the kernel will use to
305 	 * send the exception thread the exception messages.
306 	 */
307 	kr = mach_port_insert_right(task, exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND);
308 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Inserted a SEND right into the exception port");
309 
310 	/* Tell the kernel what port to send exceptions to. */
311 	kr = task_set_exception_ports(
312 		task,
313 		exception_mask,
314 		exc_port,
315 		(exception_behavior_t)(behavior | (exception_behavior_t)MACH_EXCEPTION_CODES),
316 		EXCEPTION_THREAD_STATE);
317 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Set the exception port to my custom handler");
318 
319 	return exc_port;
320 }
321 
322 static mach_port_t __unused
create_exception_port(exception_mask_t exception_mask)323 create_exception_port(exception_mask_t exception_mask)
324 {
325 	return create_exception_port_behavior64(exception_mask, EXCEPTION_STATE_IDENTITY);
326 }
327 
328 /**
329  * Thread to handle the mach exception.
330  *
331  * @param arg The exception port to wait for a message on.
332  */
333 static void *
exc_server_thread(void * arg)334 exc_server_thread(void *arg)
335 {
336 	mach_port_t exc_port = (mach_port_t)arg;
337 	kern_return_t kr;
338 
339 	/**
340 	 * mach_msg_server_once is a helper function provided by libsyscall that
341 	 * handles creating mach messages, blocks waiting for a message on the
342 	 * exception port, calls mach_exc_server() to handle the exception, and
343 	 * sends a reply based on the return value of mach_exc_server().
344 	 */
345 #define MACH_MSG_REPLY_SIZE 4096
346 	kr = mach_msg_server(mach_exc_server, MACH_MSG_REPLY_SIZE, exc_port, 0);
347 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Received mach exception message");
348 
349 	pthread_exit((void*)0);
350 	__builtin_unreachable();
351 }
352 
353 static void __unused
run_exception_handler(mach_port_t exc_port)354 run_exception_handler(mach_port_t exc_port)
355 {
356 	pthread_t exc_thread;
357 
358 	/* Spawn the exception server's thread. */
359 	int err = pthread_create(&exc_thread, (pthread_attr_t*)0, exc_server_thread, (void *)(unsigned long long)exc_port);
360 	T_QUIET; T_ASSERT_POSIX_ZERO(err, "Spawned exception server thread");
361 
362 	/* No need to wait for the exception server to be joined when it exits. */
363 	pthread_detach(exc_thread);
364 }
365 
366 T_DECL(kernel_signed_pac_thread_state, "Test that kernel signed thread state given to exception ignores the pc")
367 {
368 #if !__arm64e__
369 	T_SKIP("Running on non-arm64e target, skipping...");
370 #else
371 	mach_port_t exc_port = create_exception_port(EXC_MASK_BAD_ACCESS);
372 
373 	int expected_exception = 2;
374 
375 	run_exception_handler(exc_port);
376 	*(void *volatile*)0 = 0;
377 
378 	if (exception_count != expected_exception) {
379 		T_FAIL("Expected %d exceptions, received %d", expected_exception, exception_count);
380 	} else {
381 		T_LOG("TEST PASSED");
382 	}
383 	T_END;
384 #endif
385 }
386 
387 T_DECL(user_signed_pac_thread_state, "Test that user signed thread state given to exception works with correct diversifier")
388 {
389 #if !__arm64e__
390 	T_SKIP("Running on non-arm64e target, skipping...");
391 #else
392 	mach_port_t exc_port = create_exception_port(EXC_MASK_BAD_ACCESS | EXC_MASK_CRASH);
393 	T_ASSERT_MACH_SUCCESS(semaphore_create(mach_task_self(), &semaphore,
394 	    SYNC_POLICY_FIFO, 0), "semaphore_create");
395 
396 	exception_count = 0;
397 	int expected_exception = 2;
398 
399 	run_exception_handler(exc_port);
400 
401 	/* Set the reset diversifier variable */
402 	reset_diversifier = 1;
403 	pid_t child_pid = fork();
404 
405 	if (child_pid == 0) {
406 		*(void *volatile*)0 = 0;
407 		T_FAIL("Child should have been terminated, but it did not");
408 	}
409 
410 	T_ASSERT_MACH_SUCCESS(semaphore_wait(semaphore), "semaphore_wait");
411 
412 	if (exception_count != expected_exception) {
413 		T_FAIL("Expected %d exceptions, received %d", expected_exception, exception_count);
414 	} else {
415 		T_LOG("TEST PASSED");
416 	}
417 	T_END;
418 #endif
419 }
420