1 | /* |
2 | * Copyright (c) 2023 Apple 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 | #if CONFIG_EXCLAVES |
30 | |
31 | #include <kern/assert.h> |
32 | #include <kern/misc_protos.h> |
33 | #include <kern/thread.h> |
34 | |
35 | #include <mach/exclaves_l4.h> |
36 | |
37 | #include <uuid/uuid.h> |
38 | |
39 | #include <xnuproxy/messages.h> |
40 | #include <xnuproxy/panic.h> |
41 | |
42 | #include "exclaves_debug.h" |
43 | #include "exclaves_panic.h" |
44 | |
45 | /* Use the new version of xnuproxy_msg_t. */ |
46 | #define xnuproxy_msg_t xnuproxy_msg_new_t |
47 | |
48 | #define EXCLAVES_PANIC_FOUR_CC_FORMAT "%c%c%c%c" |
49 | #define EXCLAVES_PANIC_FOUR_CC_CHARS(c) \ |
50 | (((c) >> 24) & 0xFF), \ |
51 | (((c) >> 16) & 0xFF), \ |
52 | (((c) >> 8) & 0xFF), \ |
53 | (((c) >> 0) & 0xFF) |
54 | #define EXCLAVE_PANIC_MESSAGE_MARKER "[Exclaves]" |
55 | |
56 | // Adding 64 bytes here to accommodate the PC and LR in the panic string |
57 | #define EXCLAVES_PANIC_STRING_SIZE \ |
58 | (sizeof(EXCLAVE_PANIC_MESSAGE_MARKER) + XNUPROXY_PANIC_MESSAGE_LEN + XNUPROXY_PANIC_NAME_BYTES + 64) |
59 | |
60 | #define P2ROUNDUP(x, align) (-(-(x) & -(align))) |
61 | #define PANIC_BUFFER_PAGE_COUNT (P2ROUNDUP(sizeof (xnuproxy_panic_buffer_t), PAGE_SIZE) / PAGE_SIZE) |
62 | |
63 | static char exclaves_panic_string[EXCLAVES_PANIC_STRING_SIZE]; |
64 | static char *exclaves_panic_buffer_pages[PANIC_BUFFER_PAGE_COUNT]; |
65 | static xnuproxy_panic_buffer_t exclaves_panic_buffer; |
66 | static int exclaves_panic_thread_wait_forever; |
67 | |
68 | static void |
69 | copy_panic_buffer_pages(pmap_paddr_t addr) |
70 | { |
71 | uint64_t *pages = (uint64_t *)phystokv(addr); |
72 | |
73 | for (int i = 0; i < PANIC_BUFFER_PAGE_COUNT; i++) { |
74 | exclaves_panic_buffer_pages[i] = (char *)phystokv(pages[i]); |
75 | } |
76 | |
77 | /* |
78 | * For backwards compat always use the base page even if not listed |
79 | * explicitly. |
80 | */ |
81 | exclaves_panic_buffer_pages[0] = (char *)pages; |
82 | |
83 | return; |
84 | } |
85 | |
86 | static void |
87 | exclaves_xnu_proxy_panic_thread(void *arg __unused, wait_result_t wr __unused) |
88 | { |
89 | kern_return_t kr = KERN_SUCCESS; |
90 | Exclaves_L4_Word_t spawned_scid = 0; |
91 | thread_t thread; |
92 | |
93 | xnuproxy_msg_t msg = { |
94 | .cmd = XNUPROXY_CMD_PANIC_SETUP, |
95 | }; |
96 | |
97 | extern kern_return_t exclaves_xnu_proxy_send(xnuproxy_msg_t *, |
98 | Exclaves_L4_Word_t *); |
99 | kr = exclaves_xnu_proxy_send(&msg, &spawned_scid); |
100 | if (kr != KERN_SUCCESS) { |
101 | exclaves_debug_printf(show_errors, |
102 | "exclaves: panic thread init: xnu proxy send failed." ); |
103 | return; |
104 | } |
105 | |
106 | /* Dont copy if the panic buffer initialisation already happended */ |
107 | if (exclaves_panic_buffer_pages[0] == 0) { |
108 | copy_panic_buffer_pages(msg.cmd_panic_setup.response.physical_address); |
109 | } |
110 | |
111 | thread = current_thread(); |
112 | thread->th_exclaves_scheduling_context_id = spawned_scid; |
113 | |
114 | assert3u(thread->th_exclaves_state & TH_EXCLAVES_STATE_ANY, ==, 0); |
115 | thread->th_exclaves_state |= TH_EXCLAVES_SCHEDULER_CALL; |
116 | |
117 | while (1) { |
118 | extern kern_return_t exclaves_scheduler_resume_scheduling_context(Exclaves_L4_Word_t, |
119 | Exclaves_L4_Word_t *); |
120 | kr = exclaves_scheduler_resume_scheduling_context(spawned_scid, NULL); |
121 | assert3u(kr, ==, KERN_SUCCESS); |
122 | } |
123 | } |
124 | |
125 | static bool |
126 | exclaves_panic_buffer_sync(void) |
127 | { |
128 | char *panic_buffer = (char *)&exclaves_panic_buffer; |
129 | size_t len = sizeof(exclaves_panic_buffer); |
130 | |
131 | /* Just return if the panic buffer initialisation hasn't happened yet. */ |
132 | if (exclaves_panic_buffer_pages[0] == 0) { |
133 | return KERN_NOT_SUPPORTED; |
134 | } |
135 | |
136 | /* |
137 | * Initialize next page to the first page. This is for the backwards |
138 | * compatibility case. |
139 | */ |
140 | char *next_page = exclaves_panic_buffer_pages[0]; |
141 | for (int i = 0; i < PANIC_BUFFER_PAGE_COUNT; i++) { |
142 | size_t nbytes = MIN(len, PAGE_SIZE); |
143 | |
144 | next_page = exclaves_panic_buffer_pages[i] != 0 ? |
145 | exclaves_panic_buffer_pages[i] : next_page + PAGE_SIZE; |
146 | |
147 | (void)memcpy(panic_buffer, next_page, nbytes); |
148 | |
149 | panic_buffer += nbytes; |
150 | len -= nbytes; |
151 | |
152 | if (len == 0) { |
153 | break; |
154 | } |
155 | } |
156 | |
157 | return KERN_SUCCESS; |
158 | } |
159 | |
160 | static void |
161 | exclaves_append_panic_backtrace(void) |
162 | { |
163 | uuid_string_t uuid_string; |
164 | xnuproxy_panic_backtrace_word_t *words; |
165 | |
166 | assert3p(exclaves_panic_buffer_pages[0], !=, NULL); |
167 | |
168 | if ((exclaves_panic_buffer.panicked_thread.backtrace.frames > |
169 | XNUPROXY__PANIC_BACKTRACE_WORDS)) { |
170 | return; |
171 | } |
172 | |
173 | words = exclaves_panic_buffer.backtrace.words; |
174 | paniclog_append_noflush("Exclaves backtrace:\n" ); |
175 | for (size_t i = 0; i < exclaves_panic_buffer.panicked_thread.backtrace.frames; i++) { |
176 | uuid_unparse_upper( |
177 | (const unsigned char *)exclaves_panic_buffer.backtrace.images[words[i].image].uuid, |
178 | uuid_string); |
179 | paniclog_append_noflush("\t\t%s 0x%016zx\n" , uuid_string, |
180 | exclaves_panic_buffer.backtrace.words[i].offset); |
181 | } |
182 | |
183 | paniclog_append_noflush("\n" ); |
184 | return; |
185 | } |
186 | |
187 | static void |
188 | exclaves_append_panic_addl_info(xnuproxy_panicked_thread_t *ex_thread) |
189 | { |
190 | char component_name[XNUPROXY_PANIC_NAME_BYTES] = {0}; |
191 | |
192 | strlcpy(component_name, ex_thread->component.name, sizeof(component_name)); |
193 | |
194 | paniclog_append_noflush( |
195 | "\t\tAddress space ID: 0x%llx\n" |
196 | "\t\tComponent:\n" |
197 | "\t\t\tName: %s\n" |
198 | "\t\t\tID: 0x%llx\n" |
199 | "\t\t\tSelector: 0x%llx\n" |
200 | "\t\tspace.component.endpoint.thread: " EXCLAVES_PANIC_FOUR_CC_FORMAT "." |
201 | EXCLAVES_PANIC_FOUR_CC_FORMAT "." |
202 | EXCLAVES_PANIC_FOUR_CC_FORMAT "." |
203 | EXCLAVES_PANIC_FOUR_CC_FORMAT "\n" |
204 | "\t\tThread Context:\n" |
205 | "\t\t\tAddress: 0x%zx\n" |
206 | "\t\t\tTSS Base: 0x%zx\n" |
207 | "\t\t\tIPC Buffer 0x%zx\n" |
208 | "\t\t\tSCID 0x%zx\n" |
209 | "\t\t\tECID: 0x%zx\n" |
210 | "\t\t\tEPID: 0x%zx\n" |
211 | "\t\t\tStack:\n" |
212 | "\t\t\t\tStart: 0x%zx\n" |
213 | "\t\t\t\tSize: 0x%zx\n" |
214 | "\t\t\t\tCall base: 0x%zx\n" |
215 | "\t\t\tRegisters:\n" |
216 | "\t\t\t\tLR: 0x%zx\n" |
217 | "\t\t\t\tPC: 0x%zx\n" |
218 | "\t\t\t\tSP: 0x%zx\n" |
219 | "\t\t\t\tCPSR: 0x%zx\n" , |
220 | ex_thread->address_space_id, component_name, |
221 | ex_thread->component.numeric_id, ex_thread->component.selector, |
222 | EXCLAVES_PANIC_FOUR_CC_CHARS(ex_thread->four_cc.space), |
223 | EXCLAVES_PANIC_FOUR_CC_CHARS(ex_thread->four_cc.component), |
224 | EXCLAVES_PANIC_FOUR_CC_CHARS(ex_thread->four_cc.endpoint), |
225 | EXCLAVES_PANIC_FOUR_CC_CHARS(ex_thread->four_cc.thread), |
226 | ex_thread->thread.address, ex_thread->thread.tss_base, |
227 | ex_thread->thread.ipc_buffer, ex_thread->thread.scheduling_context_id, |
228 | ex_thread->thread.execution_context_id, ex_thread->thread.endpoint_id, |
229 | ex_thread->thread.stack.start, ex_thread->thread.stack.size, |
230 | ex_thread->thread.stack.call_base, ex_thread->thread.registers.lr, |
231 | ex_thread->thread.registers.pc, ex_thread->thread.registers.sp, |
232 | ex_thread->thread.registers.cpsr); |
233 | } |
234 | |
235 | kern_return_t |
236 | exclaves_panic_get_string(char **string) |
237 | { |
238 | uint32_t status = 0; |
239 | char component_name[XNUPROXY_PANIC_NAME_BYTES] = {0}; |
240 | |
241 | kern_return_t kr = exclaves_panic_buffer_sync(); |
242 | if (kr != KERN_SUCCESS) { |
243 | return kr; |
244 | } |
245 | |
246 | xnuproxy_panicked_thread_t *ex_thread = |
247 | &exclaves_panic_buffer.panicked_thread; |
248 | |
249 | strlcpy(component_name, ex_thread->component.name, sizeof(component_name)); |
250 | |
251 | status = os_atomic_load(&ex_thread->status, seq_cst); |
252 | if (status == XNUPROXY_PANIC_UNSET) { |
253 | return KERN_FAILURE; |
254 | } |
255 | |
256 | snprintf(exclaves_panic_string, sizeof(exclaves_panic_string), |
257 | "%s %s:%s at PC: 0x%zx, LR: 0x%zx" , EXCLAVE_PANIC_MESSAGE_MARKER, |
258 | component_name, exclaves_panic_buffer.message, |
259 | ex_thread->thread.registers.pc, ex_thread->thread.registers.lr); |
260 | exclaves_panic_string[sizeof(exclaves_panic_string) - 1] = '\0'; |
261 | *string = exclaves_panic_string; |
262 | |
263 | return KERN_SUCCESS; |
264 | } |
265 | |
266 | void |
267 | exclaves_panic_append_info(void) |
268 | { |
269 | uint32_t status = 0; |
270 | char *status_str; |
271 | |
272 | kern_return_t kr = exclaves_panic_buffer_sync(); |
273 | if (kr != KERN_SUCCESS) { |
274 | return; |
275 | } |
276 | |
277 | xnuproxy_panicked_thread_t *ex_thread = |
278 | &exclaves_panic_buffer.panicked_thread; |
279 | |
280 | status = os_atomic_load(&ex_thread->status, seq_cst); |
281 | |
282 | switch (status) { |
283 | case XNUPROXY_PANIC_PARTIAL: |
284 | status_str = "PARTIAL" ; |
285 | break; |
286 | case XNUPROXY_PANIC_COMPLETE: |
287 | status_str = "COMPLETE" ; |
288 | break; |
289 | default: |
290 | return; |
291 | } |
292 | |
293 | panic_info->eph_panic_flags |= EMBEDDED_PANIC_HEADER_FLAG_EXCLAVE_PANIC; |
294 | |
295 | paniclog_append_noflush("Exclaves additional info: STATUS: %s\n" , status_str); |
296 | exclaves_append_panic_addl_info(ex_thread); |
297 | |
298 | exclaves_append_panic_backtrace(); |
299 | } |
300 | |
301 | __attribute__((noinline, noreturn)) |
302 | void |
303 | exclaves_panic_thread_wait(void) |
304 | { |
305 | assert_wait((event_t)&exclaves_panic_thread_wait_forever, THREAD_UNINT); |
306 | (void) thread_block(THREAD_CONTINUE_NULL); |
307 | |
308 | /* NOT REACHABLE */ |
309 | panic("Exclaves panic thread woken up" ); |
310 | } |
311 | |
312 | void |
313 | handle_response_panic_buffer_address(pmap_paddr_t addr) |
314 | { |
315 | return copy_panic_buffer_pages(addr); |
316 | } |
317 | |
318 | kern_return_t |
319 | exclaves_panic_thread_setup(void) |
320 | { |
321 | thread_t thread = THREAD_NULL; |
322 | kern_return_t kr = KERN_FAILURE; |
323 | |
324 | kr = kernel_thread_start_priority(exclaves_xnu_proxy_panic_thread, NULL, |
325 | BASEPRI_DEFAULT, &thread); |
326 | if (kr != KERN_SUCCESS) { |
327 | return kr; |
328 | } |
329 | |
330 | thread_set_thread_name(thread, "EXCLAVES_PANIC_WAIT_THREAD" ); |
331 | thread_deallocate(thread); |
332 | |
333 | return KERN_SUCCESS; |
334 | } |
335 | |
336 | #endif /* CONFIG_ EXCLAVES */ |
337 | |