chromium/third_party/crashpad/crashpad/client/crashpad_client_ios.cc

// 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 <signal.h>
#include <unistd.h>

#include <atomic>
#include <ios>
#include <iterator>

#include "base/apple/mach_logging.h"
#include "base/apple/scoped_mach_port.h"
#include "base/logging.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 {

// Thread-safe version of `base::apple::ScopedMachReceiveRight` which allocates
// the Mach port upon construction and deallocates it upon destruction.
class ThreadSafeScopedMachPortWithReceiveRight {
 public:
  ThreadSafeScopedMachPortWithReceiveRight()
      : port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)) {}

  ThreadSafeScopedMachPortWithReceiveRight(
      const ThreadSafeScopedMachPortWithReceiveRight&) = delete;
  ThreadSafeScopedMachPortWithReceiveRight& operator=(
      const ThreadSafeScopedMachPortWithReceiveRight&) = delete;

  ~ThreadSafeScopedMachPortWithReceiveRight() { reset(); }

  mach_port_t get() { return port_.load(); }
  void 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";
  }

 private:
  std::atomic<mach_port_t> port_;
};

// 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<std::string, std::string>& 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<std::string, std::string>& annotations) {
    in_process_handler_.ProcessIntermediateDumps(annotations);
  }

  void ProcessIntermediateDump(
      const base::FilePath& file,
      const std::map<std::string, std::string>& 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() {
    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 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<ucontext_t*>(context),
                                  &(Get()->old_action_));
  }

  static void CatchAndReraiseSignalDefaultAction(int signo,
                                                 siginfo_t* siginfo,
                                                 void* context) {
    Get()->HandleAndReraiseSignal(
        signo, siginfo, reinterpret_cast<ucontext_t*>(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);
  }

  ThreadSafeScopedMachPortWithReceiveRight exception_port_;
  ExceptionPorts::ExceptionHandlerVector original_handlers_;
  struct sigaction old_action_ = {};
  internal::InProcessHandler in_process_handler_;
  static CrashHandler* instance_;
  std::atomic<bool> 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<std::string, std::string>& 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<std::string, std::string>& annotations) {
  CrashHandler* crash_handler = CrashHandler::Get();
  DCHECK(crash_handler);
  crash_handler->ProcessIntermediateDumps(annotations);
}

// static
void CrashpadClient::ProcessIntermediateDump(
    const base::FilePath& file,
    const std::map<std::string, std::string>& 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