Skip to content
Permalink
e7776783b8
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
2059 lines (1740 sloc) 59.3 KB
/*
* Copyright (c) 2012-2020 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 <arm/caches_internal.h>
#include <arm/cpu_data.h>
#include <arm/cpu_data_internal.h>
#include <arm/misc_protos.h>
#include <arm/thread.h>
#include <arm/rtclock.h>
#include <arm/trap.h> /* for IS_ARM_GDB_TRAP() et al */
#include <arm64/proc_reg.h>
#include <arm64/machine_machdep.h>
#include <arm64/monotonic.h>
#include <arm64/instructions.h>
#include <kern/debug.h>
#include <kern/socd_client.h>
#include <kern/thread.h>
#include <mach/exception.h>
#include <mach/arm/traps.h>
#include <mach/vm_types.h>
#include <mach/machine/thread_status.h>
#include <machine/atomic.h>
#include <machine/limits.h>
#include <pexpert/arm/protos.h>
#include <vm/vm_page.h>
#include <vm/pmap.h>
#include <vm/vm_fault.h>
#include <vm/vm_kern.h>
#include <sys/errno.h>
#include <sys/kdebug.h>
#include <kperf/kperf.h>
#include <kern/policy_internal.h>
#if CONFIG_TELEMETRY
#include <kern/telemetry.h>
#endif
#include <prng/entropy.h>
#if CONFIG_KERNEL_TBI && KASAN_TBI
#include <san/kasan.h>
#endif /* CONFIG_KERNEL_TBI && KASAN_TBI */
#if CONFIG_UBSAN_MINIMAL
#include <san/ubsan_minimal.h>
#endif /* CONFIG_UBSAN_MINIMAL */
#ifndef __arm64__
#error Should only be compiling for arm64.
#endif
#define TEST_CONTEXT32_SANITY(context) \
(context->ss.ash.flavor == ARM_SAVED_STATE32 && context->ss.ash.count == ARM_SAVED_STATE32_COUNT && \
context->ns.nsh.flavor == ARM_NEON_SAVED_STATE32 && context->ns.nsh.count == ARM_NEON_SAVED_STATE32_COUNT)
#define TEST_CONTEXT64_SANITY(context) \
(context->ss.ash.flavor == ARM_SAVED_STATE64 && context->ss.ash.count == ARM_SAVED_STATE64_COUNT && \
context->ns.nsh.flavor == ARM_NEON_SAVED_STATE64 && context->ns.nsh.count == ARM_NEON_SAVED_STATE64_COUNT)
#define ASSERT_CONTEXT_SANITY(context) \
assert(TEST_CONTEXT32_SANITY(context) || TEST_CONTEXT64_SANITY(context))
#define COPYIN(src, dst, size) \
(PSR64_IS_KERNEL(get_saved_state_cpsr(state))) ? \
copyin_kern(src, dst, size) : \
copyin(src, dst, size)
#define COPYOUT(src, dst, size) \
(PSR64_IS_KERNEL(get_saved_state_cpsr(state))) ? \
copyout_kern(src, dst, size) : \
copyout(src, dst, size)
// Below is for concatenating a string param to a string literal
#define STR1(x) #x
#define STR(x) STR1(x)
#define ARM64_KDBG_CODE_KERNEL (0 << 8)
#define ARM64_KDBG_CODE_USER (1 << 8)
#define ARM64_KDBG_CODE_GUEST (2 << 8)
_Static_assert(ARM64_KDBG_CODE_GUEST <= KDBG_CODE_MAX, "arm64 KDBG trace codes out of range");
_Static_assert(ARM64_KDBG_CODE_GUEST <= UINT16_MAX, "arm64 KDBG trace codes out of range");
void panic_with_thread_kernel_state(const char *msg, arm_saved_state_t *ss) __abortlike;
void sleh_synchronous_sp1(arm_context_t *, uint32_t, vm_offset_t) __abortlike;
void sleh_synchronous(arm_context_t *, uint32_t, vm_offset_t);
void sleh_irq(arm_saved_state_t *);
void sleh_fiq(arm_saved_state_t *);
void sleh_serror(arm_context_t *context, uint32_t esr, vm_offset_t far);
void sleh_invalid_stack(arm_context_t *context, uint32_t esr, vm_offset_t far) __dead2;
static void sleh_interrupt_handler_prologue(arm_saved_state_t *, unsigned int type);
static void sleh_interrupt_handler_epilogue(void);
static void handle_svc(arm_saved_state_t *);
static void handle_mach_absolute_time_trap(arm_saved_state_t *);
static void handle_mach_continuous_time_trap(arm_saved_state_t *);
static void handle_msr_trap(arm_saved_state_t *state, uint32_t esr);
#ifdef __ARM_ARCH_8_6__
static void handle_pac_fail(arm_saved_state_t *state, uint32_t esr) __dead2;
#endif
extern kern_return_t arm_fast_fault(pmap_t, vm_map_address_t, vm_prot_t, bool, bool);
static void handle_uncategorized(arm_saved_state_t *);
/*
* For UBSan trap and continue handling, we must be able to recover
* from handle_kernel_breakpoint().
*/
#if !CONFIG_UBSAN_MINIMAL
__dead2
#endif /* CONFIG_UBSAN_MINIMAL */
static void handle_kernel_breakpoint(arm_saved_state_t *, uint32_t);
static void handle_breakpoint(arm_saved_state_t *, uint32_t) __dead2;
typedef void (*abort_inspector_t)(uint32_t, fault_status_t *, vm_prot_t *);
static void inspect_instruction_abort(uint32_t, fault_status_t *, vm_prot_t *);
static void inspect_data_abort(uint32_t, fault_status_t *, vm_prot_t *);
static int is_vm_fault(fault_status_t);
static int is_translation_fault(fault_status_t);
static int is_alignment_fault(fault_status_t);
typedef void (*abort_handler_t)(arm_saved_state_t *, uint32_t, vm_offset_t, fault_status_t, vm_prot_t, vm_offset_t, expected_fault_handler_t);
static void handle_user_abort(arm_saved_state_t *, uint32_t, vm_offset_t, fault_status_t, vm_prot_t, vm_offset_t, expected_fault_handler_t);
static void handle_kernel_abort(arm_saved_state_t *, uint32_t, vm_offset_t, fault_status_t, vm_prot_t, vm_offset_t, expected_fault_handler_t);
static void handle_pc_align(arm_saved_state_t *ss) __dead2;
static void handle_sp_align(arm_saved_state_t *ss) __dead2;
static void handle_sw_step_debug(arm_saved_state_t *ss) __dead2;
static void handle_wf_trap(arm_saved_state_t *ss) __dead2;
static void handle_fp_trap(arm_saved_state_t *ss, uint32_t esr) __dead2;
static void handle_watchpoint(vm_offset_t fault_addr) __dead2;
static void handle_abort(arm_saved_state_t *, uint32_t, vm_offset_t, vm_offset_t, abort_inspector_t, abort_handler_t, expected_fault_handler_t);
static void handle_user_trapped_instruction32(arm_saved_state_t *, uint32_t esr) __dead2;
static void handle_simd_trap(arm_saved_state_t *, uint32_t esr) __dead2;
extern void mach_kauth_cred_thread_update(void);
void mach_syscall_trace_exit(unsigned int retval, unsigned int call_number);
struct proc;
typedef uint32_t arm64_instr_t;
extern void
unix_syscall(struct arm_saved_state * regs, thread_t thread_act, struct proc * proc);
extern void
mach_syscall(struct arm_saved_state*);
#if CONFIG_DTRACE
extern kern_return_t dtrace_user_probe(arm_saved_state_t* regs);
extern boolean_t dtrace_tally_fault(user_addr_t);
/*
* Traps for userland processing. Can't include bsd/sys/fasttrap_isa.h, so copy
* and paste the trap instructions
* over from that file. Need to keep these in sync!
*/
#define FASTTRAP_ARM32_INSTR 0xe7ffdefc
#define FASTTRAP_THUMB32_INSTR 0xdefc
#define FASTTRAP_ARM64_INSTR 0xe7eeee7e
#define FASTTRAP_ARM32_RET_INSTR 0xe7ffdefb
#define FASTTRAP_THUMB32_RET_INSTR 0xdefb
#define FASTTRAP_ARM64_RET_INSTR 0xe7eeee7d
/* See <rdar://problem/4613924> */
perfCallback tempDTraceTrapHook = NULL; /* Pointer to DTrace fbt trap hook routine */
#endif
extern void arm64_thread_exception_return(void) __dead2;
#if defined(APPLETYPHOON)
#define CPU_NAME "Typhoon"
#elif defined(APPLETWISTER)
#define CPU_NAME "Twister"
#elif defined(APPLEHURRICANE)
#define CPU_NAME "Hurricane"
#elif defined(APPLELIGHTNING)
#define CPU_NAME "Lightning"
#else
#define CPU_NAME "Unknown"
#endif
#if (CONFIG_KERNEL_INTEGRITY && defined(KERNEL_INTEGRITY_WT))
#define ESR_WT_SERROR(esr) (((esr) & 0xffffff00) == 0xbf575400)
#define ESR_WT_REASON(esr) ((esr) & 0xff)
#define WT_REASON_NONE 0
#define WT_REASON_INTEGRITY_FAIL 1
#define WT_REASON_BAD_SYSCALL 2
#define WT_REASON_NOT_LOCKED 3
#define WT_REASON_ALREADY_LOCKED 4
#define WT_REASON_SW_REQ 5
#define WT_REASON_PT_INVALID 6
#define WT_REASON_PT_VIOLATION 7
#define WT_REASON_REG_VIOLATION 8
#endif
#if defined(HAS_IPI)
void cpu_signal_handler(void);
extern unsigned int gFastIPI;
#endif /* defined(HAS_IPI) */
static arm_saved_state64_t *original_faulting_state = NULL;
TUNABLE(bool, fp_exceptions_enabled, "-fp_exceptions", false);
extern vm_offset_t static_memory_end;
static inline int
is_vm_fault(fault_status_t status)
{
switch (status) {
case FSC_TRANSLATION_FAULT_L0:
case FSC_TRANSLATION_FAULT_L1:
case FSC_TRANSLATION_FAULT_L2:
case FSC_TRANSLATION_FAULT_L3:
case FSC_ACCESS_FLAG_FAULT_L1:
case FSC_ACCESS_FLAG_FAULT_L2:
case FSC_ACCESS_FLAG_FAULT_L3:
case FSC_PERMISSION_FAULT_L1:
case FSC_PERMISSION_FAULT_L2:
case FSC_PERMISSION_FAULT_L3:
return TRUE;
default:
return FALSE;
}
}
static inline int
is_translation_fault(fault_status_t status)
{
switch (status) {
case FSC_TRANSLATION_FAULT_L0:
case FSC_TRANSLATION_FAULT_L1:
case FSC_TRANSLATION_FAULT_L2:
case FSC_TRANSLATION_FAULT_L3:
return TRUE;
default:
return FALSE;
}
}
static inline int
is_permission_fault(fault_status_t status)
{
switch (status) {
case FSC_PERMISSION_FAULT_L1:
case FSC_PERMISSION_FAULT_L2:
case FSC_PERMISSION_FAULT_L3:
return TRUE;
default:
return FALSE;
}
}
static inline int
is_alignment_fault(fault_status_t status)
{
return status == FSC_ALIGNMENT_FAULT;
}
static inline int
is_parity_error(fault_status_t status)
{
switch (status) {
case FSC_SYNC_PARITY:
case FSC_ASYNC_PARITY:
case FSC_SYNC_PARITY_TT_L1:
case FSC_SYNC_PARITY_TT_L2:
case FSC_SYNC_PARITY_TT_L3:
return TRUE;
default:
return FALSE;
}
}
__dead2 __unused
static void
arm64_implementation_specific_error(arm_saved_state_t *state, uint32_t esr, vm_offset_t far)
{
#pragma unused (state, esr, far)
panic_plain("Unhandled implementation specific error\n");
}
#if CONFIG_KERNEL_INTEGRITY
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
static void
kernel_integrity_error_handler(uint32_t esr, vm_offset_t far)
{
#if defined(KERNEL_INTEGRITY_WT)
#if (DEVELOPMENT || DEBUG)
if (ESR_WT_SERROR(esr)) {
switch (ESR_WT_REASON(esr)) {
case WT_REASON_INTEGRITY_FAIL:
panic_plain("Kernel integrity, violation in frame 0x%016lx.", far);
case WT_REASON_BAD_SYSCALL:
panic_plain("Kernel integrity, bad syscall.");
case WT_REASON_NOT_LOCKED:
panic_plain("Kernel integrity, not locked.");
case WT_REASON_ALREADY_LOCKED:
panic_plain("Kernel integrity, already locked.");
case WT_REASON_SW_REQ:
panic_plain("Kernel integrity, software request.");
case WT_REASON_PT_INVALID:
panic_plain("Kernel integrity, encountered invalid TTE/PTE while "
"walking 0x%016lx.", far);
case WT_REASON_PT_VIOLATION:
panic_plain("Kernel integrity, violation in mapping 0x%016lx.",
far);
case WT_REASON_REG_VIOLATION:
panic_plain("Kernel integrity, violation in system register %d.",
(unsigned) far);
default:
panic_plain("Kernel integrity, unknown (esr=0x%08x).", esr);
}
}
#else
if (ESR_WT_SERROR(esr)) {
panic_plain("SError esr: 0x%08x far: 0x%016lx.", esr, far);
}
#endif
#endif
}
#pragma clang diagnostic pop
#endif
static void
arm64_platform_error(arm_saved_state_t *state, uint32_t esr, vm_offset_t far)
{
#if CONFIG_KERNEL_INTEGRITY
kernel_integrity_error_handler(esr, far);
#endif
cpu_data_t *cdp = getCpuDatap();
if (PE_handle_platform_error(far)) {
return;
} else if (cdp->platform_error_handler != NULL) {
cdp->platform_error_handler(cdp->cpu_id, far);
} else {
arm64_implementation_specific_error(state, esr, far);
}
}
void
panic_with_thread_kernel_state(const char *msg, arm_saved_state_t *ss)
{
boolean_t ss_valid;
ss_valid = is_saved_state64(ss);
arm_saved_state64_t *state = saved_state64(ss);
os_atomic_cmpxchg(&original_faulting_state, NULL, state, seq_cst);
// rdar://80659177
// Read SoCD tracepoints up to twice — once the first time we call panic and
// another time if we encounter a nested panic after that.
static int twice = 2;
if (twice > 0) {
twice--;
SOCD_TRACE_XNU(KERNEL_STATE_PANIC, ADDR(state->pc),
PACK_LSB(VALUE(state->lr), VALUE(ss_valid)),
PACK_2X32(VALUE(state->esr), VALUE(state->cpsr)),
VALUE(state->far));
}
panic_plain("%s at pc 0x%016llx, lr 0x%016llx (saved state: %p%s)\n"
"\t x0: 0x%016llx x1: 0x%016llx x2: 0x%016llx x3: 0x%016llx\n"
"\t x4: 0x%016llx x5: 0x%016llx x6: 0x%016llx x7: 0x%016llx\n"
"\t x8: 0x%016llx x9: 0x%016llx x10: 0x%016llx x11: 0x%016llx\n"
"\t x12: 0x%016llx x13: 0x%016llx x14: 0x%016llx x15: 0x%016llx\n"
"\t x16: 0x%016llx x17: 0x%016llx x18: 0x%016llx x19: 0x%016llx\n"
"\t x20: 0x%016llx x21: 0x%016llx x22: 0x%016llx x23: 0x%016llx\n"
"\t x24: 0x%016llx x25: 0x%016llx x26: 0x%016llx x27: 0x%016llx\n"
"\t x28: 0x%016llx fp: 0x%016llx lr: 0x%016llx sp: 0x%016llx\n"
"\t pc: 0x%016llx cpsr: 0x%08x esr: 0x%08x far: 0x%016llx\n",
msg, state->pc, state->lr, ss, (ss_valid ? "" : " INVALID"),
state->x[0], state->x[1], state->x[2], state->x[3],
state->x[4], state->x[5], state->x[6], state->x[7],
state->x[8], state->x[9], state->x[10], state->x[11],
state->x[12], state->x[13], state->x[14], state->x[15],
state->x[16], state->x[17], state->x[18], state->x[19],
state->x[20], state->x[21], state->x[22], state->x[23],
state->x[24], state->x[25], state->x[26], state->x[27],
state->x[28], state->fp, state->lr, state->sp,
state->pc, state->cpsr, state->esr, state->far);
}
void
sleh_synchronous_sp1(arm_context_t *context, uint32_t esr, vm_offset_t far __unused)
{
esr_exception_class_t class = ESR_EC(esr);
arm_saved_state_t * state = &context->ss;
switch (class) {
case ESR_EC_UNCATEGORIZED:
{
uint32_t instr = *((uint32_t*)get_saved_state_pc(state));
if (IS_ARM_GDB_TRAP(instr)) {
DebuggerCall(EXC_BREAKPOINT, state);
}
}
OS_FALLTHROUGH; // panic if we return from the debugger
default:
panic_with_thread_kernel_state("Synchronous exception taken while SP1 selected", state);
}
}
__attribute__((noreturn))
void
thread_exception_return()
{
thread_t thread = current_thread();
if (thread->machine.exception_trace_code != 0) {
KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
MACHDBG_CODE(DBG_MACH_EXCP_SYNC_ARM, thread->machine.exception_trace_code) | DBG_FUNC_END, 0, 0, 0, 0, 0);
thread->machine.exception_trace_code = 0;
}
arm64_thread_exception_return();
__builtin_unreachable();
}
/*
* check whether task vtimers are running and set thread and CPU BSD AST
*
* must be called with interrupts masked so updates of fields are atomic
* must be emitted inline to avoid generating an FBT probe on the exception path
*
*/
__attribute__((__always_inline__))
static inline void
task_vtimer_check(thread_t thread)
{
task_t task = get_threadtask_early(thread);
if (__improbable(task != NULL && task->vtimers)) {
thread->ast |= AST_BSD;
thread->machine.CpuDatap->cpu_pending_ast |= AST_BSD;
}
}
#if MACH_ASSERT
/**
* A version of get_preemption_level() that works in early boot.
*
* If an exception is raised in early boot before the initial thread has been
* set up, then calling get_preemption_level() in the SLEH will trigger an
* infinitely-recursing exception. This function handles this edge case.
*/
static inline int
sleh_get_preemption_level(void)
{
if (__improbable(current_thread() == NULL)) {
return 0;
}
return get_preemption_level();
}
#endif // MACH_ASSERT
void
sleh_synchronous(arm_context_t *context, uint32_t esr, vm_offset_t far)
{
esr_exception_class_t class = ESR_EC(esr);
arm_saved_state_t * state = &context->ss;
vm_offset_t recover = 0;
thread_t thread = current_thread();
#if MACH_ASSERT
int preemption_level = sleh_get_preemption_level();
#endif
expected_fault_handler_t expected_fault_handler = NULL;
#ifdef CONFIG_XNUPOST
expected_fault_handler_t saved_expected_fault_handler = NULL;
uintptr_t saved_expected_fault_addr = 0;
#endif /* CONFIG_XNUPOST */
ASSERT_CONTEXT_SANITY(context);
task_vtimer_check(thread);
#if CONFIG_DTRACE
/*
* Handle kernel DTrace probes as early as possible to minimize the likelihood
* that this path will itself trigger a DTrace probe, which would lead to infinite
* probe recursion.
*/
if (__improbable((class == ESR_EC_UNCATEGORIZED) && tempDTraceTrapHook &&
(tempDTraceTrapHook(EXC_BAD_INSTRUCTION, state, 0, 0) == KERN_SUCCESS))) {
return;
}
#endif
bool is_user = PSR64_IS_USER(get_saved_state_cpsr(state));
/*
* Use KERNEL_DEBUG_CONSTANT_IST here to avoid producing tracepoints
* that would disclose the behavior of PT_DENY_ATTACH processes.
*/
if (is_user) {
thread->machine.exception_trace_code = (uint16_t)(ARM64_KDBG_CODE_USER | class);
KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
MACHDBG_CODE(DBG_MACH_EXCP_SYNC_ARM, thread->machine.exception_trace_code) | DBG_FUNC_START,
esr, far, get_saved_state_pc(state), 0, 0);
} else {
KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
MACHDBG_CODE(DBG_MACH_EXCP_SYNC_ARM, ARM64_KDBG_CODE_KERNEL | class) | DBG_FUNC_START,
esr, VM_KERNEL_ADDRHIDE(far), VM_KERNEL_UNSLIDE(get_saved_state_pc(state)), 0, 0);
}
if (__improbable(ESR_INSTR_IS_2BYTES(esr))) {
/*
* We no longer support 32-bit, which means no 2-byte
* instructions.
*/
if (is_user) {
panic("Exception on 2-byte instruction, "
"context=%p, esr=%#x, far=%p",
context, esr, (void *)far);
} else {
panic_with_thread_kernel_state("Exception on 2-byte instruction", state);
}
}
/* Don't run exception handler with recover handler set in case of double fault */
if (thread->recover) {
recover = thread->recover;
thread->recover = (vm_offset_t)NULL;
}
#ifdef CONFIG_XNUPOST
if (thread->machine.expected_fault_handler != NULL) {
saved_expected_fault_handler = thread->machine.expected_fault_handler;
saved_expected_fault_addr = thread->machine.expected_fault_addr;
thread->machine.expected_fault_handler = NULL;
thread->machine.expected_fault_addr = 0;
if (saved_expected_fault_addr == far) {
expected_fault_handler = saved_expected_fault_handler;
}
}
#endif /* CONFIG_XNUPOST */
/* Inherit the interrupt masks from previous context */
if (SPSR_INTERRUPTS_ENABLED(get_saved_state_cpsr(state))) {
ml_set_interrupts_enabled(TRUE);
}
switch (class) {
case ESR_EC_SVC_64:
if (!is_saved_state64(state) || !is_user) {
panic("Invalid SVC_64 context");
}
handle_svc(state);
break;
case ESR_EC_DABORT_EL0:
handle_abort(state, esr, far, recover, inspect_data_abort, handle_user_abort, expected_fault_handler);
break;
case ESR_EC_MSR_TRAP:
handle_msr_trap(state, esr);
break;
#ifdef __ARM_ARCH_8_6__
case ESR_EC_PAC_FAIL:
handle_pac_fail(state, esr);
__builtin_unreachable();
#endif /* __ARM_ARCH_8_6__ */
case ESR_EC_IABORT_EL0:
handle_abort(state, esr, far, recover, inspect_instruction_abort, handle_user_abort, expected_fault_handler);
break;
case ESR_EC_IABORT_EL1:
#ifdef CONFIG_XNUPOST
if ((expected_fault_handler != NULL) && expected_fault_handler(state)) {
break;
}
#endif /* CONFIG_XNUPOST */
panic_with_thread_kernel_state("Kernel instruction fetch abort", state);
case ESR_EC_PC_ALIGN:
handle_pc_align(state);
__builtin_unreachable();
case ESR_EC_DABORT_EL1:
handle_abort(state, esr, far, recover, inspect_data_abort, handle_kernel_abort, expected_fault_handler);
break;
case ESR_EC_UNCATEGORIZED:
assert(!ESR_ISS(esr));
handle_uncategorized(&context->ss);
break;
case ESR_EC_SP_ALIGN:
handle_sp_align(state);
__builtin_unreachable();
case ESR_EC_BKPT_AARCH32:
handle_breakpoint(state, esr);
__builtin_unreachable();
case ESR_EC_BRK_AARCH64:
if (PSR64_IS_KERNEL(get_saved_state_cpsr(state))) {
handle_kernel_breakpoint(state, esr);
#if CONFIG_UBSAN_MINIMAL
/* UBSan breakpoints are recoverable */
break;
#endif /* CONFIG_UBSAN_MINIMAL */
} else {
handle_breakpoint(state, esr);
__builtin_unreachable();
}
case ESR_EC_BKPT_REG_MATCH_EL0:
if (FSC_DEBUG_FAULT == ISS_SSDE_FSC(esr)) {
handle_breakpoint(state, esr);
}
panic("Unsupported Class %u event code. state=%p class=%u esr=%u far=%p",
class, state, class, esr, (void *)far);
__builtin_unreachable();
case ESR_EC_BKPT_REG_MATCH_EL1:
panic_with_thread_kernel_state("Hardware Breakpoint Debug exception from kernel. Panic (by design)", state);
__builtin_unreachable();
case ESR_EC_SW_STEP_DEBUG_EL0:
if (FSC_DEBUG_FAULT == ISS_SSDE_FSC(esr)) {
handle_sw_step_debug(state);
}
panic("Unsupported Class %u event code. state=%p class=%u esr=%u far=%p",
class, state, class, esr, (void *)far);
__builtin_unreachable();
case ESR_EC_SW_STEP_DEBUG_EL1:
panic_with_thread_kernel_state("Software Step Debug exception from kernel. Panic (by design)", state);
__builtin_unreachable();
case ESR_EC_WATCHPT_MATCH_EL0:
if (FSC_DEBUG_FAULT == ISS_SSDE_FSC(esr)) {
handle_watchpoint(far);
}
panic("Unsupported Class %u event code. state=%p class=%u esr=%u far=%p",
class, state, class, esr, (void *)far);
__builtin_unreachable();
case ESR_EC_WATCHPT_MATCH_EL1:
/*
* If we hit a watchpoint in kernel mode, probably in a copyin/copyout which we don't want to
* abort. Turn off watchpoints and keep going; we'll turn them back on in return_from_exception..
*/
if (FSC_DEBUG_FAULT == ISS_SSDE_FSC(esr)) {
arm_debug_set(NULL);
break; /* return to first level handler */
}
panic("Unsupported Class %u event code. state=%p class=%u esr=%u far=%p",
class, state, class, esr, (void *)far);
__builtin_unreachable();
case ESR_EC_TRAP_SIMD_FP:
handle_simd_trap(state, esr);
__builtin_unreachable();
case ESR_EC_ILLEGAL_INSTR_SET:
if (EXCB_ACTION_RERUN !=
ex_cb_invoke(EXCB_CLASS_ILLEGAL_INSTR_SET, far)) {
// instruction is not re-executed
panic("Illegal instruction set exception. state=%p class=%u esr=%u far=%p spsr=0x%x",
state, class, esr, (void *)far, get_saved_state_cpsr(state));
}
// must clear this fault in PSR to re-run
mask_saved_state_cpsr(state, 0, PSR64_IL);
break;
case ESR_EC_MCR_MRC_CP15_TRAP:
case ESR_EC_MCRR_MRRC_CP15_TRAP:
case ESR_EC_MCR_MRC_CP14_TRAP:
case ESR_EC_LDC_STC_CP14_TRAP:
case ESR_EC_MCRR_MRRC_CP14_TRAP:
handle_user_trapped_instruction32(state, esr);
__builtin_unreachable();
case ESR_EC_WFI_WFE:
// Use of WFI or WFE instruction when they have been disabled for EL0
handle_wf_trap(state);
__builtin_unreachable();
case ESR_EC_FLOATING_POINT_64:
handle_fp_trap(state, esr);
__builtin_unreachable();
default:
handle_uncategorized(state);
}
#ifdef CONFIG_XNUPOST
if (saved_expected_fault_handler != NULL) {
thread->machine.expected_fault_handler = saved_expected_fault_handler;
thread->machine.expected_fault_addr = saved_expected_fault_addr;
}
#endif /* CONFIG_XNUPOST */
if (recover) {
thread->recover = recover;
}
if (is_user) {
KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
MACHDBG_CODE(DBG_MACH_EXCP_SYNC_ARM, thread->machine.exception_trace_code) | DBG_FUNC_END,
esr, far, get_saved_state_pc(state), 0, 0);
thread->machine.exception_trace_code = 0;
} else {
KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
MACHDBG_CODE(DBG_MACH_EXCP_SYNC_ARM, ARM64_KDBG_CODE_KERNEL | class) | DBG_FUNC_END,
esr, VM_KERNEL_ADDRHIDE(far), VM_KERNEL_UNSLIDE(get_saved_state_pc(state)), 0, 0);
}
#if MACH_ASSERT
if (preemption_level != sleh_get_preemption_level()) {
panic("synchronous exception changed preemption level from %d to %d", preemption_level, sleh_get_preemption_level());
}
#endif
}
/*
* Uncategorized exceptions are a catch-all for general execution errors.
* ARM64_TODO: For now, we assume this is for undefined instruction exceptions.
*/
static void
handle_uncategorized(arm_saved_state_t *state)
{
exception_type_t exception = EXC_BAD_INSTRUCTION;
mach_exception_data_type_t codes[2] = {EXC_ARM_UNDEFINED};
mach_msg_type_number_t numcodes = 2;
uint32_t instr = 0;
COPYIN(get_saved_state_pc(state), (char *)&instr, sizeof(instr));
#if CONFIG_DTRACE
if (PSR64_IS_USER64(get_saved_state_cpsr(state))) {
/*
* For a 64bit user process, we care about all 4 bytes of the
* instr.
*/
if (instr == FASTTRAP_ARM64_INSTR || instr == FASTTRAP_ARM64_RET_INSTR) {
if (dtrace_user_probe(state) == KERN_SUCCESS) {
return;
}
}
} else if (PSR64_IS_USER32(get_saved_state_cpsr(state))) {
/*
* For a 32bit user process, we check for thumb mode, in
* which case we only care about a 2 byte instruction length.
* For non-thumb mode, we care about all 4 bytes of the instructin.
*/
if (get_saved_state_cpsr(state) & PSR64_MODE_USER32_THUMB) {
if (((uint16_t)instr == FASTTRAP_THUMB32_INSTR) ||
((uint16_t)instr == FASTTRAP_THUMB32_RET_INSTR)) {
if (dtrace_user_probe(state) == KERN_SUCCESS) {
return;
}
}
} else {
if ((instr == FASTTRAP_ARM32_INSTR) ||
(instr == FASTTRAP_ARM32_RET_INSTR)) {
if (dtrace_user_probe(state) == KERN_SUCCESS) {
return;
}
}
}
}
#endif /* CONFIG_DTRACE */
if (PSR64_IS_KERNEL(get_saved_state_cpsr(state))) {
if (IS_ARM_GDB_TRAP(instr)) {
boolean_t interrupt_state;
exception = EXC_BREAKPOINT;
interrupt_state = ml_set_interrupts_enabled(FALSE);
/* Save off the context here (so that the debug logic
* can see the original state of this thread).
*/
current_thread()->machine.kpcb = state;
/* Hop into the debugger (typically either due to a
* fatal exception, an explicit panic, or a stackshot
* request.
*/
DebuggerCall(exception, state);
current_thread()->machine.kpcb = NULL;
(void) ml_set_interrupts_enabled(interrupt_state);
return;
} else {
panic("Undefined kernel instruction: pc=%p instr=%x", (void*)get_saved_state_pc(state), instr);
}
}
/*
* Check for GDB breakpoint via illegal opcode.
*/
if (IS_ARM_GDB_TRAP(instr)) {
exception = EXC_BREAKPOINT;
codes[0] = EXC_ARM_BREAKPOINT;
codes[1] = instr;
} else {
codes[1] = instr;
}
exception_triage(exception, codes, numcodes);
__builtin_unreachable();
}
#if __has_feature(ptrauth_calls)
static const uint16_t ptrauth_brk_comment_base = 0xc470;
static inline bool
brk_comment_is_ptrauth(uint16_t comment)
{
return comment >= ptrauth_brk_comment_base &&
comment <= ptrauth_brk_comment_base + ptrauth_key_asdb;
}
static inline const char *
ptrauth_key_to_string(ptrauth_key key)
{
switch (key) {
case ptrauth_key_asia:
return "IA";
case ptrauth_key_asib:
return "IB";
case ptrauth_key_asda:
return "DA";
case ptrauth_key_asdb:
return "DB";
default:
__builtin_unreachable();
}
}
#endif /* __has_feature(ptrauth_calls) */
#if CONFIG_KERNEL_TBI && KASAN_TBI
static inline bool
brk_comment_is_kasan_failure(uint16_t comment)
{
return comment >= KASAN_TBI_ESR_BASE &&
comment <= KASAN_TBI_ESR_TOP;
}
#endif /* CONFIG_KERNEL_TBI && KASAN_TBI */
#if CONFIG_UBSAN_MINIMAL
static inline bool
brk_comment_is_ubsan(uint16_t comment)
{
return comment >= UBSAN_MINIMAL_TRAPS_START &&
comment < UBSAN_MINIMAL_TRAPS_END;
}
#endif /* CONFIG_UBSAN_MINIMAL */
static void
handle_kernel_breakpoint(arm_saved_state_t *state, uint32_t esr)
{
uint16_t comment = ISS_BRK_COMMENT(esr);
#if __has_feature(ptrauth_calls)
if (brk_comment_is_ptrauth(comment)) {
#define MSG_FMT "Break 0x%04X instruction exception from kernel. Ptrauth failure with %s key resulted in 0x%016llx"
char msg[strlen(MSG_FMT)
- strlen("0x%04X") + strlen("0xFFFF")
- strlen("%s") + strlen("IA")
- strlen("0x%016llx") + strlen("0xFFFFFFFFFFFFFFFF")
+ 1];
ptrauth_key key = (ptrauth_key)(comment - ptrauth_brk_comment_base);
const char *key_str = ptrauth_key_to_string(key);
snprintf(msg, sizeof(msg), MSG_FMT, comment, key_str, saved_state64(state)->x[16]);
panic_with_thread_kernel_state(msg, state);
__builtin_unreachable();
#undef MSG_FMT
}
#endif /* __has_feature(ptrauth_calls) */
#if CONFIG_KERNEL_TBI && KASAN_TBI
if (brk_comment_is_kasan_failure(comment)) {
kasan_handle_brk_failure(saved_state64(state)->x[0], comment);
__builtin_unreachable();
}
#endif /* CONFIG_KERNEL_TBI && KASAN_TBI */
#if CONFIG_UBSAN_MINIMAL
if (brk_comment_is_ubsan(comment)) {
ubsan_handle_brk_trap(comment, get_saved_state_pc(state),
get_saved_state_fp(state));
add_saved_state_pc(state, 4);
return;
}
#endif /* CONFIG_UBSAN_MINIMAL */
#define MSG_FMT "Break 0x%04X instruction exception from kernel. Panic (by design)"
char msg[strlen(MSG_FMT) - strlen("0x%04X") + strlen("0xFFFF") + 1];
snprintf(msg, sizeof(msg), MSG_FMT, comment);
#undef MSG_FMT
panic_with_thread_kernel_state(msg, state);
__builtin_unreachable();
}
static void
handle_breakpoint(arm_saved_state_t *state, uint32_t esr __unused)
{
exception_type_t exception = EXC_BREAKPOINT;
mach_exception_data_type_t codes[2] = {EXC_ARM_BREAKPOINT};
mach_msg_type_number_t numcodes = 2;
#if __has_feature(ptrauth_calls) && !__ARM_ARCH_8_6__
if (ESR_EC(esr) == ESR_EC_BRK_AARCH64 &&
brk_comment_is_ptrauth(ISS_BRK_COMMENT(esr))) {
exception |= EXC_PTRAUTH_BIT;
}
#endif /* __has_feature(ptrauth_calls) && !__ARM_ARCH_8_6__ */
codes[1] = get_saved_state_pc(state);
exception_triage(exception, codes, numcodes);
__builtin_unreachable();
}
static void
handle_watchpoint(vm_offset_t fault_addr)
{
exception_type_t exception = EXC_BREAKPOINT;
mach_exception_data_type_t codes[2] = {EXC_ARM_DA_DEBUG};
mach_msg_type_number_t numcodes = 2;
codes[1] = fault_addr;
exception_triage(exception, codes, numcodes);
__builtin_unreachable();
}
static void
handle_abort(arm_saved_state_t *state, uint32_t esr, vm_offset_t fault_addr, vm_offset_t recover,
abort_inspector_t inspect_abort, abort_handler_t handler, expected_fault_handler_t expected_fault_handler)
{
fault_status_t fault_code;
vm_prot_t fault_type;
inspect_abort(ESR_ISS(esr), &fault_code, &fault_type);
handler(state, esr, fault_addr, fault_code, fault_type, recover, expected_fault_handler);
}
static void
inspect_instruction_abort(uint32_t iss, fault_status_t *fault_code, vm_prot_t *fault_type)
{
getCpuDatap()->cpu_stat.instr_ex_cnt++;
*fault_code = ISS_IA_FSC(iss);
*fault_type = (VM_PROT_READ | VM_PROT_EXECUTE);
}
static void
inspect_data_abort(uint32_t iss, fault_status_t *fault_code, vm_prot_t *fault_type)
{
getCpuDatap()->cpu_stat.data_ex_cnt++;
*fault_code = ISS_DA_FSC(iss);
/*
* Cache maintenance operations always report faults as write access.
* Change these to read access, unless they report a permission fault.
* Only certain cache maintenance operations (e.g. 'dc ivac') require write
* access to the mapping, but if a cache maintenance operation that only requires
* read access generates a permission fault, then we will not be able to handle
* the fault regardless of whether we treat it as a read or write fault.
*/
if ((iss & ISS_DA_WNR) && (!(iss & ISS_DA_CM) || is_permission_fault(*fault_code))) {
*fault_type = (VM_PROT_READ | VM_PROT_WRITE);
} else {
*fault_type = (VM_PROT_READ);
}
}
#if __has_feature(ptrauth_calls)
#ifdef __ARM_ARCH_8_6__
static inline uint64_t
fault_addr_bitmask(unsigned int bit_from, unsigned int bit_to)
{
return ((1ULL << (bit_to - bit_from + 1)) - 1) << bit_from;
}
#else
static inline bool
fault_addr_bit(vm_offset_t fault_addr, unsigned int bit)
{
return (bool)((fault_addr >> bit) & 1);
}
#endif /* __ARM_ARCH_8_6__ */
/**
* Determines whether a fault address taken at EL0 contains a PAC error code
* corresponding to the specified kind of ptrauth key.
*/
static bool
user_fault_addr_matches_pac_error_code(vm_offset_t fault_addr, bool data_key)
{
bool instruction_tbi = !(get_tcr() & TCR_TBID0_TBI_DATA_ONLY);
bool tbi = data_key || __improbable(instruction_tbi);
#ifdef __ARM_ARCH_8_6__
/*
* EnhancedPAC2 CPUs don't encode error codes at fixed positions, so
* treat all non-canonical address bits like potential poison bits.
*/
uint64_t mask = fault_addr_bitmask(T0SZ_BOOT, 54);
if (!tbi) {
mask |= fault_addr_bitmask(56, 63);
}
return (fault_addr & mask) != 0;
#else /* !__ARM_ARCH_8_6__ */
unsigned int poison_shift;
if (tbi) {
poison_shift = 53;
} else {
poison_shift = 61;
}
/* PAC error codes are always in the form key_number:NOT(key_number) */
bool poison_bit_1 = fault_addr_bit(fault_addr, poison_shift);
bool poison_bit_2 = fault_addr_bit(fault_addr, poison_shift + 1);
return poison_bit_1 != poison_bit_2;
#endif /* __ARM_ARCH_8_6__ */
}
#endif /* __has_feature(ptrauth_calls) */
static void
handle_pc_align(arm_saved_state_t *ss)
{
exception_type_t exc;
mach_exception_data_type_t codes[2];
mach_msg_type_number_t numcodes = 2;
if (!PSR64_IS_USER(get_saved_state_cpsr(ss))) {
panic_with_thread_kernel_state("PC alignment exception from kernel.", ss);
}
exc = EXC_BAD_ACCESS;
#if __has_feature(ptrauth_calls)
if (user_fault_addr_matches_pac_error_code(get_saved_state_pc(ss), false)) {
exc |= EXC_PTRAUTH_BIT;
}
#endif /* __has_feature(ptrauth_calls) */
codes[0] = EXC_ARM_DA_ALIGN;
codes[1] = get_saved_state_pc(ss);
exception_triage(exc, codes, numcodes);
__builtin_unreachable();
}
static void
handle_sp_align(arm_saved_state_t *ss)
{
exception_type_t exc;
mach_exception_data_type_t codes[2];
mach_msg_type_number_t numcodes = 2;
if (!PSR64_IS_USER(get_saved_state_cpsr(ss))) {
panic_with_thread_kernel_state("SP alignment exception from kernel.", ss);
}
exc = EXC_BAD_ACCESS;
#if __has_feature(ptrauth_calls)
if (user_fault_addr_matches_pac_error_code(get_saved_state_sp(ss), true)) {
exc |= EXC_PTRAUTH_BIT;
}
#endif /* __has_feature(ptrauth_calls) */
codes[0] = EXC_ARM_SP_ALIGN;
codes[1] = get_saved_state_sp(ss);
exception_triage(exc, codes, numcodes);
__builtin_unreachable();
}
static void
handle_wf_trap(arm_saved_state_t *state)
{
exception_type_t exc;
mach_exception_data_type_t codes[2];
mach_msg_type_number_t numcodes = 2;
uint32_t instr = 0;
COPYIN(get_saved_state_pc(state), (char *)&instr, sizeof(instr));
exc = EXC_BAD_INSTRUCTION;
codes[0] = EXC_ARM_UNDEFINED;
codes[1] = instr;
exception_triage(exc, codes, numcodes);
__builtin_unreachable();
}
static void
handle_fp_trap(arm_saved_state_t *state, uint32_t esr)
{
exception_type_t exc = EXC_ARITHMETIC;
mach_exception_data_type_t codes[2];
mach_msg_type_number_t numcodes = 2;
uint32_t instr = 0;
if (PSR64_IS_KERNEL(get_saved_state_cpsr(state))) {
panic_with_thread_kernel_state("Floating point exception from kernel", state);
}
COPYIN(get_saved_state_pc(state), (char *)&instr, sizeof(instr));
codes[1] = instr;
/* The floating point trap flags are only valid if TFV is set. */
if (!fp_exceptions_enabled) {
exc = EXC_BAD_INSTRUCTION;
codes[0] = EXC_ARM_UNDEFINED;
} else if (!(esr & ISS_FP_TFV)) {
codes[0] = EXC_ARM_FP_UNDEFINED;
} else if (esr & ISS_FP_UFF) {
codes[0] = EXC_ARM_FP_UF;
} else if (esr & ISS_FP_OFF) {
codes[0] = EXC_ARM_FP_OF;
} else if (esr & ISS_FP_IOF) {
codes[0] = EXC_ARM_FP_IO;
} else if (esr & ISS_FP_DZF) {
codes[0] = EXC_ARM_FP_DZ;
} else if (esr & ISS_FP_IDF) {
codes[0] = EXC_ARM_FP_ID;
} else if (esr & ISS_FP_IXF) {
codes[0] = EXC_ARM_FP_IX;
} else {
panic("Unrecognized floating point exception, state=%p, esr=%#x", state, esr);
}
exception_triage(exc, codes, numcodes);
__builtin_unreachable();
}
/*
* handle_alignment_fault_from_user:
* state: Saved state
*
* Attempts to deal with an alignment fault from userspace (possibly by
* emulating the faulting instruction). If emulation failed due to an
* unservicable fault, the ESR for that fault will be stored in the
* recovery_esr field of the thread by the exception code.
*
* Returns:
* -1: Emulation failed (emulation of state/instr not supported)
* 0: Successfully emulated the instruction
* EFAULT: Emulation failed (probably due to permissions)
* EINVAL: Emulation failed (probably due to a bad address)
*/
static int
handle_alignment_fault_from_user(arm_saved_state_t *state, kern_return_t *vmfr)
{
int ret = -1;
#pragma unused (state)
#pragma unused (vmfr)
return ret;
}
static void
handle_sw_step_debug(arm_saved_state_t *state)
{
thread_t thread = current_thread();
exception_type_t exc;
mach_exception_data_type_t codes[2];
mach_msg_type_number_t numcodes = 2;
if (!PSR64_IS_USER(get_saved_state_cpsr(state))) {
panic_with_thread_kernel_state("SW_STEP_DEBUG exception from kernel.", state);
}
// Disable single step and unmask interrupts (in the saved state, anticipating next exception return)
if (thread->machine.DebugData != NULL) {
thread->machine.DebugData->uds.ds64.mdscr_el1 &= ~0x1;
} else {
panic_with_thread_kernel_state("SW_STEP_DEBUG exception thread DebugData is NULL.", state);
}
mask_saved_state_cpsr(thread->machine.upcb, 0, PSR64_SS | DAIF_ALL);
// Special encoding for gdb single step event on ARM
exc = EXC_BREAKPOINT;
codes[0] = 1;
codes[1] = 0;
exception_triage(exc, codes, numcodes);
__builtin_unreachable();
}
static void
set_saved_state_pc_to_recovery_handler(arm_saved_state_t *iss, vm_offset_t recover)
{
#if defined(HAS_APPLE_PAC)
thread_t thread = current_thread();
const uintptr_t disc = ptrauth_blend_discriminator(&thread->recover, PAC_DISCRIMINATOR_RECOVER);
const char *panic_msg = "Illegal thread->recover value %p";
MANIPULATE_SIGNED_THREAD_STATE(iss,
// recover = (vm_offset_t)ptrauth_auth_data((void *)recover, ptrauth_key_function_pointer,
// ptrauth_blend_discriminator(&thread->recover, PAC_DISCRIMINATOR_RECOVER));
"mov x1, %[recover] \n"
"mov x6, %[disc] \n"
"autia x1, x6 \n"
#if !__ARM_ARCH_8_6__
// if (recover != (vm_offset_t)ptrauth_strip((void *)recover, ptrauth_key_function_pointer)) {
"mov x6, x1 \n"
"xpaci x6 \n"
"cmp x1, x6 \n"
"beq 1f \n"
// panic("Illegal thread->recover value %p", (void *)recover);
"mov x0, %[panic_msg] \n"
"bl _panic \n"
// }
"1: \n"
#endif /* !__ARM_ARCH_8_6__ */
"str x1, [x0, %[SS64_PC]] \n",
[recover] "r"(recover),
[disc] "r"(disc),
[panic_msg] "r"(panic_msg)
);
#else
set_saved_state_pc(iss, recover);
#endif
}
static void
handle_user_abort(arm_saved_state_t *state, uint32_t esr, vm_offset_t fault_addr,
fault_status_t fault_code, vm_prot_t fault_type, vm_offset_t recover, expected_fault_handler_t expected_fault_handler)
{
exception_type_t exc = EXC_BAD_ACCESS;
mach_exception_data_type_t codes[2];
mach_msg_type_number_t numcodes = 2;
thread_t thread = current_thread();
(void)esr;
(void)expected_fault_handler;
if (ml_at_interrupt_context()) {
panic_with_thread_kernel_state("Apparently on interrupt stack when taking user abort!\n", state);
}
thread->iotier_override = THROTTLE_LEVEL_NONE; /* Reset IO tier override before handling abort from userspace */
if (is_vm_fault(fault_code)) {
vm_map_t map = thread->map;
vm_offset_t vm_fault_addr = fault_addr;
kern_return_t result = KERN_FAILURE;
assert(map != kernel_map);
if (!(fault_type & VM_PROT_EXECUTE)) {
vm_fault_addr = tbi_clear(fault_addr);
}
#if CONFIG_DTRACE
if (thread->t_dtrace_inprobe) { /* Executing under dtrace_probe? */
if (dtrace_tally_fault(vm_fault_addr)) { /* Should a user mode fault under dtrace be ignored? */
if (recover) {
thread->machine.recover_esr = esr;
thread->machine.recover_far = vm_fault_addr;
set_saved_state_pc_to_recovery_handler(state, recover);
} else {
panic_with_thread_kernel_state("copyin/out has no recovery point", state);
}
return;
} else {
panic_with_thread_kernel_state("Unexpected UMW page fault under dtrace_probe", state);
}
}
#else
(void)recover;
#endif
/* check to see if it is just a pmap ref/modify fault */
if (!is_translation_fault(fault_code)) {
result = arm_fast_fault(map->pmap,
vm_fault_addr,
fault_type, (fault_code == FSC_ACCESS_FLAG_FAULT_L3), TRUE);
}
if (result == KERN_SUCCESS) {
return;
}
{
/* We have to fault the page in */
result = vm_fault(map, vm_fault_addr, fault_type,
/* change_wiring */ FALSE, VM_KERN_MEMORY_NONE, THREAD_ABORTSAFE,
/* caller_pmap */ NULL, /* caller_pmap_addr */ 0);
}
if (result == KERN_SUCCESS || result == KERN_ABORTED) {
return;
}
/*
* vm_fault() should never return KERN_FAILURE for page faults from user space.
* If it does, we're leaking preemption disables somewhere in the kernel.
*/
if (__improbable(result == KERN_FAILURE)) {
panic("vm_fault() KERN_FAILURE from user fault on thread %p", thread);
}
codes[0] = result;
} else if (is_alignment_fault(fault_code)) {
kern_return_t vmfkr = KERN_SUCCESS;
thread->machine.recover_esr = 0;
thread->machine.recover_far = 0;
int result = handle_alignment_fault_from_user(state, &vmfkr);
if (result == 0) {
/* Successfully emulated, or instruction
* copyin() for decode/emulation failed.
* Continue, or redrive instruction.
*/
thread_exception_return();
} else if (((result == EFAULT) || (result == EINVAL)) &&
(thread->machine.recover_esr == 0)) {
/*
* If we didn't actually take a fault, but got one of
* these errors, then we failed basic sanity checks of
* the fault address. Treat this as an invalid
* address.
*/
codes[0] = KERN_INVALID_ADDRESS;
} else if ((result == EFAULT) &&
(thread->machine.recover_esr)) {
/*
* Since alignment aborts are prioritized
* ahead of translation aborts, the misaligned
* atomic emulation flow may have triggered a
* VM pagefault, which the VM could not resolve.
* Report the VM fault error in codes[]
*/
codes[0] = vmfkr;
assertf(vmfkr != KERN_SUCCESS, "Unexpected vmfkr 0x%x", vmfkr);
/* Cause ESR_EC to reflect an EL0 abort */
thread->machine.recover_esr &= ~ESR_EC_MASK;
thread->machine.recover_esr |= (ESR_EC_DABORT_EL0 << ESR_EC_SHIFT);
set_saved_state_esr(thread->machine.upcb, thread->machine.recover_esr);
set_saved_state_far(thread->machine.upcb, thread->machine.recover_far);
fault_addr = thread->machine.recover_far;
} else {
/* This was just an unsupported alignment
* exception. Misaligned atomic emulation
* timeouts fall in this category.
*/
codes[0] = EXC_ARM_DA_ALIGN;
}
} else if (is_parity_error(fault_code)) {
#if defined(APPLE_ARM64_ARCH_FAMILY)
if (fault_code == FSC_SYNC_PARITY) {
arm64_platform_error(state, esr, fault_addr);
return;
}
#else
panic("User parity error.");
#endif
} else {
codes[0] = KERN_FAILURE;
}
codes[1] = fault_addr;
#if __has_feature(ptrauth_calls)
bool is_data_abort = (ESR_EC(esr) == ESR_EC_DABORT_EL0);
if (user_fault_addr_matches_pac_error_code(fault_addr, is_data_abort)) {
exc |= EXC_PTRAUTH_BIT;
}
#endif /* __has_feature(ptrauth_calls) */
exception_triage(exc, codes, numcodes);
__builtin_unreachable();
}
#if __ARM_PAN_AVAILABLE__
static int
is_pan_fault(arm_saved_state_t *state, uint32_t esr, vm_offset_t fault_addr, fault_status_t fault_code)
{
// PAN (Privileged Access Never) fault occurs for data read/write in EL1 to
// virtual address that is readable/writeable from both EL1 and EL0
// To check for PAN fault, we evaluate if the following conditions are true:
// 1. This is a permission fault
// 2. PAN is enabled
// 3. AT instruction (on which PAN has no effect) on the same faulting address
// succeeds
vm_offset_t pa;
if (!(is_permission_fault(fault_code) && get_saved_state_cpsr(state) & PSR64_PAN)) {
return FALSE;
}
if (esr & ISS_DA_WNR) {
pa = mmu_kvtop_wpreflight(fault_addr);
} else {
pa = mmu_kvtop(fault_addr);
}
return (pa)? TRUE: FALSE;
}
#endif
static void
handle_kernel_abort(arm_saved_state_t *state, uint32_t esr, vm_offset_t fault_addr,
fault_status_t fault_code, vm_prot_t fault_type, vm_offset_t recover, expected_fault_handler_t expected_fault_handler)
{
thread_t thread = current_thread();
(void)esr;
#ifndef CONFIG_XNUPOST
(void)expected_fault_handler;
#endif /* CONFIG_XNUPOST */
#if CONFIG_DTRACE
if (is_vm_fault(fault_code) && thread->t_dtrace_inprobe) { /* Executing under dtrace_probe? */
if (dtrace_tally_fault(fault_addr)) { /* Should a fault under dtrace be ignored? */
/*
* Point to next instruction, or recovery handler if set.
*/
if (recover) {
thread->machine.recover_esr = esr;
thread->machine.recover_far = fault_addr;
set_saved_state_pc_to_recovery_handler(state, recover);
} else {
add_saved_state_pc(state, 4);
}
return;
} else {
panic_with_thread_kernel_state("Unexpected page fault under dtrace_probe", state);
}
}
#endif
if (ml_at_interrupt_context()) {
panic_with_thread_kernel_state("Unexpected abort while on interrupt stack.", state);
}
if (is_vm_fault(fault_code)) {
kern_return_t result = KERN_FAILURE;
vm_map_t map;
int interruptible;
/*
* Ensure no faults in the physical aperture. This could happen if
* a page table is incorrectly allocated from the read only region
* when running with KTRR.
*/
#ifdef CONFIG_XNUPOST
if (expected_fault_handler && expected_fault_handler(state)) {
return;
}
#endif /* CONFIG_XNUPOST */
if (fault_addr >= gVirtBase && fault_addr < static_memory_end) {
panic_with_thread_kernel_state("Unexpected fault in kernel static region\n", state);
}
if (VM_KERNEL_ADDRESS(fault_addr) || thread == THREAD_NULL) {
map = kernel_map;
interruptible = THREAD_UNINT;
} else {
map = thread->map;
/**
* In the case that the recovery handler is set (e.g., during copyio
* and dtrace probes), we don't want the vm_fault() operation to be
* aborted early. Those code paths can't handle restarting the
* vm_fault() operation so don't allow it to return early without
* creating the wanted mapping.
*/
interruptible = (recover) ? THREAD_UNINT : THREAD_ABORTSAFE;
}
/* check to see if it is just a pmap ref/modify fault */
if (!is_translation_fault(fault_code)) {
result = arm_fast_fault(map->pmap,
fault_addr,
fault_type, (fault_code == FSC_ACCESS_FLAG_FAULT_L3), FALSE);
if (result == KERN_SUCCESS) {
return;
}
}
if (result != KERN_PROTECTION_FAILURE) {
/*
* We have to "fault" the page in.
*/
result = vm_fault(map, fault_addr, fault_type,
/* change_wiring */ FALSE, VM_KERN_MEMORY_NONE, interruptible,
/* caller_pmap */ NULL, /* caller_pmap_addr */ 0);
}
if (result == KERN_SUCCESS) {
return;
}
/*
* If we have a recover handler, invoke it now.
*/
if (recover) {
thread->machine.recover_esr = esr;
thread->machine.recover_far = fault_addr;
set_saved_state_pc_to_recovery_handler(state, recover);
return;
}
#if __ARM_PAN_AVAILABLE__
if (is_pan_fault(state, esr, fault_addr, fault_code)) {
panic_with_thread_kernel_state("Privileged access never abort.", state);
}
#endif
} else if (is_alignment_fault(fault_code)) {
if (recover) {
thread->machine.recover_esr = esr;
thread->machine.recover_far = fault_addr;
set_saved_state_pc_to_recovery_handler(state, recover);
return;
}
panic_with_thread_kernel_state("Unaligned kernel data abort.", state);
} else if (is_parity_error(fault_code)) {
#if defined(APPLE_ARM64_ARCH_FAMILY)
if (fault_code == FSC_SYNC_PARITY) {
arm64_platform_error(state, esr, fault_addr);
return;
}
#else
panic_with_thread_kernel_state("Kernel parity error.", state);
#endif
} else {
kprintf("Unclassified kernel abort (fault_code=0x%x)\n", fault_code);
}
panic_with_thread_kernel_state("Kernel data abort.", state);
}
extern void syscall_trace(struct arm_saved_state * regs);
static void
handle_svc(arm_saved_state_t *state)
{
int trap_no = get_saved_state_svc_number(state);
thread_t thread = current_thread();
struct proc *p;
#define handle_svc_kprintf(x...) /* kprintf("handle_svc: " x) */
#define TRACE_SYSCALL 1
#if TRACE_SYSCALL
syscall_trace(state);
#endif
thread->iotier_override = THROTTLE_LEVEL_NONE; /* Reset IO tier override before handling SVC from userspace */
if (trap_no == (int)PLATFORM_SYSCALL_TRAP_NO) {
platform_syscall(state);
panic("Returned from platform_syscall()?");
}
mach_kauth_cred_thread_update();
if (trap_no < 0) {
switch (trap_no) {
case MACH_ARM_TRAP_ABSTIME:
handle_mach_absolute_time_trap(state);
return;
case MACH_ARM_TRAP_CONTTIME:
handle_mach_continuous_time_trap(state);
return;
}
/* Counting perhaps better in the handler, but this is how it's been done */
thread->syscalls_mach++;
mach_syscall(state);
} else {
/* Counting perhaps better in the handler, but this is how it's been done */
thread->syscalls_unix++;
p = get_bsdthreadtask_info(thread);
assert(p);
unix_syscall(state, thread, p);
}
}
static void
handle_mach_absolute_time_trap(arm_saved_state_t *state)
{
uint64_t now = mach_absolute_time();
saved_state64(state)->x[0] = now;
}
static void
handle_mach_continuous_time_trap(arm_saved_state_t *state)
{
uint64_t now = mach_continuous_time();
saved_state64(state)->x[0] = now;
}
__attribute__((noreturn))
static void
handle_msr_trap(arm_saved_state_t *state, uint32_t esr)
{
exception_type_t exception = EXC_BAD_INSTRUCTION;
mach_exception_data_type_t codes[2] = {EXC_ARM_UNDEFINED};
mach_msg_type_number_t numcodes = 2;
uint32_t instr = 0;
if (!is_saved_state64(state)) {
panic("MSR/MRS trap (ESR 0x%x) from 32-bit state", esr);
}
if (PSR64_IS_KERNEL(get_saved_state_cpsr(state))) {
panic("MSR/MRS trap (ESR 0x%x) from kernel", esr);
}
COPYIN(get_saved_state_pc(state), (char *)&instr, sizeof(instr));
codes[1] = instr;
exception_triage(exception, codes, numcodes);
__builtin_unreachable();
}
#ifdef __ARM_ARCH_8_6__
static void
autxx_instruction_extract_reg(uint32_t instr, char reg[4])
{
unsigned int rd = ARM64_INSTR_AUTxx_RD_GET(instr);
switch (rd) {
case 29:
strncpy(reg, "fp", 4);
return;
case 30:
strncpy(reg, "lr", 4);
return;
case 31:
strncpy(reg, "xzr", 4);
return;
default:
snprintf(reg, 4, "x%u", rd);
return;
}
}
static const char *
autix_system_instruction_extract_reg(uint32_t instr)
{
unsigned int crm_op2 = ARM64_INSTR_AUTIx_SYSTEM_CRM_OP2_GET(instr);
if (crm_op2 == ARM64_INSTR_AUTIx_SYSTEM_CRM_OP2_AUTIA1716 ||
crm_op2 == ARM64_INSTR_AUTIx_SYSTEM_CRM_OP2_AUTIB1716) {
return "x17";
} else {
return "lr";
}
}
static void
handle_pac_fail(arm_saved_state_t *state, uint32_t esr)
{
exception_type_t exception = EXC_BAD_ACCESS | EXC_PTRAUTH_BIT;
mach_exception_data_type_t codes[2] = {EXC_ARM_PAC_FAIL};
mach_msg_type_number_t numcodes = 2;
uint32_t instr = 0;
if (!is_saved_state64(state)) {
panic("PAC failure (ESR 0x%x) from 32-bit state", esr);
}
COPYIN(get_saved_state_pc(state), (char *)&instr, sizeof(instr));
if (PSR64_IS_KERNEL(get_saved_state_cpsr(state))) {
#define GENERIC_PAC_FAILURE_MSG_FMT "PAC failure from kernel with %s key"
#define AUTXX_MSG_FMT GENERIC_PAC_FAILURE_MSG_FMT " while authing %s"
#define GENERIC_MSG_FMT GENERIC_PAC_FAILURE_MSG_FMT
char msg[strlen(AUTXX_MSG_FMT)
- strlen("%s") + strlen("IA")
- strlen("%s") + strlen("xzr")
+ 1];
ptrauth_key key = (ptrauth_key)(esr & 0x3);
const char *key_str = ptrauth_key_to_string(key);
if (ARM64_INSTR_IS_AUTxx(instr)) {
char reg[4];
autxx_instruction_extract_reg(instr, reg);
snprintf(msg, sizeof(msg), AUTXX_MSG_FMT, key_str, reg);
} else if (ARM64_INSTR_IS_AUTIx_SYSTEM(instr)) {
const char *reg = autix_system_instruction_extract_reg(instr);
snprintf(msg, sizeof(msg), AUTXX_MSG_FMT, key_str, reg);
} else {
snprintf(msg, sizeof(msg), GENERIC_MSG_FMT, key_str);
}
panic_with_thread_kernel_state(msg, state);
}
codes[1] = instr;
exception_triage(exception, codes, numcodes);
__builtin_unreachable();
}
#endif /* __ARM_ARCH_8_6__ */
static void
handle_user_trapped_instruction32(arm_saved_state_t *state, uint32_t esr)
{
exception_type_t exception = EXC_BAD_INSTRUCTION;
mach_exception_data_type_t codes[2] = {EXC_ARM_UNDEFINED};
mach_msg_type_number_t numcodes = 2;
uint32_t instr;
if (is_saved_state64(state)) {
panic("ESR (0x%x) for instruction trapped from U32, but saved state is 64-bit.", esr);
}
if (PSR64_IS_KERNEL(get_saved_state_cpsr(state))) {
panic("ESR (0x%x) for instruction trapped from U32, actually came from kernel?", esr);
}
COPYIN(get_saved_state_pc(state), (char *)&instr, sizeof(instr));
codes[1] = instr;
exception_triage(exception, codes, numcodes);
__builtin_unreachable();
}
static void
handle_simd_trap(arm_saved_state_t *state, uint32_t esr)
{
exception_type_t exception = EXC_BAD_INSTRUCTION;
mach_exception_data_type_t codes[2] = {EXC_ARM_UNDEFINED};
mach_msg_type_number_t numcodes = 2;
uint32_t instr = 0;
if (PSR64_IS_KERNEL(get_saved_state_cpsr(state))) {
panic("ESR (0x%x) for SIMD trap from userland, actually came from kernel?", esr);
}
COPYIN(get_saved_state_pc(state), (char *)&instr, sizeof(instr));
codes[1] = instr;
exception_triage(exception, codes, numcodes);
__builtin_unreachable();
}
void
sleh_irq(arm_saved_state_t *state)
{
cpu_data_t * cdp __unused = getCpuDatap();
#if MACH_ASSERT
int preemption_level = sleh_get_preemption_level();
#endif
sleh_interrupt_handler_prologue(state, DBG_INTR_TYPE_OTHER);
#if USE_APPLEARMSMP
PE_handle_ext_interrupt();
#else
/* Run the registered interrupt handler. */
cdp->interrupt_handler(cdp->interrupt_target,
cdp->interrupt_refCon,
cdp->interrupt_nub,
cdp->interrupt_source);
#endif
entropy_collect();
sleh_interrupt_handler_epilogue();
#if MACH_ASSERT
if (preemption_level != sleh_get_preemption_level()) {
panic("irq handler %p changed preemption level from %d to %d", cdp->interrupt_handler, preemption_level, sleh_get_preemption_level());
}
#endif
}
void
sleh_fiq(arm_saved_state_t *state)
{
unsigned int type = DBG_INTR_TYPE_UNKNOWN;
#if MACH_ASSERT
int preemption_level = sleh_get_preemption_level();
#endif
#if MONOTONIC_FIQ
uint64_t pmcr0 = 0, upmsr = 0;
#endif /* MONOTONIC_FIQ */
#if defined(HAS_IPI)
boolean_t is_ipi = FALSE;
uint64_t ipi_sr = 0;
if (gFastIPI) {
MRS(ipi_sr, "S3_5_C15_C1_1");
if (ipi_sr & 1) {
is_ipi = TRUE;
}
}
if (is_ipi) {
type = DBG_INTR_TYPE_IPI;
} else
#endif /* defined(HAS_IPI) */
if (ml_get_timer_pending()) {
type = DBG_INTR_TYPE_TIMER;
}
#if MONOTONIC_FIQ
/* Consult the PMI sysregs last, after IPI/timer
* classification.
*/
else if (mt_pmi_pending(&pmcr0, &upmsr)) {
type = DBG_INTR_TYPE_PMI;
}
#endif /* MONOTONIC_FIQ */
sleh_interrupt_handler_prologue(state, type);
#if APPLEVIRTUALPLATFORM
uint64_t iar = __builtin_arm_rsr64("ICC_IAR0_EL1");
#endif
#if defined(HAS_IPI)
if (is_ipi) {
/*
* Order is important here: we must ack the IPI by writing IPI_SR
* before we call cpu_signal_handler(). Otherwise, there will be
* a window between the completion of pending-signal processing in
* cpu_signal_handler() and the ack during which a newly-issued
* IPI to this CPU may be lost. ISB is required to ensure the msr
* is retired before execution of cpu_signal_handler().
*/
MSR("S3_5_C15_C1_1", ipi_sr);
__builtin_arm_isb(ISB_SY);
cpu_signal_handler();
} else
#endif /* defined(HAS_IPI) */
#if MONOTONIC_FIQ
if (type == DBG_INTR_TYPE_PMI) {
INTERRUPT_MASKED_DEBUG_START(mt_fiq, DBG_INTR_TYPE_PMI);
mt_fiq(getCpuDatap(), pmcr0, upmsr);
INTERRUPT_MASKED_DEBUG_END();
} else
#endif /* MONOTONIC_FIQ */
{
/*
* We don't know that this is a timer, but we don't have insight into
* the other interrupts that go down this path.
*/
cpu_data_t *cdp = getCpuDatap();
cdp->cpu_decrementer = -1; /* Large */
/*
* ARM64_TODO: whether we're coming from userland is ignored right now.
* We can easily thread it through, but not bothering for the
* moment (AArch32 doesn't either).
*/
INTERRUPT_MASKED_DEBUG_START(rtclock_intr, DBG_INTR_TYPE_TIMER);
rtclock_intr(TRUE);
INTERRUPT_MASKED_DEBUG_END();
}
#if APPLEVIRTUALPLATFORM
if (iar != GIC_SPURIOUS_IRQ) {
__builtin_arm_wsr64("ICC_EOIR0_EL1", iar);
__builtin_arm_isb(ISB_SY);
}
#endif
sleh_interrupt_handler_epilogue();
#if MACH_ASSERT
if (preemption_level != sleh_get_preemption_level()) {
panic("fiq type %u changed preemption level from %d to %d", type, preemption_level, sleh_get_preemption_level());
}
#endif
}
void
sleh_serror(arm_context_t *context, uint32_t esr, vm_offset_t far)
{
task_vtimer_check(current_thread());
KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_EXCP_SERR_ARM, 0) | DBG_FUNC_START,
esr, VM_KERNEL_ADDRHIDE(far));
arm_saved_state_t *state = &context->ss;
#if MACH_ASSERT
int preemption_level = sleh_get_preemption_level();
#endif
ASSERT_CONTEXT_SANITY(context);
arm64_platform_error(state, esr, far);
#if MACH_ASSERT
if (preemption_level != sleh_get_preemption_level()) {
panic("serror changed preemption level from %d to %d", preemption_level, sleh_get_preemption_level());
}
#endif
KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_EXCP_SERR_ARM, 0) | DBG_FUNC_END,
esr, VM_KERNEL_ADDRHIDE(far));
}
void
mach_syscall_trace_exit(unsigned int retval,
unsigned int call_number)
{
KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
MACHDBG_CODE(DBG_MACH_EXCP_SC, (call_number)) |
DBG_FUNC_END, retval, 0, 0, 0, 0);
}
__attribute__((noreturn))
void
thread_syscall_return(kern_return_t error)
{
thread_t thread;
struct arm_saved_state *state;
thread = current_thread();
state = get_user_regs(thread);
assert(is_saved_state64(state));
saved_state64(state)->x[0] = error;
#if MACH_ASSERT
kern_allocation_name_t
prior __assert_only = thread_get_kernel_state(thread)->allocation_name;
assertf(prior == NULL, "thread_set_allocation_name(\"%s\") not cleared", kern_allocation_get_name(prior));
#endif /* MACH_ASSERT */
if (kdebug_enable) {
/* Invert syscall number (negative for a mach syscall) */
mach_syscall_trace_exit(error, (-1) * get_saved_state_svc_number(state));
}
thread_exception_return();
}
void
syscall_trace(
struct arm_saved_state * regs __unused)
{
/* kprintf("syscall: %d\n", saved_state64(regs)->x[16]); */
}
static void
sleh_interrupt_handler_prologue(arm_saved_state_t *state, unsigned int type)
{
boolean_t is_user = PSR64_IS_USER(get_saved_state_cpsr(state));
task_vtimer_check(current_thread());
uint64_t pc = is_user ? get_saved_state_pc(state) :
VM_KERNEL_UNSLIDE(get_saved_state_pc(state));
KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_EXCP_INTR, 0) | DBG_FUNC_START,
0, pc, is_user, type);
#if CONFIG_TELEMETRY
if (telemetry_needs_record) {
telemetry_mark_curthread(is_user, FALSE);
}
#endif /* CONFIG_TELEMETRY */
}
static void
sleh_interrupt_handler_epilogue(void)
{
#if KPERF
kperf_interrupt();
#endif /* KPERF */
KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_EXCP_INTR, 0) | DBG_FUNC_END);
}
void
sleh_invalid_stack(arm_context_t *context, uint32_t esr __unused, vm_offset_t far __unused)
{
thread_t thread = current_thread();
vm_offset_t kernel_stack_bottom, sp;
sp = get_saved_state_sp(&context->ss);
kernel_stack_bottom = round_page(thread->machine.kstackptr) - KERNEL_STACK_SIZE;
if ((sp < kernel_stack_bottom) && (sp >= (kernel_stack_bottom - PAGE_SIZE))) {
panic_with_thread_kernel_state("Invalid kernel stack pointer (probable overflow).", &context->ss);
}
panic_with_thread_kernel_state("Invalid kernel stack pointer (probable corruption).", &context->ss);
}