| 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 | |