blob: f149ae71643c9b73d09490f3650f29ac2f97a2c2 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "client/crash_handler_ios.h"
#include <sys/sysctl.h>
#include <unistd.h>
#include <iterator>
#include "base/apple/mach_logging.h"
#include "util/ios/raw_logging.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/mach_message_server.h"
#include "util/posix/signals.h"
namespace {
bool IsBeingDebugged() {
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
size_t len = sizeof(kinfo_proc);
kinfo_proc kern_proc_info;
if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) {
return kern_proc_info.kp_proc.p_flag & P_TRACED;
}
return false;
}
} // namespace
namespace crashpad {
CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::
ThreadSafeScopedMachPortWithReceiveRight()
: port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)) {}
CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::
~ThreadSafeScopedMachPortWithReceiveRight() {
reset();
}
mach_port_t CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::get() {
return port_.load();
}
void CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::reset() {
mach_port_t old_port = port_.exchange(MACH_PORT_NULL);
if (old_port == MACH_PORT_NULL) {
// Already reset, nothing to do.
return;
}
kern_return_t kr = mach_port_mod_refs(
mach_task_self(), old_port, MACH_PORT_RIGHT_RECEIVE, -1);
MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
<< "ThreadSafeScopedMachPortWithReceiveRight mach_port_mod_refs";
kr = mach_port_deallocate(mach_task_self(), old_port);
MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
<< "ThreadSafeScopedMachPortWithReceiveRight mach_port_deallocate";
}
CrashHandler* CrashHandler::instance_ = nullptr;
CrashHandler::CrashHandler() = default;
CrashHandler::~CrashHandler() {
UninstallObjcExceptionPreprocessor();
// Reset the SIGPIPE handler only if the current handler is the
// one installed by DoInitialize(). In other words, if an
// application has set its own SIGPIPE handler after initializing
// Crashpad, there is no need to change the signal handler here.
struct sigaction sa;
if (sigaction(SIGPIPE, nullptr, &sa) == 0 &&
sa.sa_sigaction == CatchAndReraiseSignalDefaultAction) {
Signals::InstallDefaultHandler(SIGPIPE);
}
Signals::InstallDefaultHandler(SIGABRT);
UninstallMachExceptionHandler();
}
// static
CrashHandler* CrashHandler::Get() {
if (!instance_)
instance_ = new CrashHandler();
return instance_;
}
// static
void CrashHandler::ResetForTesting() {
delete instance_;
instance_ = nullptr;
}
bool CrashHandler::DoInitialize() {
if (!InstallMachExceptionHandler() ||
// xnu turns hardware faults into Mach exceptions, so the only signal
// left to register is SIGABRT, which never starts off as a hardware
// fault. Installing a handler for other signals would lead to
// recording exceptions twice. As a consequence, Crashpad will not
// generate intermediate dumps for anything manually calling
// raise(SIG*). In practice, this doesn’t actually happen for crash
// signals that originate as hardware faults.
!Signals::InstallHandler(
SIGABRT, CatchAndReraiseSignal, 0, &old_action_)) {
return false;
}
// For applications that haven't ignored or set a handler for SIGPIPE:
// It’s OK for an application to set its own SIGPIPE handler (including
// SIG_IGN) before initializing Crashpad, because Crashpad will discover the
// existing handler and not install its own.
// It’s OK for Crashpad to install its own SIGPIPE handler and for the
// application to subsequently install its own (including SIG_IGN)
// afterwards, because its handler will replace Crashpad’s.
// This is useful to cover the default situation where nobody installs a
// SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a
// “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without
// Crashpad, SIGPIPE results in a silent and unreported kill (and not even
// ReportCrash will record it), but developers probably want to be alerted to
// the condition.
struct sigaction sa;
if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) {
Signals::InstallHandler(
SIGPIPE, CatchAndReraiseSignalDefaultAction, 0, nullptr);
}
InstallObjcExceptionPreprocessor(this);
return true;
}
bool CrashHandler::InstallMachExceptionHandler() {
mach_port_t exception_port = exception_port_.get();
if (exception_port == MACH_PORT_NULL) {
return false;
}
kern_return_t kr = mach_port_insert_right(mach_task_self(),
exception_port,
exception_port,
MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
MACH_LOG(ERROR, kr) << "mach_port_insert_right";
return false;
}
// TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT.
// Until then, remove |EXC_MASK_BREAKPOINT| while attached to a debugger.
exception_mask_t mask =
ExcMaskAll() &
~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_RPC_ALERT |
EXC_MASK_GUARD | (IsBeingDebugged() ? EXC_MASK_BREAKPOINT : 0));
ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL);
if (!exception_ports.GetExceptionPorts(mask, &original_handlers_) ||
!exception_ports.SetExceptionPort(
mask,
exception_port,
EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
MACHINE_THREAD_STATE)) {
return false;
}
mach_handler_running_ = true;
Start();
return true;
}
void CrashHandler::UninstallMachExceptionHandler() {
mach_handler_running_ = false;
exception_port_.reset();
Join();
}
// Thread:
void CrashHandler::ThreadMain() {
UniversalMachExcServer universal_mach_exc_server(this);
while (mach_handler_running_) {
mach_msg_return_t mr =
MachMessageServer::Run(&universal_mach_exc_server,
exception_port_.get(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kPersistent,
MachMessageServer::kReceiveLargeIgnore,
kMachMessageTimeoutWaitIndefinitely);
MACH_CHECK(
mach_handler_running_
? mr == MACH_SEND_INVALID_DEST // This shouldn't happen for
// exception messages that come
// from the kernel itself, but if
// something else in-process sends
// exception messages and breaks,
// handle that case.
: (mr == MACH_RCV_PORT_CHANGED || // Port was closed while the
// thread was listening.
mr == MACH_RCV_INVALID_NAME), // Port was closed before the
// thread started listening.
mr)
<< "MachMessageServer::Run";
}
}
// UniversalMachExcServer::Interface:
kern_return_t CrashHandler::CatchMachException(
exception_behavior_t behavior,
exception_handler_t exception_port,
thread_t thread,
task_t task,
exception_type_t exception,
const mach_exception_data_type_t* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t* flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count,
thread_state_t new_state,
mach_msg_type_number_t* new_state_count,
const mach_msg_trailer_t* trailer,
bool* destroy_complex_request) {
*destroy_complex_request = true;
// TODO(justincohen): Forward exceptions to original_handlers_ with
// UniversalExceptionRaise.
// iOS shouldn't have any child processes, but just in case, those will
// inherit the task exception ports, and this process isn’t prepared to
// handle them
if (task != mach_task_self()) {
CRASHPAD_RAW_LOG("MachException task != mach_task_self()");
return KERN_FAILURE;
}
HandleMachException(behavior,
thread,
exception,
code,
code_count,
*flavor,
old_state,
old_state_count);
// Respond with KERN_FAILURE so the system will continue to handle this
// exception. xnu will turn this Mach exception into a signal and take the
// default action to terminate the process. However, if sigprocmask is
// called before this Mach exception returns (such as by another thread
// calling abort, see: Libc-1506.40.4/stdlib/FreeBSD/abort.c), the Mach
// exception will be converted into a signal but delivery will be blocked.
// Since concurrent exceptions lead to the losing thread sleeping
// indefinitely, if the abort thread never returns, the thread that
// triggered this Mach exception will repeatedly trap and the process will
// never terminate. If the abort thread didn’t have a user-space signal
// handler that slept forever, the abort would terminate the process even if
// all other signals had been blocked. Instead, unblock all signals
// corresponding to all Mach exceptions Crashpad is registered for before
// returning KERN_FAILURE. There is still racy behavior possible with this
// call to sigprocmask, but the repeated calls to CatchMachException here
// will eventually lead to termination.
sigset_t unblock_set;
sigemptyset(&unblock_set);
sigaddset(&unblock_set, SIGILL); // EXC_BAD_INSTRUCTION
sigaddset(&unblock_set, SIGTRAP); // EXC_BREAKPOINT
sigaddset(&unblock_set, SIGFPE); // EXC_ARITHMETIC
sigaddset(&unblock_set, SIGBUS); // EXC_BAD_ACCESS
sigaddset(&unblock_set, SIGSEGV); // EXC_BAD_ACCESS
if (sigprocmask(SIG_UNBLOCK, &unblock_set, nullptr) != 0) {
CRASHPAD_RAW_LOG("sigprocmask");
}
return KERN_FAILURE;
}
void CrashHandler::HandleMachException(exception_behavior_t behavior,
thread_t thread,
exception_type_t exception,
const mach_exception_data_type_t* code,
mach_msg_type_number_t code_count,
thread_state_flavor_t flavor,
ConstThreadState old_state,
mach_msg_type_number_t old_state_count) {
in_process_handler().DumpExceptionFromMachException(behavior,
thread,
exception,
code,
code_count,
flavor,
old_state,
old_state_count);
}
// static
void CrashHandler::CatchAndReraiseSignal(int signo,
siginfo_t* siginfo,
void* context) {
Get()->HandleAndReraiseSignal(signo,
siginfo,
reinterpret_cast<ucontext_t*>(context),
&(Get()->old_action_));
}
// static
void CrashHandler::CatchAndReraiseSignalDefaultAction(int signo,
siginfo_t* siginfo,
void* context) {
Get()->HandleAndReraiseSignal(
signo, siginfo, reinterpret_cast<ucontext_t*>(context), nullptr);
}
} // namespace crashpad