Mach exception delivery

osfmk/kern/exception.c · 1092 lines · browse source

Machine-independent exception delivery. exception_triage searches the thread, task and host exception ports in turn and delivers the exception; unclaimed exceptions fall back to a BSD signal.

exception_init source
Routine: exception_init Purpose: Global initialization of state for exceptions. Conditions: None.
void
exception_init(void)
{
	int tmp = 0;

	if (PE_parse_boot_argn("-panic_on_exception_triage", &tmp, sizeof(tmp))) {
		panic_on_exception_triage = true;
	}

#if (DEVELOPMENT || DEBUG)
	if (exception_log_max_pid) {
		printf("Logging all exceptions where pid < exception_log_max_pid (%d)\n", exception_log_max_pid);
	}
#endif /* (DEVELOPMENT || DEBUG) */
}
exception_port_copy_send source
ipc_port_t
exception_port_copy_send(ipc_port_t port)
{
	if (IP_VALID(port)) {
		if (is_ux_handler_port(port)) {
			/* is_ux_handler_port() compares against __DATA_CONST */
			port = ipc_port_copy_send_any(port);
		} else {
			port = ipc_port_copy_send_mqueue(port);
		}
	}
	return port;
}
exception_deliver source
Routine: exception_deliver Purpose: Make an upcall to the exception server provided. Conditions: Nothing locked and no resources held. Called from an exception context, so thread_exception_return and thread_kdb_return are possible. Returns: KERN_SUCCESS if the exception was handled
kern_return_t
exception_deliver(
	thread_t                thread,
	exception_type_t        exception,
	mach_exception_data_t   code,
	mach_msg_type_number_t  codeCnt,
	struct exception_action *excp,
	lck_mtx_t               *mutex)
{
	ipc_port_t              exc_port = IPC_PORT_NULL;
	exception_data_type_t   small_code[EXCEPTION_CODE_MAX];
	thread_state_t          new_state = NULL;
	int                     code64;
	int                     behavior;
	int                     flavor;
	kern_return_t           kr = KERN_FAILURE;
	task_t task;
	task_id_token_t task_token;
	ipc_port_t thread_port = IPC_PORT_NULL,
	    task_port = IPC_PORT_NULL,
	    task_token_port = IPC_PORT_NULL;
	thread_set_status_flags_t get_flags = TSSF_TRANSLATE_TO_USER;
	thread_set_status_flags_t set_flags = TSSF_CHECK_USER_FLAGS;

	/*
	 *  Save work if we are terminating.
	 *  Just go back to our AST handler.
	 */
	if (!thread->active && !thread->inspection) {
		return KERN_SUCCESS;
… more in source
exception_deliver_backtrace source
Routine: exception_deliver_backtrace Purpose: Attempt exception delivery with backtrace info to exception ports in exc_ports in order. Conditions: Caller has a reference on bt_object, and send rights on exc_ports. Does not consume any passed references or rights
void
exception_deliver_backtrace(
	kcdata_object_t  bt_object,
	ipc_port_t       exc_ports[static BT_EXC_PORTS_COUNT],
	exception_type_t exception)
{
	kern_return_t kr;
	mach_exception_data_type_t code[EXCEPTION_CODE_MAX];
	ipc_port_t target_port, bt_obj_port;

	assert(exception == EXC_GUARD);

	code[0] = exception;
	code[1] = 0;

	kcdata_object_reference(bt_object);
	bt_obj_port = convert_kcdata_object_to_port(bt_object);
	/* backtrace object ref consumed, no-senders is armed */

	if (!IP_VALID(bt_obj_port)) {
		return;
	}

	/*
	 * We are guaranteed at task_enqueue_exception_with_corpse() time
	 * that the exception port prefers backtrace delivery.
	 */
	for (unsigned int i = 0; i < BT_EXC_PORTS_COUNT; i++) {
		target_port = exc_ports[i];
… more in source
check_exc_receiver_dependency source
Routine: check_exc_receiver_dependency Purpose: Verify that the port destined for receiving this exception is not on the current task. This would cause hang in kernel for EXC_CRASH primarily. Note: If port is transferred between check and delivery then deadlock may happen. Conditions: Nothing locked and no resources held. Called from an exception context. Returns: KERN_SUCCESS if its ok to send exception message.
static kern_return_t
check_exc_receiver_dependency(
	exception_type_t exception,
	struct exception_action *excp,
	lck_mtx_t *mutex)
{
	kern_return_t retval = KERN_SUCCESS;

	if (excp == NULL || exception != EXC_CRASH) {
		return retval;
	}

	task_t task = current_task();
	lck_mtx_lock(mutex);
	ipc_port_t xport = excp[exception].port;
	if (IP_VALID(xport) && ip_in_space_noauth(xport, task->itk_space)) {
		retval = KERN_FAILURE;
	}
	lck_mtx_unlock(mutex);
	return retval;
}
exception_triage_thread source
Routine: exception_triage_thread Purpose: The thread caught an exception. We make an up-call to the thread's exception server. Conditions: Nothing locked and no resources held. Called from an exception context, so thread_exception_return and thread_kdb_return are possible. Returns: KERN_SUCCESS if exception is handled by any of the handlers.
kern_return_t
exception_triage_thread(
	exception_type_t        exception,
	mach_exception_data_t   code,
	mach_msg_type_number_t  codeCnt,
	thread_t                thread)
{
	task_t                  task;
	thread_ro_t             tro;
	host_priv_t             host_priv;
	lck_mtx_t               *mutex;
	struct exception_action *actions;
	kern_return_t   kr = KERN_FAILURE;

	assert(exception != EXC_RPC_ALERT);

	/*
	 * If this behavior has been requested by the the kernel
	 * (due to the boot environment), we should panic if we
	 * enter this function.  This is intended as a debugging
	 * aid; it should allow us to debug why we caught an
	 * exception in environments where debugging is especially
	 * difficult.
	 */
	if (panic_on_exception_triage) {
		panic("called exception_triage when it was forbidden by the boot environment");
	}

	/*
	 * Try to raise the exception at the activation level.
… more in source
pac_exception_triage source
static void
pac_exception_triage(
	exception_type_t        exception,
	mach_exception_data_t   code)
{
	boolean_t traced_flag = FALSE;
	task_t task = current_task();
	void *proc = get_bsdtask_info(task);
	char *proc_name = (char *) "unknown";
	int pid = 0;

#ifdef MACH_BSD
	pid = proc_selfpid();
	if (proc) {
		traced_flag = proc_is_traced(proc);
		/* Should only be called on current proc */
		proc_name = proc_name_address(proc);

		/*
		 * For a ptrauth violation, check if process isn't being ptraced and
		 * the task has the TFRO_PAC_EXC_FATAL flag set. If both conditions are true,
		 * terminate the task via exit_with_reason
		 */
		if (!traced_flag) {
			if (pac_exception_telemetry) {
				ca_event_t ca_event = CA_EVENT_ALLOCATE(pac_exception_event);
				CA_EVENT_TYPE(pac_exception_event) * pexc_event = ca_event->data;
				pexc_event->exception = exception;
				pexc_event->exception_code_0 = code[0];
				pexc_event->exception_code_1 = code[1];
… more in source
maybe_unrecoverable_exception_triage source
__has_feature(ptrauth_calls)
static void
maybe_unrecoverable_exception_triage(
	exception_type_t        exception,
	mach_exception_data_t   code)
{
	task_t task = current_task();
	void *proc = get_bsdtask_info(task);

#ifdef MACH_BSD
	if (!proc) {
		return;
	}

	/*
	 * Note that the below policy to decide whether this should be unrecoverable is
	 * likely conceptually specific to the particular exception.
	 * If you find yourself adding another user_brk_..._descriptor and want to customize the
	 * policy for whether it should be unrecoverable, consider attaching each policy to
	 * the corresponding descriptor and somehow carrying it through to here.
	 */
	/* These exceptions are deliverable (and potentially recoverable) if the process is being debugged. */
	if (is_address_space_debugged(proc)) {
		return;
	}

	/*
	 * By policy, this exception is uncatchable by exception/signal handlers.
	 * Therefore exit immediately.
	 */
	/* Should only be called on current proc */
… more in source
exception_triage source
kern_return_t
exception_triage(
	exception_type_t        exception,
	mach_exception_data_t   code,
	mach_msg_type_number_t  codeCnt)
{
	thread_t thread = current_thread();
	task_t   task   = current_task();

	assert(codeCnt > 0);

	if (VM_MAP_PAGE_SIZE(task->map) < PAGE_SIZE) {
		DEBUG4K_EXC("thread %p task %p map %p exception %d codes 0x%llx 0x%llx\n",
		    thread, task, task->map, exception, code[0], codeCnt > 1 ? code[1] : 0);
		if (debug4k_panic_on_exception) {
			panic("DEBUG4K thread %p task %p map %p exception %d codes 0x%llx 0x%llx",
			    thread, task, task->map, exception, code[0], codeCnt > 1 ? code[1] : 0);
		}
	}

#if DEVELOPMENT || DEBUG
#ifdef MACH_BSD
	if (proc_pid(get_bsdtask_info(task)) <= exception_log_max_pid) {
		record_system_event(SYSTEM_EVENT_TYPE_INFO, SYSTEM_EVENT_SUBSYSTEM_PROCESS, "process exit",
		    "exception_log_max_pid: pid %d (%s): sending exception %d (0x%llx 0x%llx)",
		    proc_pid(get_bsdtask_info(task)), proc_name_address(get_bsdtask_info(task)),
		    exception, code[0], codeCnt > 1 ? code[1] : 0);
	}
#endif /* MACH_BSD */
#endif /* DEVELOPMENT || DEBUG */
… more in source
bsd_exception source
kern_return_t
bsd_exception(
	exception_type_t        exception,
	mach_exception_data_t   code,
	mach_msg_type_number_t  codeCnt)
{
	task_t                  task;
	lck_mtx_t               *mutex;
	thread_t                self = current_thread();
	kern_return_t           kr;

	/*
	 * Maybe the task level will handle it.
	 */
	task = current_task();
	mutex = &task->itk_lock_data;

	kr = exception_deliver(self, exception, code, codeCnt, task->exc_actions, mutex);

	if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED) {
		return KERN_SUCCESS;
	}
	return KERN_FAILURE;
}
task_exception_notify source
Raise an exception on a task. This should tell launchd to launch Crash Reporter for this task. If the exception is fatal, we should be careful about sending a synchronous exception
kern_return_t
task_exception_notify(exception_type_t exception,
    mach_exception_data_type_t exccode, mach_exception_data_type_t excsubcode, const bool fatal)
{
	mach_exception_data_type_t      code[EXCEPTION_CODE_MAX];
	wait_interrupt_t                wsave;
	kern_return_t kr = KERN_SUCCESS;

	/*
	 * If we are not in dev mode, nobody should be allowed to synchronously handle
	 * a fatal EXC_GUARD - they might stall on it indefinitely
	 */
	if (fatal && !developer_mode_state() && exception == EXC_GUARD) {
		return KERN_DENIED;
	}

	code[0] = exccode;
	code[1] = excsubcode;

	wsave = thread_interrupt_level(THREAD_UNINT);
	kr = exception_triage(exception, code, EXCEPTION_CODE_MAX);
	(void) thread_interrupt_level(wsave);
	return kr;
}
sys_perf_notify source
Handle interface for special performance monitoring This is a special case of the host exception handler
kern_return_t
sys_perf_notify(thread_t thread, int pid)
{
	host_priv_t             hostp;
	ipc_port_t              xport;
	struct exception_action saved_exc_actions[EXC_TYPES_COUNT] = {};
	wait_interrupt_t        wsave;
	kern_return_t           ret;
	struct label            *temp_label;

	hostp = host_priv_self();       /* Get the host privileged ports */
	mach_exception_data_type_t      code[EXCEPTION_CODE_MAX];
	code[0] = 0xFF000001;           /* Set terminate code */
	code[1] = pid;          /* Pass out the pid */

#if CONFIG_MACF
	/* Create new label for saved_exc_actions[EXC_RPC_ALERT] */
	mac_exc_associate_action_label(&saved_exc_actions[EXC_RPC_ALERT],
	    mac_exc_create_label(&saved_exc_actions[EXC_RPC_ALERT]));
#endif /* CONFIG_MACF */

	lck_mtx_lock(&hostp->lock);
	xport = hostp->exc_actions[EXC_RPC_ALERT].port;

	/* Make sure we're not catching our own exception */
	if (!IP_VALID(xport) ||
	    !ip_active(xport) ||
	    ip_in_space_noauth(xport, get_threadtask(thread)->itk_space)) {
		lck_mtx_unlock(&hostp->lock);
#if CONFIG_MACF
… more in source
proc_is_traced source
MACH_BSD
extern bool proc_is_traced(void *p);
proc_selfpid source · proc_selfpid reference
extern int proc_selfpid(void);
proc_name_address source
extern char *proc_name_address(struct proc *p);