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
63static char exclaves_panic_string[EXCLAVES_PANIC_STRING_SIZE];
64static char *exclaves_panic_buffer_pages[PANIC_BUFFER_PAGE_COUNT];
65static xnuproxy_panic_buffer_t exclaves_panic_buffer;
66static int exclaves_panic_thread_wait_forever;
67
68static void
69copy_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
86static void
87exclaves_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
125static bool
126exclaves_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
160static void
161exclaves_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
187static void
188exclaves_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
235kern_return_t
236exclaves_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
266void
267exclaves_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))
302void
303exclaves_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
312void
313handle_response_panic_buffer_address(pmap_paddr_t addr)
314{
315 return copy_panic_buffer_pages(addr);
316}
317
318kern_return_t
319exclaves_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