/* * Copyright (c) 2021 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This 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 OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if CONFIG_SPTM #include #endif #if XNU_KERNEL_PRIVATE vm_address_t code_signing_allocate( size_t alloc_size) { vm_address_t alloc_addr = 0; if (alloc_size == 0) { panic("%s: zero allocation size", __FUNCTION__); } size_t aligned_size = round_page(alloc_size); kern_return_t ret = kmem_alloc( kernel_map, &alloc_addr, aligned_size, KMA_KOBJECT | KMA_DATA | KMA_ZERO, VM_KERN_MEMORY_SECURITY); if (ret != KERN_SUCCESS) { printf("%s: unable to allocate %lu bytes\n", __FUNCTION__, aligned_size); } else if (alloc_addr == 0) { printf("%s: invalid allocation\n", __FUNCTION__); } return alloc_addr; } void code_signing_deallocate( vm_address_t *alloc_addr, size_t alloc_size) { if (alloc_addr == NULL) { panic("%s: invalid pointer provided", __FUNCTION__); } else if ((*alloc_addr == 0) || ((*alloc_addr & PAGE_MASK) != 0)) { panic("%s: address provided: %p", __FUNCTION__, (void*)(*alloc_addr)); } else if (alloc_size == 0) { panic("%s: zero allocation size", __FUNCTION__); } size_t aligned_size = round_page(alloc_size); /* Free the allocation */ kmem_free(kernel_map, *alloc_addr, aligned_size); /* Clear the address */ *alloc_addr = 0; } #endif /* XNU_KERNEL_PRIVATE */ SYSCTL_DECL(_security); SYSCTL_DECL(_security_codesigning); SYSCTL_NODE(_security, OID_AUTO, codesigning, CTLFLAG_RD, 0, "XNU Code Signing"); static SECURITY_READ_ONLY_LATE(bool) cs_config_set = false; static SECURITY_READ_ONLY_LATE(code_signing_monitor_type_t) cs_monitor = CS_MONITOR_TYPE_NONE; static SECURITY_READ_ONLY_LATE(code_signing_config_t) cs_config = 0; static uint32_t security_boot_mode_complete = 0; SYSCTL_UINT(_security_codesigning, OID_AUTO, monitor, CTLFLAG_RD, &cs_monitor, 0, "code signing monitor type"); SYSCTL_UINT(_security_codesigning, OID_AUTO, config, CTLFLAG_RD, &cs_config, 0, "code signing configuration"); SYSCTL_UINT( _security_codesigning, OID_AUTO, security_boot_mode_complete, CTLFLAG_RD, &security_boot_mode_complete, 0, "security boot mode completion status"); void code_signing_configuration( code_signing_monitor_type_t *monitor_type_out, code_signing_config_t *config_out) { code_signing_monitor_type_t monitor_type = CS_MONITOR_TYPE_NONE; code_signing_config_t config = 0; /* * Since we read this variable with load-acquire semantics, if we observe a value * of true, it means we should be able to observe writes to cs_monitor and also * cs_config. */ if (os_atomic_load(&cs_config_set, acquire) == true) { goto config_set; } /* * Add support for all the code signing features. This function is called very * early in the system boot, much before kernel extensions such as Apple Mobile * File Integrity come online. As a result, this function assumes that all the * code signing features are enabled, and later on, different components can * disable support for different features using disable_code_signing_feature(). */ config |= CS_CONFIG_MAP_JIT; config |= CS_CONFIG_DEVELOPER_MODE_SUPPORTED; config |= CS_CONFIG_COMPILATION_SERVICE; config |= CS_CONFIG_LOCAL_SIGNING; config |= CS_CONFIG_OOP_JIT; #if CODE_SIGNING_MONITOR /* Mark the code signing monitor as enabled if required */ if (csm_enabled() == true) { config |= CS_CONFIG_CSM_ENABLED; } #if CONFIG_SPTM /* * Since TrustedExecutionMonitor cannot call into any function within XNU, we * query it's code signing configuration even before this function is called. * Using that, we modify the state of the code signing features available. */ if (csm_enabled() == true) { bool platform_code_only = txm_cs_config->systemPolicy->platformCodeOnly; /* Disable unsupported features when enforcing platform-code-only */ if (platform_code_only == true) { config &= ~CS_CONFIG_MAP_JIT; config &= ~CS_CONFIG_COMPILATION_SERVICE; config &= ~CS_CONFIG_LOCAL_SIGNING; config &= ~CS_CONFIG_OOP_JIT; } /* * Restricted Execution Mode support. The pattern for this code snippet breaks * the norm compared to others. For the other features, we consider them enabled * by default unless TXM disables them. For REM, given this is a TXM only feature, * we consider it disabled unless TXM explicitly tells us it is enabled. */ if (txm_cs_config->systemPolicy->featureSet.restrictedExecutionMode == true) { config |= CS_CONFIG_REM_SUPPORTED; } /* MAP_JIT support */ if (txm_cs_config->systemPolicy->featureSet.JIT == false) { config &= ~CS_CONFIG_MAP_JIT; } /* Developer mode support */ if (txm_cs_config->systemPolicy->featureSet.developerMode == false) { config &= ~CS_CONFIG_DEVELOPER_MODE_SUPPORTED; } /* Compilation service support */ if (txm_cs_config->systemPolicy->featureSet.compilationService == false) { config &= ~CS_CONFIG_COMPILATION_SERVICE; } /* Local signing support */ if (txm_cs_config->systemPolicy->featureSet.localSigning == false) { config &= ~CS_CONFIG_LOCAL_SIGNING; } /* OOP-JIT support */ if (txm_cs_config->systemPolicy->featureSet.OOPJit == false) { config &= ~CS_CONFIG_OOP_JIT; } } monitor_type = CS_MONITOR_TYPE_TXM; #elif PMAP_CS_PPL_MONITOR monitor_type = CS_MONITOR_TYPE_PPL; #endif /* CONFIG_SPTM */ #endif /* CODE_SIGNING_MONITOR */ #if DEVELOPMENT || DEBUG /* * We only ever need to parse for boot-args based exemption state on DEVELOPMENT * or DEBUG builds as this state is not respected by any code signing component * on RELEASE builds. */ #define CS_AMFI_MASK_UNRESTRICT_TASK_FOR_PID 0x01 #define CS_AMFI_MASK_ALLOW_ANY_SIGNATURE 0x02 #define CS_AMFI_MASK_GET_OUT_OF_MY_WAY 0x80 int amfi_mask = 0; int amfi_allow_any_signature = 0; int amfi_unrestrict_task_for_pid = 0; int amfi_get_out_of_my_way = 0; int cs_enforcement_disabled = 0; int cs_integrity_skip = 0; int amfi_relax_profile_trust = 0; /* Parse the AMFI mask */ PE_parse_boot_argn("amfi", &amfi_mask, sizeof(amfi_mask)); /* Parse the AMFI soft-bypass */ PE_parse_boot_argn( "amfi_allow_any_signature", &amfi_allow_any_signature, sizeof(amfi_allow_any_signature)); /* Parse the AMFI debug-bypass */ PE_parse_boot_argn( "amfi_unrestrict_task_for_pid", &amfi_unrestrict_task_for_pid, sizeof(amfi_unrestrict_task_for_pid)); /* Parse the AMFI hard-bypass */ PE_parse_boot_argn( "amfi_get_out_of_my_way", &amfi_get_out_of_my_way, sizeof(amfi_get_out_of_my_way)); /* Parse the system code signing hard-bypass */ PE_parse_boot_argn( "cs_enforcement_disable", &cs_enforcement_disabled, sizeof(cs_enforcement_disabled)); /* Parse the system code signing integrity-check bypass */ PE_parse_boot_argn( "cs_integrity_skip", &cs_integrity_skip, sizeof(cs_integrity_skip)); /* Parse the AMFI profile trust bypass */ PE_parse_boot_argn( "amfi_relax_profile_trust", &amfi_relax_profile_trust, sizeof(amfi_relax_profile_trust)); /* CS_CONFIG_UNRESTRICTED_DEBUGGING */ if (amfi_mask & CS_AMFI_MASK_UNRESTRICT_TASK_FOR_PID) { config |= CS_CONFIG_UNRESTRICTED_DEBUGGING; } else if (amfi_unrestrict_task_for_pid) { config |= CS_CONFIG_UNRESTRICTED_DEBUGGING; } /* CS_CONFIG_ALLOW_ANY_SIGNATURE */ if (amfi_mask & CS_AMFI_MASK_ALLOW_ANY_SIGNATURE) { config |= CS_CONFIG_ALLOW_ANY_SIGNATURE; } else if (amfi_mask & CS_AMFI_MASK_GET_OUT_OF_MY_WAY) { config |= CS_CONFIG_ALLOW_ANY_SIGNATURE; } else if (amfi_allow_any_signature) { config |= CS_CONFIG_ALLOW_ANY_SIGNATURE; } else if (amfi_get_out_of_my_way) { config |= CS_CONFIG_ALLOW_ANY_SIGNATURE; } else if (cs_enforcement_disabled) { config |= CS_CONFIG_ALLOW_ANY_SIGNATURE; } /* CS_CONFIG_ENFORCEMENT_DISABLED */ if (cs_enforcement_disabled) { config |= CS_CONFIG_ENFORCEMENT_DISABLED; } /* CS_CONFIG_GET_OUT_OF_MY_WAY */ if (amfi_mask & CS_AMFI_MASK_GET_OUT_OF_MY_WAY) { config |= CS_CONFIG_GET_OUT_OF_MY_WAY; } else if (amfi_get_out_of_my_way) { config |= CS_CONFIG_GET_OUT_OF_MY_WAY; } else if (cs_enforcement_disabled) { config |= CS_CONFIG_GET_OUT_OF_MY_WAY; } /* CS_CONFIG_INTEGRITY_SKIP */ if (cs_integrity_skip) { config |= CS_CONFIG_INTEGRITY_SKIP; } /* CS_CONFIG_RELAX_PROFILE_TRUST */ if (amfi_relax_profile_trust) { config |= CS_CONFIG_RELAX_PROFILE_TRUST; } #if CONFIG_SPTM if (csm_enabled() == true) { /* allow_any_signature */ if (txm_cs_config->exemptions.allowAnySignature == false) { config &= ~CS_CONFIG_ALLOW_ANY_SIGNATURE; } /* unrestrict_task_for_pid */ if (txm_ro_data && !txm_ro_data->exemptions.allowUnrestrictedDebugging) { config &= ~CS_CONFIG_UNRESTRICTED_DEBUGGING; } /* cs_enforcement_disable */ if (txm_ro_data && !txm_ro_data->exemptions.allowModifiedCode) { config &= ~CS_CONFIG_ENFORCEMENT_DISABLED; } /* get_out_of_my_way (skip_trust_evaluation) */ if (txm_cs_config->exemptions.skipTrustEvaluation == false) { config &= ~CS_CONFIG_GET_OUT_OF_MY_WAY; } #if kTXMKernelAPIVersion >= 7 /* * In some cases, the relax_profile_trust exemption can be set even without * the boot-arg on TXM devices. As a result, we always overrule the kernel's * data with TXM's data for this exemption. */ if (txm_cs_config->exemptions.relaxProfileTrust == true) { config |= CS_CONFIG_RELAX_PROFILE_TRUST; } else { config &= ~CS_CONFIG_RELAX_PROFILE_TRUST; } #endif } #elif PMAP_CS_PPL_MONITOR if (csm_enabled() == true) { int pmap_cs_allow_any_signature = 0; bool override = PE_parse_boot_argn( "pmap_cs_allow_any_signature", &pmap_cs_allow_any_signature, sizeof(pmap_cs_allow_any_signature)); if (!pmap_cs_allow_any_signature && override) { config &= ~CS_CONFIG_ALLOW_ANY_SIGNATURE; } int pmap_cs_unrestrict_task_for_pid = 0; override = PE_parse_boot_argn( "pmap_cs_unrestrict_pmap_cs_disable", &pmap_cs_unrestrict_task_for_pid, sizeof(pmap_cs_unrestrict_task_for_pid)); if (!pmap_cs_unrestrict_task_for_pid && override) { config &= ~CS_CONFIG_UNRESTRICTED_DEBUGGING; } int pmap_cs_enforcement_disable = 0; override = PE_parse_boot_argn( "pmap_cs_allow_modified_code_pages", &pmap_cs_enforcement_disable, sizeof(pmap_cs_enforcement_disable)); if (!pmap_cs_enforcement_disable && override) { config &= ~CS_CONFIG_ENFORCEMENT_DISABLED; } } #endif /* CONFIG_SPTM */ #endif /* DEVELOPMENT || DEBUG */ os_atomic_store(&cs_monitor, monitor_type, relaxed); os_atomic_store(&cs_config, config, relaxed); /* * We write the cs_config_set variable with store-release semantics which means * no writes before this call will be re-ordered to after this call. Hence, if * someone reads this variable with load-acquire semantics, and they observe a * value of true, then they will be able to observe the correct values of the * cs_monitor and the cs_config variables as well. */ os_atomic_store(&cs_config_set, true, release); config_set: /* Ensure configuration has been set */ assert(os_atomic_load(&cs_config_set, relaxed) == true); /* Set the monitor type */ if (monitor_type_out) { *monitor_type_out = os_atomic_load(&cs_monitor, relaxed); } /* Set the configuration */ if (config_out) { *config_out = os_atomic_load(&cs_config, relaxed); } } void disable_code_signing_feature( code_signing_config_t feature) { /* * We require that this function be called only after the code signing config * has been setup initially with a call to code_signing_configuration. */ if (os_atomic_load(&cs_config_set, acquire) == false) { panic("attempted to disable code signing feature without init: %u", feature); } /* * We require that only a single feature be disabled through a single call to this * function. Moreover, we ensure that only valid features are being disabled. */ switch (feature) { case CS_CONFIG_DEVELOPER_MODE_SUPPORTED: cs_config &= ~CS_CONFIG_DEVELOPER_MODE_SUPPORTED; break; case CS_CONFIG_COMPILATION_SERVICE: cs_config &= ~CS_CONFIG_COMPILATION_SERVICE; break; case CS_CONFIG_LOCAL_SIGNING: cs_config &= ~CS_CONFIG_LOCAL_SIGNING; break; case CS_CONFIG_OOP_JIT: cs_config &= ~CS_CONFIG_OOP_JIT; break; case CS_CONFIG_MAP_JIT: cs_config &= ~CS_CONFIG_MAP_JIT; break; default: panic("attempted to disable a code signing feature invalidly: %u", feature); } /* Ensure all readers can observe the latest data */ #if defined(__arm64__) __asm__ volatile ("dmb ish" ::: "memory"); #elif defined(__x86_64__) __asm__ volatile ("mfence" ::: "memory"); #else #error "Unknown platform -- fence instruction unavailable" #endif } kern_return_t secure_channel_shared_page( uint64_t *secure_channel_phys, size_t *secure_channel_size) { return CSM_PREFIX(secure_channel_shared_page)( secure_channel_phys, secure_channel_size); } #pragma mark Developer Mode void enable_developer_mode(void) { CSM_PREFIX(toggle_developer_mode)(true); } void disable_developer_mode(void) { CSM_PREFIX(toggle_developer_mode)(false); } bool developer_mode_state(void) { /* Assume false if the pointer isn't setup */ if (developer_mode_enabled == NULL) { return false; } return os_atomic_load(developer_mode_enabled, relaxed); } #pragma mark Restricted Execution Mode kern_return_t restricted_execution_mode_enable(void) { return CSM_PREFIX(rem_enable)(); } kern_return_t restricted_execution_mode_state(void) { return CSM_PREFIX(rem_state)(); } void update_csm_device_state(void) { CSM_PREFIX(update_device_state)(); } void complete_security_boot_mode( uint32_t security_boot_mode) { CSM_PREFIX(complete_security_boot_mode)(security_boot_mode); /* * If we're reach here, it means the completion of the security boot mode was * successful. We update our sysctl with the provided boot mode in order to * signify both completion and the boot mode identifier. */ security_boot_mode_complete = security_boot_mode; } #pragma mark Provisioning Profiles /* * AMFI performs full profile validation by itself. XNU only needs to manage provisioning * profiles when we have a monitor since the monitor needs to independently verify the * profile data as well. */ void garbage_collect_provisioning_profiles(void) { #if CODE_SIGNING_MONITOR csm_free_provisioning_profiles(); #endif } #if CODE_SIGNING_MONITOR /* Structure used to maintain the set of registered profiles on the system */ typedef struct _cs_profile { /* The UUID of the registered profile */ uuid_t profile_uuid; /* The profile validation object from the monitor */ void *profile_obj; /* * In order to minimize the number of times the same profile would need to be * registered, we allow frequently used profiles to skip the garbage collector * for one pass. */ bool skip_collector; /* We skip repeated trust validations of the profile */ bool trusted; /* Linked list linkage */ SLIST_ENTRY(_cs_profile) link; } cs_profile_t; /* Linked list head for registered profiles */ static SLIST_HEAD(, _cs_profile) all_profiles = SLIST_HEAD_INITIALIZER(all_profiles); /* Lock for the provisioning profiles */ LCK_GRP_DECLARE(profiles_lck_grp, "profiles_lck_grp"); decl_lck_rw_data(, profiles_lock); void csm_initialize_provisioning_profiles(void) { /* Ensure the CoreTrust kernel extension has loaded */ if (coretrust == NULL) { panic("coretrust interface not available"); } /* Initialize the provisoning profiles lock */ lck_rw_init(&profiles_lock, &profiles_lck_grp, 0); printf("initialized XNU provisioning profile data\n"); #if PMAP_CS_PPL_MONITOR pmap_initialize_provisioning_profiles(); #endif } static cs_profile_t* search_for_profile_uuid( const uuid_t profile_uuid) { cs_profile_t *profile = NULL; /* Caller is required to acquire the lock */ lck_rw_assert(&profiles_lock, LCK_RW_ASSERT_HELD); SLIST_FOREACH(profile, &all_profiles, link) { if (uuid_compare(profile_uuid, profile->profile_uuid) == 0) { return profile; } } return NULL; } kern_return_t csm_register_provisioning_profile( const uuid_t profile_uuid, const void *profile_blob, const size_t profile_blob_size) { cs_profile_t *profile = NULL; void *monitor_profile_obj = NULL; kern_return_t ret = KERN_DENIED; /* Only proceed if code-signing-monitor is enabled */ if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } /* Allocate storage for the profile wrapper object */ profile = kalloc_type(cs_profile_t, Z_WAITOK_ZERO); assert(profile != NULL); /* Lock the profile set exclusively */ lck_rw_lock_exclusive(&profiles_lock); /* Check to make sure this isn't a duplicate UUID */ cs_profile_t *dup_profile = search_for_profile_uuid(profile_uuid); if (dup_profile != NULL) { /* This profile might be used soon -- skip garbage collector */ dup_profile->skip_collector = true; ret = KERN_ALREADY_IN_SET; goto exit; } ret = CSM_PREFIX(register_provisioning_profile)( profile_blob, profile_blob_size, &monitor_profile_obj); if (ret == KERN_SUCCESS) { /* Copy in the profile UUID */ uuid_copy(profile->profile_uuid, profile_uuid); /* Setup the monitor's profile object */ profile->profile_obj = monitor_profile_obj; /* This profile might be used soon -- skip garbage collector */ profile->skip_collector = true; /* Insert at the head of the profile set */ SLIST_INSERT_HEAD(&all_profiles, profile, link); } exit: /* Unlock the profile set */ lck_rw_unlock_exclusive(&profiles_lock); if (ret != KERN_SUCCESS) { /* Free the profile wrapper object */ kfree_type(cs_profile_t, profile); profile = NULL; if (ret != KERN_ALREADY_IN_SET) { printf("unable to register profile with monitor: %d\n", ret); } } return ret; } kern_return_t csm_trust_provisioning_profile( const uuid_t profile_uuid, const void *sig_data, size_t sig_size) { cs_profile_t *profile = NULL; kern_return_t ret = KERN_NOT_FOUND; /* * We don't explicitly make a check here for if the code-signing-monitor is enabled * or not because this function should never be called unless registration of the * profile succeeded, which it won't in cases where the CSM is disabled. * * If this function does somehow get called, it'll result in a panic -- this is good * for us to detect and to fix the code path which results in this behavior. */ /* Lock the profile set exclusively */ lck_rw_lock_exclusive(&profiles_lock); /* Search for the registered profile */ profile = search_for_profile_uuid(profile_uuid); if (profile == NULL) { goto exit; } else if (profile->trusted == true) { ret = KERN_SUCCESS; goto exit; } ret = CSM_PREFIX(trust_provisioning_profile)( profile->profile_obj, sig_data, sig_size); /* Mark profile as trusted if needed */ if (ret == KERN_SUCCESS) { profile->trusted = true; } else { printf("unable to trust profile with monitor: %d\n", ret); } exit: /* Unlock the profile set */ lck_rw_unlock_exclusive(&profiles_lock); return ret; } kern_return_t csm_associate_provisioning_profile( void *monitor_sig_obj, const uuid_t profile_uuid) { cs_profile_t *profile = NULL; kern_return_t ret = KERN_DENIED; /* * We don't explicitly make a check here for if the code-signing-monitor is enabled * or not because this function should never be called unless registration of the * profile succeeded, which it won't in cases where the CSM is disabled. * * If this function does somehow get called, it'll result in a panic -- this is good * for us to detect and to fix the code path which results in this behavior. */ /* Lock the profile set as shared */ lck_rw_lock_shared(&profiles_lock); /* Search for the provisioning profile */ profile = search_for_profile_uuid(profile_uuid); if (profile == NULL) { ret = KERN_NOT_FOUND; goto exit; } ret = CSM_PREFIX(associate_provisioning_profile)( monitor_sig_obj, profile->profile_obj); if (ret == KERN_SUCCESS) { /* * This seems like an active profile -- let it skip the garbage collector on * the next pass. We can modify this field even though we've only taken a shared * lock as in this case we're always setting it to a fixed value. */ profile->skip_collector = true; } exit: /* Unlock the profile set */ lck_rw_unlock_shared(&profiles_lock); if (ret != KERN_SUCCESS) { printf("unable to associate profile: %d\n", ret); } return ret; } kern_return_t csm_disassociate_provisioning_profile( void *monitor_sig_obj) { kern_return_t ret = KERN_DENIED; if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } /* Call out to the monitor */ ret = CSM_PREFIX(disassociate_provisioning_profile)(monitor_sig_obj); if ((ret != KERN_SUCCESS) && (ret != KERN_NOT_FOUND)) { printf("unable to disassociate profile: %d\n", ret); } return ret; } static kern_return_t unregister_provisioning_profile( cs_profile_t *profile) { kern_return_t ret = KERN_DENIED; /* Call out to the monitor */ ret = CSM_PREFIX(unregister_provisioning_profile)(profile->profile_obj); /* * KERN_FAILURE represents the case when the unregistration failed because the * monitor noted that the profile was still being used. Other than that, there * is no other error expected out of this interface. In fact, there is no easy * way to deal with other errors, as the profile state may be corrupted. If we * see a different error, then we panic. */ if ((ret != KERN_SUCCESS) && (ret != KERN_FAILURE)) { panic("unable to unregister profile from monitor: %d | %p\n", ret, profile); } return ret; } void csm_free_provisioning_profiles(void) { kern_return_t ret = KERN_DENIED; cs_profile_t *profile = NULL; cs_profile_t *temp_profile = NULL; /* Lock the profile set exclusively */ lck_rw_lock_exclusive(&profiles_lock); SLIST_FOREACH_SAFE(profile, &all_profiles, link, temp_profile) { if (profile->skip_collector == true) { profile->skip_collector = false; continue; } /* Attempt to unregister this profile from the system */ ret = unregister_provisioning_profile(profile); if (ret == KERN_SUCCESS) { /* Remove the profile from the profile set */ SLIST_REMOVE(&all_profiles, profile, _cs_profile, link); /* Free the memory consumed for the profile wrapper object */ kfree_type(cs_profile_t, profile); profile = NULL; } } /* Unlock the profile set */ lck_rw_unlock_exclusive(&profiles_lock); } #endif /* CODE_SIGNING_MONITOR */ #pragma mark Code Signing /* * AMFI performs full signature validation by itself. For some things, AMFI uses XNU in * order to abstract away the underlying implementation for data storage, but for most of * these, AMFI doesn't directly interact with them, and they're only required when we have * a code signing monitor on the system. */ void set_compilation_service_cdhash( const uint8_t cdhash[CS_CDHASH_LEN]) { CSM_PREFIX(set_compilation_service_cdhash)(cdhash); } bool match_compilation_service_cdhash( const uint8_t cdhash[CS_CDHASH_LEN]) { return CSM_PREFIX(match_compilation_service_cdhash)(cdhash); } void set_local_signing_public_key( const uint8_t public_key[XNU_LOCAL_SIGNING_KEY_SIZE]) { CSM_PREFIX(set_local_signing_public_key)(public_key); } uint8_t* get_local_signing_public_key(void) { return CSM_PREFIX(get_local_signing_public_key)(); } void unrestrict_local_signing_cdhash( __unused const uint8_t cdhash[CS_CDHASH_LEN]) { /* * Since AMFI manages code signing on its own, we only need to unrestrict the * local signing cdhash when we have a monitor environment. */ #if CODE_SIGNING_MONITOR CSM_PREFIX(unrestrict_local_signing_cdhash)(cdhash); #endif } kern_return_t get_trust_level_kdp( __unused pmap_t pmap, __unused uint32_t *trust_level) { #if CODE_SIGNING_MONITOR return csm_get_trust_level_kdp(pmap, trust_level); #else return KERN_NOT_SUPPORTED; #endif } kern_return_t get_jit_address_range_kdp( __unused pmap_t pmap, __unused uintptr_t *jit_region_start, __unused uintptr_t *jit_region_end) { #if CODE_SIGNING_MONITOR return csm_get_jit_address_range_kdp(pmap, jit_region_start, jit_region_end); #else return KERN_NOT_SUPPORTED; #endif } kern_return_t csm_resolve_os_entitlements_from_proc( __unused const proc_t process, __unused const void **os_entitlements) { #if CODE_SIGNING_MONITOR task_t task = NULL; vm_map_t task_map = NULL; pmap_t task_pmap = NULL; kern_return_t ret = KERN_DENIED; if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } /* Ensure the process comes from the proc_task zone */ proc_require(process, PROC_REQUIRE_ALLOW_ALL); /* Acquire the task from the proc */ task = proc_task(process); if (task == NULL) { return KERN_NOT_FOUND; } /* Acquire the virtual memory map from the task -- takes a reference on it */ task_map = get_task_map_reference(task); if (task_map == NULL) { return KERN_NOT_FOUND; } /* Acquire the pmap from the virtual memory map */ task_pmap = vm_map_get_pmap(task_map); assert(task_pmap != NULL); /* Call into the monitor to resolve the entitlements */ ret = CSM_PREFIX(resolve_kernel_entitlements)(task_pmap, os_entitlements); /* Release the reference on the virtual memory map */ vm_map_deallocate(task_map); return ret; #else return KERN_NOT_SUPPORTED; #endif } kern_return_t address_space_debugged( const proc_t process) { /* Must pass in a valid proc_t */ if (process == NULL) { printf("%s: provided a NULL process\n", __FUNCTION__); return KERN_DENIED; } proc_require(process, PROC_REQUIRE_ALLOW_ALL); /* Developer mode must always be enabled for this to return successfully */ if (developer_mode_state() == false) { return KERN_DENIED; } #if CODE_SIGNING_MONITOR task_t task = NULL; vm_map_t task_map = NULL; pmap_t task_pmap = NULL; if (csm_enabled() == true) { /* Acquire the task from the proc */ task = proc_task(process); if (task == NULL) { return KERN_NOT_FOUND; } /* Acquire the virtual memory map from the task -- takes a reference on it */ task_map = get_task_map_reference(task); if (task_map == NULL) { return KERN_NOT_FOUND; } /* Acquire the pmap from the virtual memory map */ task_pmap = vm_map_get_pmap(task_map); assert(task_pmap != NULL); /* Acquire the state from the monitor */ kern_return_t ret = CSM_PREFIX(address_space_debugged)(task_pmap); /* Release the reference on the virtual memory map */ vm_map_deallocate(task_map); return ret; } #endif /* CODE_SIGNING_MONITOR */ /* Check read-only process flags for state */ if (proc_getcsflags(process) & CS_DEBUGGED) { return KERN_SUCCESS; } #if XNU_TARGET_OS_OSX /* * For macOS systems only, we allow the execution of unsigned code. On Intel, code * doesn't need to be signed, and on ASi, Rosetta binaries don't need to be signed. * In these cases, we return successfully from this function because we don't know * what else we can do. */ if ((proc_getcsflags(process) & CS_SIGNED) == 0) { return KERN_SUCCESS; } #endif return KERN_DENIED; } #if CODE_SIGNING_MONITOR bool csm_enabled(void) { return CSM_PREFIX(code_signing_enabled)(); } vm_size_t csm_signature_size_limit(void) { return CSM_PREFIX(managed_code_signature_size)(); } void csm_check_lockdown_mode(void) { if (get_lockdown_mode_state() == 0) { return; } /* Inform the code signing monitor about lockdown mode */ CSM_PREFIX(enter_lockdown_mode)(); #if CONFIG_SPTM /* MAP_JIT lockdown */ if (txm_cs_config->systemPolicy->featureSet.JIT == false) { disable_code_signing_feature(CS_CONFIG_MAP_JIT); } /* Compilation service lockdown */ if (txm_cs_config->systemPolicy->featureSet.compilationService == false) { disable_code_signing_feature(CS_CONFIG_COMPILATION_SERVICE); } /* Local signing lockdown */ if (txm_cs_config->systemPolicy->featureSet.localSigning == false) { disable_code_signing_feature(CS_CONFIG_LOCAL_SIGNING); } /* OOP-JIT lockdown */ if (txm_cs_config->systemPolicy->featureSet.OOPJit == false) { disable_code_signing_feature(CS_CONFIG_OOP_JIT); } #else /* * Lockdown mode is supposed to disable all forms of JIT on the system. For now, * we leave JIT enabled by default until some blockers are resolved. The way this * code is written, we don't need to change anything once we enforce MAP_JIT to * be disabled for lockdown mode. */ if (ppl_lockdown_mode_enforce_jit == true) { disable_code_signing_feature(CS_CONFIG_MAP_JIT); } disable_code_signing_feature(CS_CONFIG_OOP_JIT); disable_code_signing_feature(CS_CONFIG_LOCAL_SIGNING); disable_code_signing_feature(CS_CONFIG_COMPILATION_SERVICE); #endif /* CONFIG_SPTM */ } void csm_code_signing_violation( proc_t proc, vm_offset_t addr) { /* No enforcement if code-signing-monitor is disabled */ if (csm_enabled() == false) { return; } /* Leave a log for triage purposes */ printf("[%s] code-signing-violation at %p\n", proc_best_name(proc), (void*)addr); /* * For now, the only input into this function is from current_proc(), so using current_thread() * over here is alright. If this function ever gets called from another location, we need to * then change where we get the user thread from. */ assert(proc == current_proc()); /* * Force exit the process and set it to allow generating crash reports, which is critical * for better triaging these issues. */ exception_info_t info = { .os_reason = OS_REASON_CODESIGNING, .exception_type = EXC_BAD_ACCESS, .mx_code = CODESIGNING_EXIT_REASON_INVALID_PAGE, .mx_subcode = VM_USER_STRIP_PTR(addr), .kt_info.kt_subsys = KDBG_TRIAGE_SUBSYS_VM, .kt_info.kt_error = KDBG_TRIAGE_VM_CODE_SIGNING }; exit_with_mach_exception(proc, info, PX_KTRIAGE); } kern_return_t csm_register_code_signature( const vm_address_t signature_addr, const vm_size_t signature_size, const vm_offset_t code_directory_offset, const char *signature_path, void **monitor_sig_obj, vm_address_t *monitor_signature_addr) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(register_code_signature)( signature_addr, signature_size, code_directory_offset, signature_path, monitor_sig_obj, monitor_signature_addr); } kern_return_t csm_unregister_code_signature( void *monitor_sig_obj) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(unregister_code_signature)(monitor_sig_obj); } kern_return_t csm_verify_code_signature( void *monitor_sig_obj) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(verify_code_signature)(monitor_sig_obj); } kern_return_t csm_reconstitute_code_signature( void *monitor_sig_obj, vm_address_t *unneeded_addr, vm_size_t *unneeded_size) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(reconstitute_code_signature)( monitor_sig_obj, unneeded_addr, unneeded_size); } kern_return_t csm_associate_code_signature( pmap_t monitor_pmap, void *monitor_sig_obj, const vm_address_t region_addr, const vm_size_t region_size, const vm_offset_t region_offset) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(associate_code_signature)( monitor_pmap, monitor_sig_obj, region_addr, region_size, region_offset); } kern_return_t csm_allow_jit_region( pmap_t monitor_pmap) { if (csm_enabled() == false) { return KERN_SUCCESS; } else if (monitor_pmap == NULL) { return KERN_DENIED; } kern_return_t ret = CSM_PREFIX(allow_jit_region)(monitor_pmap); if (ret == KERN_NOT_SUPPORTED) { /* * Some monitor environments do not support this API and as a result will * return KERN_NOT_SUPPORTED. The caller here should not interpret that as * a failure. */ ret = KERN_SUCCESS; } return ret; } kern_return_t csm_associate_jit_region( pmap_t monitor_pmap, const vm_address_t region_addr, const vm_size_t region_size) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(associate_jit_region)( monitor_pmap, region_addr, region_size); } kern_return_t csm_associate_debug_region( pmap_t monitor_pmap, const vm_address_t region_addr, const vm_size_t region_size) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(associate_debug_region)( monitor_pmap, region_addr, region_size); } kern_return_t csm_allow_invalid_code( pmap_t pmap) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(allow_invalid_code)(pmap); } kern_return_t csm_get_trust_level_kdp( pmap_t pmap, uint32_t *trust_level) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(get_trust_level_kdp)(pmap, trust_level); } kern_return_t csm_get_jit_address_range_kdp( pmap_t pmap, uintptr_t *jit_region_start, uintptr_t *jit_region_end) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(get_jit_address_range_kdp)(pmap, jit_region_start, jit_region_end); } kern_return_t csm_address_space_exempt( const pmap_t pmap) { /* * These exemptions are actually orthogonal to the code signing enforcement. As * a result, we let each monitor explicitly decide how to deal with the exemption * in case code signing enforcement is disabled. */ return CSM_PREFIX(address_space_exempt)(pmap); } kern_return_t csm_fork_prepare( pmap_t old_pmap, pmap_t new_pmap) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(fork_prepare)(old_pmap, new_pmap); } kern_return_t csm_acquire_signing_identifier( const void *monitor_sig_obj, const char **signing_id) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(acquire_signing_identifier)(monitor_sig_obj, signing_id); } kern_return_t csm_associate_os_entitlements( void *monitor_sig_obj, const void *os_entitlements) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } else if (os_entitlements == NULL) { /* Not every signature has entitlements */ return KERN_SUCCESS; } return CSM_PREFIX(associate_kernel_entitlements)(monitor_sig_obj, os_entitlements); } kern_return_t csm_accelerate_entitlements( void *monitor_sig_obj, CEQueryContext_t *ce_ctx) { if (csm_enabled() == false) { return KERN_NOT_SUPPORTED; } return CSM_PREFIX(accelerate_entitlements)(monitor_sig_obj, ce_ctx); } #endif /* CODE_SIGNING_MONITOR */ #pragma mark AppleImage4 /* * AppleImage4 uses the monitor environment to safeguard critical security data. * In order to ease the implementation specific, AppleImage4 always depends on these * abstracted APIs, regardless of whether the system has a monitor environment or * not. */ void* kernel_image4_storage_data( size_t *allocated_size) { return CSM_PREFIX(image4_storage_data)(allocated_size); } void kernel_image4_set_nonce( const img4_nonce_domain_index_t ndi, const img4_nonce_t *nonce) { return CSM_PREFIX(image4_set_nonce)(ndi, nonce); } void kernel_image4_roll_nonce( const img4_nonce_domain_index_t ndi) { return CSM_PREFIX(image4_roll_nonce)(ndi); } errno_t kernel_image4_copy_nonce( const img4_nonce_domain_index_t ndi, img4_nonce_t *nonce_out) { return CSM_PREFIX(image4_copy_nonce)(ndi, nonce_out); } errno_t kernel_image4_execute_object( img4_runtime_object_spec_index_t obj_spec_index, const img4_buff_t *payload, const img4_buff_t *manifest) { return CSM_PREFIX(image4_execute_object)( obj_spec_index, payload, manifest); } errno_t kernel_image4_copy_object( img4_runtime_object_spec_index_t obj_spec_index, vm_address_t object_out, size_t *object_length) { return CSM_PREFIX(image4_copy_object)( obj_spec_index, object_out, object_length); } const void* kernel_image4_get_monitor_exports(void) { return CSM_PREFIX(image4_get_monitor_exports)(); } errno_t kernel_image4_set_release_type( const char *release_type) { return CSM_PREFIX(image4_set_release_type)(release_type); } errno_t kernel_image4_set_bnch_shadow( const img4_nonce_domain_index_t ndi) { return CSM_PREFIX(image4_set_bnch_shadow)(ndi); } #pragma mark Image4 - New static errno_t _kernel_image4_monitor_trap_image_activate( image4_cs_trap_t selector, const void *input_data) { /* * csmx_payload (csmx_payload_len) --> __cs_xfer * csmx_manifest (csmx_manifest_len) --> __cs_borrow */ image4_cs_trap_argv(image_activate) input = {0}; vm_address_t payload_addr = 0; vm_address_t manifest_addr = 0; errno_t err = EPERM; /* Copy the input data */ memcpy(&input, input_data, sizeof(input)); payload_addr = code_signing_allocate(input.csmx_payload_len); if (payload_addr == 0) { goto out; } memcpy((void*)payload_addr, (void*)input.csmx_payload, input.csmx_payload_len); manifest_addr = code_signing_allocate(input.csmx_manifest_len); if (manifest_addr == 0) { goto out; } memcpy((void*)manifest_addr, (void*)input.csmx_manifest, input.csmx_manifest_len); /* Transfer both regions to the monitor */ CSM_PREFIX(image4_transfer_region)(selector, payload_addr, input.csmx_payload_len); CSM_PREFIX(image4_transfer_region)(selector, manifest_addr, input.csmx_manifest_len); /* Setup the input with new addresses */ input.csmx_payload = payload_addr; input.csmx_manifest = manifest_addr; /* Trap into the monitor for this selector */ err = CSM_PREFIX(image4_monitor_trap)(selector, &input, sizeof(input)); out: if ((err != 0) && (payload_addr != 0)) { /* Retyping only happens after allocating the manifest */ if (manifest_addr != 0) { CSM_PREFIX(image4_reclaim_region)( selector, payload_addr, input.csmx_payload_len); } code_signing_deallocate(&payload_addr, input.csmx_payload_len); } if (manifest_addr != 0) { /* Reclaim the manifest region -- will be retyped if not NULL */ CSM_PREFIX(image4_reclaim_region)( selector, manifest_addr, input.csmx_manifest_len); /* Deallocate the manifest region */ code_signing_deallocate(&manifest_addr, input.csmx_manifest_len); } return err; } static errno_t _kernel_image4_monitor_trap( image4_cs_trap_t selector, const void *input_data, size_t input_size) { /* Validate input size for the selector */ if (input_size != image4_cs_trap_vector_size(selector)) { printf("image4 dispatch: invalid input: %llu | %lu\n", selector, input_size); return EINVAL; } switch (selector) { case IMAGE4_CS_TRAP_IMAGE_ACTIVATE: return _kernel_image4_monitor_trap_image_activate(selector, input_data); default: return CSM_PREFIX(image4_monitor_trap)(selector, input_data, input_size); } } errno_t kernel_image4_monitor_trap( image4_cs_trap_t selector, const void *input_data, size_t input_size, __unused void *output_data, __unused size_t *output_size) { size_t length_check = 0; /* Input data is always required */ if ((input_data == NULL) || (input_size == 0)) { printf("image4 dispatch: no input data: %llu\n", selector); return EINVAL; } else if (os_add_overflow((vm_address_t)input_data, input_size, &length_check)) { panic("image4_ dispatch: overflow on input: %p | %lu", input_data, input_size); } return _kernel_image4_monitor_trap(selector, input_data, input_size); }