/* * Copyright (c) 2023 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #if CONFIG_EXCLAVES #if CONFIG_SPTM #include #else #error Invalid configuration #endif /* CONFIG_SPTM */ #include #include #include #include #include #include "exclaves_boot.h" #include "exclaves_debug.h" #include "exclaves_frame_mint.h" #include "exclaves_upcalls.h" #include "exclaves_internal.h" /* Lock around single-threaded exclaves boot */ LCK_MTX_DECLARE(exclaves_boot_lock, &exclaves_lck_grp); /* Boot status. */ __enum_closed_decl(exclaves_boot_status_t, uint32_t, { EXCLAVES_BS_NOT_DEFINED = 0, EXCLAVES_BS_NOT_SUPPORTED = 1, EXCLAVES_BS_NOT_STARTED = 2, EXCLAVES_BS_BOOTED_EXCLAVECORE = 3, EXCLAVES_BS_BOOTED_EXCLAVEKIT = 4, EXCLAVES_BS_BOOTED_FAILURE = 5, }); /* Atomic so that it can be safely checked outside the boot lock. */ static os_atomic(exclaves_boot_status_t) exclaves_boot_status = EXCLAVES_BS_NOT_DEFINED; static thread_t exclaves_boot_thread = THREAD_NULL; extern exclaves_boot_task_entry_t exclaves_boot_task_entries[] __SECTION_START_SYM(EXCLAVES_BOOT_TASK_SEGMENT, EXCLAVES_BOOT_TASK_SECTION); extern exclaves_boot_task_entry_t exclaves_boot_task_entries_end[] __SECTION_END_SYM(EXCLAVES_BOOT_TASK_SEGMENT, EXCLAVES_BOOT_TASK_SECTION); static int ebt_cmp(const void *e1, const void *e2) { const struct exclaves_boot_task_entry *a = e1; const struct exclaves_boot_task_entry *b = e2; if (a->ebt_rank > b->ebt_rank) { return 1; } if (a->ebt_rank < b->ebt_rank) { return -1; } return 0; } static void exclaves_boot_tasks(void) { const size_t count = exclaves_boot_task_entries_end - exclaves_boot_task_entries; assert3u(count, >, 0); __assert_only const size_t size = ((uintptr_t)exclaves_boot_task_entries_end - (uintptr_t)exclaves_boot_task_entries); assert3u(size % sizeof(exclaves_boot_task_entry_t), ==, 0); /* * exclaves_boot_task_entries is in _DATA_CONST, make a stack copy so it * can be sorted. */ exclaves_boot_task_entry_t boot_tasks[count]; memcpy(boot_tasks, exclaves_boot_task_entries, count * sizeof(boot_tasks[0])); extern void qsort(void *, size_t, size_t, int (*)(const void *, const void *)); qsort(boot_tasks, count, sizeof(boot_tasks[0]), ebt_cmp); for (size_t i = 0; i < count; i++) { exclaves_debug_printf(show_progress, "exclaves: boot task started, %s\n", boot_tasks[i].ebt_name); KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_EXCLAVES, MACH_EXCLAVES_BOOT_TASK) | DBG_FUNC_START, i); kern_return_t ret = boot_tasks[i].ebt_func(); KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_EXCLAVES, MACH_EXCLAVES_BOOT_TASK) | DBG_FUNC_END); exclaves_debug_printf(show_progress, "exclaves: boot task done, %s\n", boot_tasks[i].ebt_name); if (ret != KERN_SUCCESS) { panic("exclaves: boot task failed: %s (%p)", boot_tasks[i].ebt_name, &boot_tasks[i]); } } } /* * Check early in boot if the secure world has bootstrapped, and update the * boot status if not. */ __startup_func static void exclaves_check_sk(void) { const bool sk_bootstrapped = SPTMArgs->sk_bootstrapped; if (sk_bootstrapped) { os_atomic_store(&exclaves_boot_status, EXCLAVES_BS_NOT_STARTED, relaxed); } else { os_atomic_store(&exclaves_boot_status, EXCLAVES_BS_NOT_SUPPORTED, relaxed); } } STARTUP(EXCLAVES, STARTUP_RANK_FIRST, exclaves_check_sk); static void exclaves_boot_status_wait(const exclaves_boot_status_t status) { lck_mtx_assert(&exclaves_boot_lock, LCK_MTX_ASSERT_OWNED); while (os_atomic_load(&exclaves_boot_status, relaxed) < status) { lck_mtx_sleep(&exclaves_boot_lock, LCK_SLEEP_DEFAULT, (event_t)(uintptr_t)&exclaves_boot_status, THREAD_UNINT); } } static void exclaves_boot_status_wake(void) { lck_mtx_assert(&exclaves_boot_lock, LCK_MTX_ASSERT_OWNED); thread_wakeup((event_t)(uintptr_t)&exclaves_boot_status); } static void exclaves_boot_status_set(const exclaves_boot_status_t status) { assert3u(status, >, os_atomic_load(&exclaves_boot_status, relaxed)); lck_mtx_assert(&exclaves_boot_lock, LCK_MTX_ASSERT_OWNED); /* * 'release' here to ensure that the status update is only seen after * any boot update. exclaves_boot_status is loaded with 'acquire' in * exclaves_boot_wait() without holding the lock so the mutex alone * isn't sufficient for ordering. */ os_atomic_store(&exclaves_boot_status, status, release); exclaves_boot_status_wake(); } static bool exclaves_boot_status_is_supported(exclaves_boot_status_t status) { assert3u(status, !=, EXCLAVES_BS_NOT_DEFINED); return status != EXCLAVES_BS_NOT_DEFINED && status != EXCLAVES_BS_NOT_SUPPORTED; } static kern_return_t exclaves_boot_exclavecore(void) { lck_mtx_assert(&exclaves_boot_lock, LCK_MTX_ASSERT_OWNED); kern_return_t kr = KERN_FAILURE; const exclaves_boot_status_t status = os_atomic_load(&exclaves_boot_status, relaxed); if (!exclaves_boot_status_is_supported(status)) { return KERN_NOT_SUPPORTED; } /* * This should only ever happen if there's a userspace bug which causes * boot to be called twice and cause a race. */ if (status != EXCLAVES_BS_NOT_STARTED) { return KERN_INVALID_ARGUMENT; } /* Initialize xnu upcall server. */ kr = exclaves_upcall_init(); if (kr != KERN_SUCCESS) { panic("Exclaves upcall early init failed"); } /* Early boot. */ extern kern_return_t exclaves_boot_early(void); kr = exclaves_boot_early(); if (kr != KERN_SUCCESS) { /* * If exclaves failed to boot, there's not much that can be done other * than panic. */ panic("exclaves early boot failed"); } /* * At this point it should be possible to make tightbeam calls/configure * endpoints etc. */ exclaves_boot_thread = current_thread(); exclaves_boot_tasks(); exclaves_boot_thread = THREAD_NULL; exclaves_boot_status_set(EXCLAVES_BS_BOOTED_EXCLAVECORE); return kr; } static kern_return_t exclaves_boot_exclavekit(void) { lck_mtx_assert(&exclaves_boot_lock, LCK_MTX_ASSERT_OWNED); /* This should require an entitlement at some point. */ const exclaves_boot_status_t status = os_atomic_load(&exclaves_boot_status, relaxed); if (!exclaves_boot_status_is_supported(status)) { return KERN_NOT_SUPPORTED; } switch (status) { case EXCLAVES_BS_BOOTED_EXCLAVEKIT: return KERN_SUCCESS; case EXCLAVES_BS_BOOTED_FAILURE: return KERN_FAILURE; case EXCLAVES_BS_BOOTED_EXCLAVECORE: break; default: return KERN_INVALID_ARGUMENT; } /* * If framebank initialization fails, treat a failure to boot exclavekit * as a transition to the EXCLAVES_BS_BOOTED_FAILURE state * and return a failure. On RELEASE we simply panic as exclavekit is required. */ kern_return_t kr = exclaves_frame_mint_populate(); if (kr != KERN_SUCCESS) { exclaves_requirement_assert(EXCLAVES_R_FRAMEBANK, "failed to populate frame mint"); exclaves_boot_status_set(EXCLAVES_BS_BOOTED_FAILURE); return KERN_FAILURE; } exclaves_boot_status_set(EXCLAVES_BS_BOOTED_EXCLAVEKIT); return KERN_SUCCESS; } kern_return_t exclaves_boot(exclaves_boot_stage_t boot_stage) { lck_mtx_lock(&exclaves_boot_lock); kern_return_t kr = KERN_FAILURE; switch (boot_stage) { case EXCLAVES_BOOT_STAGE_EXCLAVECORE: kr = exclaves_boot_exclavecore(); break; case EXCLAVES_BOOT_STAGE_EXCLAVEKIT: if (!exclaves_requirement_is_relaxed(EXCLAVES_R_EXCLAVEKIT)) { kr = exclaves_boot_exclavekit(); } else { /* * If booting exclavekit was skipped due to a relaxed requirement, * treat that as a transiton to the EXCLAVES_BS_BOOTED_FAILURE * state and return a failure. On RELEASE we simply panic * as exclavekit is required. */ exclaves_requirement_assert(EXCLAVES_R_EXCLAVEKIT, "booting exclavekit skipped"); exclaves_boot_status_set(EXCLAVES_BS_BOOTED_FAILURE); } break; default: kr = KERN_INVALID_ARGUMENT; break; } lck_mtx_unlock(&exclaves_boot_lock); return kr; } /* * This returns once the specified boot stage has been reached. If exclaves are * unavailable, it returns immediately with KERN_NOT_SUPPORTED. * Make it NOINLINE so it shows up in backtraces. */ kern_return_t OS_NOINLINE exclaves_boot_wait(const exclaves_boot_stage_t desired_boot_stage) { assert(desired_boot_stage == EXCLAVES_BOOT_STAGE_EXCLAVECORE || desired_boot_stage == EXCLAVES_BOOT_STAGE_EXCLAVEKIT); /* Look up the equivalent status for the specified boot stage. */ const exclaves_boot_status_t desired_boot_status = desired_boot_stage == EXCLAVES_BOOT_STAGE_EXCLAVECORE ? EXCLAVES_BS_BOOTED_EXCLAVECORE: EXCLAVES_BS_BOOTED_EXCLAVEKIT; /* * See comment in exclaves_boot_status_set() for why this is an acquire. */ const exclaves_boot_status_t current_boot_status = os_atomic_load(&exclaves_boot_status, acquire); if (current_boot_status >= desired_boot_status) { /* * Special-case the situation where the request is to wait for * EXCLAVES_BOOT_STAGE_EXCLAVEKIT. EXCLAVEKIT boot can fail * (unlike EXCLAVECORE boot which will panic on failure). If * EXCLAVEKIT has failed, just return KERN_NOT_SUPPORTED. */ if (desired_boot_status == EXCLAVES_BS_BOOTED_EXCLAVEKIT && current_boot_status == EXCLAVES_BS_BOOTED_FAILURE) { return KERN_NOT_SUPPORTED; } return KERN_SUCCESS; } if (!exclaves_boot_status_is_supported(current_boot_status)) { return KERN_NOT_SUPPORTED; } /* * Allow the exclaves boot thread to pass this check during exclavecore * boot. This allows exclaves boot tasks to make TB calls etc during * exclavecore boot. */ if (desired_boot_status == EXCLAVES_BS_BOOTED_EXCLAVECORE && current_thread() == exclaves_boot_thread) { return KERN_SUCCESS; } /* * Otherwise, wait until exclaves has booted to the requested stage or * failed to boot. */ lck_mtx_lock(&exclaves_boot_lock); exclaves_boot_status_wait(desired_boot_status); lck_mtx_unlock(&exclaves_boot_lock); /* * At this point there are two possibilities. Success or EXCLAVEKIT has * failed (EXCLAVECORE can never fail as it panics on failure). */ if (desired_boot_status == EXCLAVES_BS_BOOTED_EXCLAVEKIT && current_boot_status == EXCLAVES_BS_BOOTED_FAILURE) { return KERN_NOT_SUPPORTED; } return KERN_SUCCESS; } /* * This returns AVAILABLE once EXCLAVES_BOOT_STAGE_EXCLAVECORE has completed. This is to * maintain backwards compatibility with existing code. */ exclaves_status_t exclaves_get_status(void) { kern_return_t kr = exclaves_boot_wait(EXCLAVES_BOOT_STAGE_EXCLAVECORE); assert(kr == KERN_SUCCESS || kr == KERN_NOT_SUPPORTED); if (kr == KERN_SUCCESS) { return EXCLAVES_STATUS_AVAILABLE; } return EXCLAVES_STATUS_NOT_SUPPORTED; } const char * exclaves_get_boot_status_string(void) { exclaves_boot_status_t boot_status = os_atomic_load(&exclaves_boot_status, relaxed); switch (boot_status) { case EXCLAVES_BS_NOT_DEFINED: return "NOT_DEFINED"; case EXCLAVES_BS_NOT_SUPPORTED: return "NOT_SUPPORTED"; case EXCLAVES_BS_NOT_STARTED: return "NOT_STARTED"; case EXCLAVES_BS_BOOTED_EXCLAVECORE: return "BOOTED_EXCLAVECORE"; case EXCLAVES_BS_BOOTED_EXCLAVEKIT: return "BOOTED_EXCLAVEKIT"; case EXCLAVES_BS_BOOTED_FAILURE: return "BOOTED_FAILURE"; default: assertf(false, "unknown boot status %u", boot_status); return "UNKNOWN"; } } exclaves_boot_stage_t exclaves_get_boot_stage(void) { exclaves_boot_status_t status = os_atomic_load(&exclaves_boot_status, relaxed); switch (status) { case EXCLAVES_BS_NOT_STARTED: case EXCLAVES_BS_NOT_SUPPORTED: case EXCLAVES_BS_NOT_DEFINED: return EXCLAVES_BOOT_STAGE_NONE; case EXCLAVES_BS_BOOTED_EXCLAVECORE: return EXCLAVES_BOOT_STAGE_EXCLAVECORE; case EXCLAVES_BS_BOOTED_EXCLAVEKIT: return EXCLAVES_BOOT_STAGE_EXCLAVEKIT; case EXCLAVES_BS_BOOTED_FAILURE: return EXCLAVES_BOOT_STAGE_FAILED; default: panic("unknown boot status: %u", status); } } bool exclaves_boot_supported(void) { exclaves_boot_status_t status; status = os_atomic_load(&exclaves_boot_status, relaxed); return exclaves_boot_status_is_supported(status); } #else /* CONFIG_EXCLAVES */ exclaves_status_t exclaves_get_status(void) { return EXCLAVES_STATUS_NOT_SUPPORTED; } exclaves_boot_stage_t exclaves_get_boot_stage(void) { return EXCLAVES_BOOT_STAGE_NONE; } bool exclaves_boot_supported(void) { return false; } #endif /* CONFIG_EXCLAVES */