// Copyright 2020 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "client/crashpad_client.h" #include #include #include #include #include #include "base/logging.h" #include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_port.h" #include "client/ios_handler/exception_processor.h" #include "client/ios_handler/in_process_handler.h" #include "util/ios/raw_logging.h" #include "util/mach/exc_server_variants.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/mach/mach_message_server.h" #include "util/misc/initialization_state_dcheck.h" #include "util/posix/signals.h" #include "util/thread/thread.h" namespace { bool IsBeingDebugged() { kinfo_proc kern_proc_info; int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; size_t len = sizeof(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 { namespace { // A base class for signal handler and Mach exception server. class CrashHandler : public Thread, public UniversalMachExcServer::Interface, public ObjcExceptionDelegate { public: CrashHandler(const CrashHandler&) = delete; CrashHandler& operator=(const CrashHandler&) = delete; static CrashHandler* Get() { if (!instance_) instance_ = new CrashHandler(); return instance_; } static void ResetForTesting() { delete instance_; instance_ = nullptr; } bool Initialize( const base::FilePath& database, const std::string& url, const std::map& annotations, internal::InProcessHandler::ProcessPendingReportsObservationCallback callback) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); if (!in_process_handler_.Initialize(database, url, annotations, callback) || !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_)) { LOG(ERROR) << "Unable to initialize Crashpad."; 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 conditon. struct sigaction sa; if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) { Signals::InstallHandler( SIGPIPE, CatchAndReraiseSignalDefaultAction, 0, nullptr); } InstallObjcExceptionPreprocessor(this); INITIALIZATION_STATE_SET_VALID(initialized_); return true; } void ProcessIntermediateDumps( const std::map& annotations) { in_process_handler_.ProcessIntermediateDumps(annotations); } void ProcessIntermediateDump( const base::FilePath& file, const std::map& annotations) { in_process_handler_.ProcessIntermediateDump(file, annotations); } void DumpWithoutCrash(NativeCPUContext* context, bool process_dump) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); base::FilePath path; if (!in_process_handler_.DumpExceptionFromSimulatedMachException( context, kMachExceptionSimulated, &path)) { return; } if (process_dump) { in_process_handler_.ProcessIntermediateDump(path); } } void DumpWithoutCrashAtPath(NativeCPUContext* context, const base::FilePath& path) { in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath( context, kMachExceptionSimulated, path); } void StartProcessingPendingReports(UploadBehavior upload_behavior) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); in_process_handler_.StartProcessingPendingReports(upload_behavior); } void SetMachExceptionCallbackForTesting(void (*callback)()) { in_process_handler_.SetMachExceptionCallbackForTesting(callback); } uint64_t GetThreadIdForTesting() { return Thread::GetThreadIdForTesting(); } private: CrashHandler() = default; ~CrashHandler() { UninstallObjcExceptionPreprocessor(); Signals::InstallDefaultHandler(SIGABRT); UninstallMachExceptionHandler(); } bool InstallMachExceptionHandler() { exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); if (!exception_port_.is_valid()) { return false; } kern_return_t kr = mach_port_insert_right(mach_task_self(), exception_port_.get(), exception_port_.get(), 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_.get(), EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, MACHINE_THREAD_STATE)) { return false; } mach_handler_running_ = true; Start(); return true; } void UninstallMachExceptionHandler() { mach_handler_running_ = false; exception_port_.reset(); Join(); } // Thread: void ThreadMain() override { 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 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) override { *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 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); } void HandleUncaughtNSException(const uint64_t* frames, const size_t num_frames) override { in_process_handler_.DumpExceptionFromNSExceptionWithFrames(frames, num_frames); // After uncaught exceptions are reported, the system immediately triggers a // call to std::terminate()/abort(). Remove the abort handler so a second // dump isn't generated. CHECK(Signals::InstallDefaultHandler(SIGABRT)); } void HandleUncaughtNSExceptionWithContext( NativeCPUContext* context) override { base::FilePath path; in_process_handler_.DumpExceptionFromSimulatedMachException( context, kMachExceptionFromNSException, &path); // After uncaught exceptions are reported, the system immediately triggers a // call to std::terminate()/abort(). Remove the abort handler so a second // dump isn't generated. CHECK(Signals::InstallDefaultHandler(SIGABRT)); } void HandleUncaughtNSExceptionWithContextAtPath( NativeCPUContext* context, const base::FilePath& path) override { in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath( context, kMachExceptionFromNSException, path); } bool MoveIntermediateDumpAtPathToPending( const base::FilePath& path) override { if (in_process_handler_.MoveIntermediateDumpAtPathToPending(path)) { // After uncaught exceptions are reported, the system immediately triggers // a call to std::terminate()/abort(). Remove the abort handler so a // second dump isn't generated. CHECK(Signals::InstallDefaultHandler(SIGABRT)); return true; } return false; } // The signal handler installed at OS-level. static void CatchAndReraiseSignal(int signo, siginfo_t* siginfo, void* context) { Get()->HandleAndReraiseSignal(signo, siginfo, reinterpret_cast(context), &(Get()->old_action_)); } static void CatchAndReraiseSignalDefaultAction(int signo, siginfo_t* siginfo, void* context) { Get()->HandleAndReraiseSignal( signo, siginfo, reinterpret_cast(context), nullptr); } void HandleAndReraiseSignal(int signo, siginfo_t* siginfo, ucontext_t* context, struct sigaction* old_action) { in_process_handler_.DumpExceptionFromSignal(siginfo, context); // Always call system handler. Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action); } base::mac::ScopedMachReceiveRight exception_port_; ExceptionPorts::ExceptionHandlerVector original_handlers_; struct sigaction old_action_ = {}; internal::InProcessHandler in_process_handler_; static CrashHandler* instance_; std::atomic mach_handler_running_ = false; InitializationStateDcheck initialized_; }; CrashHandler* CrashHandler::instance_ = nullptr; } // namespace CrashpadClient::CrashpadClient() {} CrashpadClient::~CrashpadClient() {} // static bool CrashpadClient::StartCrashpadInProcessHandler( const base::FilePath& database, const std::string& url, const std::map& annotations, ProcessPendingReportsObservationCallback callback) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); return crash_handler->Initialize(database, url, annotations, callback); } // static void CrashpadClient::ProcessIntermediateDumps( const std::map& annotations) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->ProcessIntermediateDumps(annotations); } // static void CrashpadClient::ProcessIntermediateDump( const base::FilePath& file, const std::map& annotations) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->ProcessIntermediateDump(file, annotations); } // static void CrashpadClient::StartProcessingPendingReports( UploadBehavior upload_behavior) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->StartProcessingPendingReports(upload_behavior); } // static void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->DumpWithoutCrash(context, /*process_dump=*/true); } // static void CrashpadClient::DumpWithoutCrashAndDeferProcessing( NativeCPUContext* context) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->DumpWithoutCrash(context, /*process_dump=*/false); } // static void CrashpadClient::DumpWithoutCrashAndDeferProcessingAtPath( NativeCPUContext* context, const base::FilePath path) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->DumpWithoutCrashAtPath(context, path); } void CrashpadClient::ResetForTesting() { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->ResetForTesting(); } void CrashpadClient::SetMachExceptionCallbackForTesting(void (*callback)()) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); crash_handler->SetMachExceptionCallbackForTesting(callback); } uint64_t CrashpadClient::GetThreadIdForTesting() { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); return crash_handler->GetThreadIdForTesting(); } } // namespace crashpad