]> git.saurik.com Git - apple/xnu.git/blame - tests/exc_helpers.c
xnu-7195.50.7.100.1.tar.gz
[apple/xnu.git] / tests / exc_helpers.c
CommitLineData
f427ee49
A
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
34#if __arm64__
35#define EXCEPTION_THREAD_STATE ARM_THREAD_STATE64
36#define EXCEPTION_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT
37#elif __arm__
38#define EXCEPTION_THREAD_STATE ARM_THREAD_STATE
39#define EXCEPTION_THREAD_STATE_COUNT ARM_THREAD_STATE_COUNT
40#elif __x86_64__
41#define EXCEPTION_THREAD_STATE x86_THREAD_STATE
42#define EXCEPTION_THREAD_STATE_COUNT x86_THREAD_STATE_COUNT
43#else
44#error Unsupported architecture
45#endif
46
47/**
48 * mach_exc_server() is a MIG-generated function that verifies the message
49 * that was received is indeed a mach exception and then calls
50 * catch_mach_exception_raise_state() to handle the exception.
51 */
52extern boolean_t mach_exc_server(mach_msg_header_t *, mach_msg_header_t *);
53
54extern kern_return_t
55catch_mach_exception_raise(
56 mach_port_t exception_port,
57 mach_port_t thread,
58 mach_port_t task,
59 exception_type_t type,
60 exception_data_t codes,
61 mach_msg_type_number_t code_count);
62
63extern kern_return_t
64catch_mach_exception_raise_state(
65 mach_port_t exception_port,
66 exception_type_t type,
67 exception_data_t codes,
68 mach_msg_type_number_t code_count,
69 int *flavor,
70 thread_state_t in_state,
71 mach_msg_type_number_t in_state_count,
72 thread_state_t out_state,
73 mach_msg_type_number_t *out_state_count);
74
75extern kern_return_t
76catch_mach_exception_raise_state_identity(
77 mach_port_t exception_port,
78 mach_port_t thread,
79 mach_port_t task,
80 exception_type_t type,
81 exception_data_t codes,
82 mach_msg_type_number_t code_count,
83 int *flavor,
84 thread_state_t in_state,
85 mach_msg_type_number_t in_state_count,
86 thread_state_t out_state,
87 mach_msg_type_number_t *out_state_count);
88
89static exc_handler_callback_t exc_handler_callback;
90
91/**
92 * This has to be defined for linking purposes, but it's unused.
93 */
94kern_return_t
95catch_mach_exception_raise(
96 mach_port_t exception_port,
97 mach_port_t thread,
98 mach_port_t task,
99 exception_type_t type,
100 exception_data_t codes,
101 mach_msg_type_number_t code_count)
102{
103#pragma unused(exception_port, thread, task, type, codes, code_count)
104 T_FAIL("Triggered catch_mach_exception_raise() which shouldn't happen...");
105 __builtin_unreachable();
106}
107
108/**
109 * Called by mach_exc_server() to handle the exception. This will call the
110 * test's exception-handler callback and will then modify
111 * the thread state to move to the next instruction.
112 */
113kern_return_t
114catch_mach_exception_raise_state(
115 mach_port_t exception_port __unused,
116 exception_type_t type,
117 exception_data_t codes,
118 mach_msg_type_number_t code_count,
119 int *flavor,
120 thread_state_t in_state,
121 mach_msg_type_number_t in_state_count,
122 thread_state_t out_state,
123 mach_msg_type_number_t *out_state_count)
124{
125 T_LOG("Caught a mach exception!\n");
126
127 /* There should only be two code values. */
128 T_ASSERT_EQ(code_count, 2, "Two code values were provided with the mach exception");
129
130 /**
131 * The code values should be 64-bit since MACH_EXCEPTION_CODES was specified
132 * when setting the exception port.
133 */
134 mach_exception_data_t codes_64 = (mach_exception_data_t)(void *)codes;
135 T_LOG("Mach exception codes[0]: %#llx, codes[1]: %#llx\n", codes_64[0], codes_64[1]);
136
137 /* Verify that we're receiving the expected thread state flavor. */
138 T_ASSERT_EQ(*flavor, EXCEPTION_THREAD_STATE, "The thread state flavor is EXCEPTION_THREAD_STATE");
139 T_ASSERT_EQ(in_state_count, EXCEPTION_THREAD_STATE_COUNT, "The thread state count is EXCEPTION_THREAD_STATE_COUNT");
140
141 size_t advance_pc = exc_handler_callback(type, codes_64);
142
143 /**
144 * Increment the PC by the requested amount so the thread doesn't cause
145 * another exception when it resumes.
146 */
147 *out_state_count = in_state_count; /* size of state object in 32-bit words */
148 memcpy((void*)out_state, (void*)in_state, in_state_count * 4);
149
150#if __arm64__
151 arm_thread_state64_t *state = (arm_thread_state64_t*)(void *)out_state;
152
153 void *pc = (void*)(arm_thread_state64_get_pc(*state) + advance_pc);
154 /* Have to sign the new PC value when pointer authentication is enabled. */
155 pc = ptrauth_sign_unauthenticated(pc, ptrauth_key_function_pointer, 0);
156 arm_thread_state64_set_pc_fptr(*state, pc);
157#else
158 T_FAIL("catch_mach_exception_raise_state() not fully implemented on this architecture");
159 __builtin_unreachable();
160#endif
161
162 /* Return KERN_SUCCESS to tell the kernel to keep running the victim thread. */
163 return KERN_SUCCESS;
164}
165
166/**
167 * This has to be defined for linking purposes, but it's unused.
168 */
169kern_return_t
170catch_mach_exception_raise_state_identity(
171 mach_port_t exception_port,
172 mach_port_t thread,
173 mach_port_t task,
174 exception_type_t type,
175 exception_data_t codes,
176 mach_msg_type_number_t code_count,
177 int *flavor,
178 thread_state_t in_state,
179 mach_msg_type_number_t in_state_count,
180 thread_state_t out_state,
181 mach_msg_type_number_t *out_state_count)
182{
183#pragma unused(exception_port, thread, task, type, codes, code_count, flavor, in_state, in_state_count, out_state, out_state_count)
184 T_FAIL("Triggered catch_mach_exception_raise_state_identity() which shouldn't happen...");
185 __builtin_unreachable();
186}
187
188mach_port_t
189create_exception_port(exception_mask_t exception_mask)
190{
191 mach_port_t exc_port = MACH_PORT_NULL;
192 mach_port_t task = mach_task_self();
193 mach_port_t thread = mach_thread_self();
194 kern_return_t kr = KERN_SUCCESS;
195
196 /* Create the mach port the exception messages will be sent to. */
197 kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &exc_port);
198 T_ASSERT_MACH_SUCCESS(kr, "Allocated mach exception port");
199
200 /**
201 * Insert a send right into the exception port that the kernel will use to
202 * send the exception thread the exception messages.
203 */
204 kr = mach_port_insert_right(task, exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND);
205 T_ASSERT_MACH_SUCCESS(kr, "Inserted a SEND right into the exception port");
206
207 /* Tell the kernel what port to send exceptions to. */
208 kr = thread_set_exception_ports(
209 thread,
210 exception_mask,
211 exc_port,
212 (exception_behavior_t)(EXCEPTION_STATE | MACH_EXCEPTION_CODES),
213 EXCEPTION_THREAD_STATE);
214 T_ASSERT_MACH_SUCCESS(kr, "Set the exception port to my custom handler");
215
216 return exc_port;
217}
218
219/**
220 * Thread to handle the mach exception.
221 *
222 * @param arg The exception port to wait for a message on.
223 */
224static void *
225exc_server_thread(void *arg)
226{
227 mach_port_t exc_port = (mach_port_t)arg;
228
229 /**
230 * mach_msg_server_once is a helper function provided by libsyscall that
231 * handles creating mach messages, blocks waiting for a message on the
232 * exception port, calls mach_exc_server() to handle the exception, and
233 * sends a reply based on the return value of mach_exc_server().
234 */
235#define MACH_MSG_REPLY_SIZE 4096
236 kern_return_t kr = mach_msg_server_once(mach_exc_server, MACH_MSG_REPLY_SIZE, exc_port, 0);
237 T_ASSERT_MACH_SUCCESS(kr, "Received mach exception message");
238
239 pthread_exit((void*)0);
240 __builtin_unreachable();
241}
242
243void
244run_exception_handler(mach_port_t exc_port, exc_handler_callback_t callback)
245{
246 exc_handler_callback = callback;
247
248 pthread_t exc_thread;
249
250 /* Spawn the exception server's thread. */
251 int err = pthread_create(&exc_thread, (pthread_attr_t*)0, exc_server_thread, (void*)(uintptr_t)exc_port);
252 T_ASSERT_POSIX_ZERO(err, "Spawned exception server thread");
253
254 /* No need to wait for the exception server to be joined when it exits. */
255 pthread_detach(exc_thread);
256}