// Copyright 2018 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 #include #include #include #include #include #include #include #include #include "base/logging.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "client/client_argv_handling.h" #include "third_party/lss/lss.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" #include "util/linux/exception_handler_client.h" #include "util/linux/exception_information.h" #include "util/linux/scoped_pr_set_dumpable.h" #include "util/linux/scoped_pr_set_ptracer.h" #include "util/linux/socket.h" #include "util/misc/address_sanitizer.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/scoped_mmap.h" #include "util/posix/signals.h" #include "util/posix/spawn_subprocess.h" namespace crashpad { namespace { std::string FormatArgumentInt(const std::string& name, int value) { return base::StringPrintf("--%s=%d", name.c_str(), value); } std::string FormatArgumentAddress(const std::string& name, const void* addr) { return base::StringPrintf("--%s=%p", name.c_str(), addr); } #if BUILDFLAG(IS_ANDROID) std::vector BuildAppProcessArgs( const std::string& class_name, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { #if defined(ARCH_CPU_64_BITS) static constexpr char kAppProcess[] = "/system/bin/app_process64"; #else static constexpr char kAppProcess[] = "/system/bin/app_process32"; #endif std::vector argv; argv.push_back(kAppProcess); argv.push_back("/system/bin"); argv.push_back("--application"); argv.push_back(class_name); std::vector handler_argv = BuildHandlerArgvStrings(base::FilePath(kAppProcess), database, metrics_dir, url, annotations, arguments); if (socket != kInvalidFileHandle) { handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket)); } argv.insert(argv.end(), handler_argv.begin(), handler_argv.end()); return argv; } std::vector BuildArgsToLaunchWithLinker( const std::string& handler_trampoline, const std::string& handler_library, bool is_64_bit, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { std::vector argv; if (is_64_bit) { argv.push_back("/system/bin/linker64"); } else { argv.push_back("/system/bin/linker"); } argv.push_back(handler_trampoline); argv.push_back(handler_library); std::vector handler_argv = BuildHandlerArgvStrings( base::FilePath(), database, metrics_dir, url, annotations, arguments); if (socket != kInvalidFileHandle) { handler_argv.push_back(FormatArgumentInt("initial-client-fd", socket)); } argv.insert(argv.end(), handler_argv.begin() + 1, handler_argv.end()); return argv; } #endif // BUILDFLAG(IS_ANDROID) // A base class for Crashpad signal handler implementations. class SignalHandler { public: SignalHandler(const SignalHandler&) = delete; SignalHandler& operator=(const SignalHandler&) = delete; // Returns the currently installed signal hander. May be `nullptr` if no // handler has been installed. static SignalHandler* Get() { return handler_; } // Disables any installed Crashpad signal handler. If a crash signal is // received, any previously installed (non-Crashpad) signal handler will be // restored and the signal reraised. static void Disable() { if (!handler_->disabled_.test_and_set()) { handler_->WakeThreads(); } } void SetFirstChanceHandler(CrashpadClient::FirstChanceHandlerLinux handler) { first_chance_handler_ = handler; } // The base implementation for all signal handlers, suitable for calling // directly to simulate signal delivery. void HandleCrash(int signo, siginfo_t* siginfo, void* context) { exception_information_.siginfo_address = FromPointerCast( siginfo); exception_information_.context_address = FromPointerCast( context); exception_information_.thread_id = sys_gettid(); ScopedPrSetDumpable set_dumpable(false); HandleCrashImpl(); } protected: SignalHandler() = default; ~SignalHandler() = default; bool Install(const std::set* unhandled_signals) { bool signal_stack_initialized = CrashpadClient::InitializeSignalStackForThread(); DCHECK(signal_stack_initialized); DCHECK(!handler_); handler_ = this; return Signals::InstallCrashHandlers(HandleOrReraiseSignal, SA_ONSTACK | SA_EXPOSE_TAGBITS, &old_actions_, unhandled_signals); } const ExceptionInformation& GetExceptionInfo() { return exception_information_; } virtual void HandleCrashImpl() = 0; private: static constexpr int32_t kDumpNotDone = 0; static constexpr int32_t kDumpDone = 1; // The signal handler installed at OS-level. static void HandleOrReraiseSignal(int signo, siginfo_t* siginfo, void* context) { if (handler_->first_chance_handler_ && handler_->first_chance_handler_( signo, siginfo, static_cast(context))) { return; } // Only handle the first fatal signal observed. If another thread receives a // crash signal, it waits for the first dump to complete instead of // requesting another. if (!handler_->disabled_.test_and_set()) { handler_->HandleCrash(signo, siginfo, context); handler_->WakeThreads(); } else { // Processes on Android normally have several chained signal handlers that // co-operate to report crashes. e.g. WebView will have this signal // handler installed, the app embedding WebView may have a signal handler // installed, and Bionic will have a signal handler. Each signal handler // runs in succession, possibly managed by libsigchain. This wait is // intended to avoid ill-effects from multiple signal handlers from // different layers (possibly all trying to use ptrace()) from running // simultaneously. It does not block forever so that in most conditions, // those signal handlers will still have a chance to run and ensures // process termination in case the first crashing thread crashes again in // its signal handler. Though less typical, this situation also occurs on // other Linuxes, e.g. to produce in-process stack traces for debug // builds. handler_->WaitForDumpDone(); } Signals::RestoreHandlerAndReraiseSignalOnReturn( siginfo, handler_->old_actions_.ActionForSignal(signo)); } void WaitForDumpDone() { kernel_timespec timeout; timeout.tv_sec = 5; timeout.tv_nsec = 0; sys_futex(&dump_done_futex_, FUTEX_WAIT_PRIVATE, kDumpNotDone, &timeout, nullptr, 0); } void WakeThreads() { dump_done_futex_ = kDumpDone; sys_futex( &dump_done_futex_, FUTEX_WAKE_PRIVATE, INT_MAX, nullptr, nullptr, 0); } Signals::OldActions old_actions_ = {}; ExceptionInformation exception_information_ = {}; CrashpadClient::FirstChanceHandlerLinux first_chance_handler_ = nullptr; int32_t dump_done_futex_ = kDumpNotDone; #if !defined(__cpp_lib_atomic_value_initialization) || \ __cpp_lib_atomic_value_initialization < 201911L std::atomic_flag disabled_ = ATOMIC_FLAG_INIT; #else std::atomic_flag disabled_; #endif static SignalHandler* handler_; }; SignalHandler* SignalHandler::handler_ = nullptr; // Launches a single use handler to snapshot this process. class LaunchAtCrashHandler : public SignalHandler { public: LaunchAtCrashHandler(const LaunchAtCrashHandler&) = delete; LaunchAtCrashHandler& operator=(const LaunchAtCrashHandler&) = delete; static LaunchAtCrashHandler* Get() { static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler(); return instance; } bool Initialize(std::vector* argv_in, const std::vector* envp, const std::set* unhandled_signals) { argv_strings_.swap(*argv_in); if (envp) { envp_strings_ = *envp; StringVectorToCStringVector(envp_strings_, &envp_); set_envp_ = true; } argv_strings_.push_back(FormatArgumentAddress("trace-parent-with-exception", &GetExceptionInfo())); StringVectorToCStringVector(argv_strings_, &argv_); return Install(unhandled_signals); } void HandleCrashImpl() override { ScopedPrSetPtracer set_ptracer(sys_getpid(), /* may_log= */ false); pid_t pid = fork(); if (pid < 0) { return; } if (pid == 0) { if (set_envp_) { execve(argv_[0], const_cast(argv_.data()), const_cast(envp_.data())); } else { execv(argv_[0], const_cast(argv_.data())); } _exit(EXIT_FAILURE); } int status; waitpid(pid, &status, 0); } private: LaunchAtCrashHandler() = default; ~LaunchAtCrashHandler() = delete; std::vector argv_strings_; std::vector argv_; std::vector envp_strings_; std::vector envp_; bool set_envp_ = false; }; class RequestCrashDumpHandler : public SignalHandler { public: RequestCrashDumpHandler(const RequestCrashDumpHandler&) = delete; RequestCrashDumpHandler& operator=(const RequestCrashDumpHandler&) = delete; static RequestCrashDumpHandler* Get() { static RequestCrashDumpHandler* instance = new RequestCrashDumpHandler(); return instance; } // pid < 0 indicates the handler pid should be determined by communicating // over the socket. // pid == 0 indicates it is not necessary to set the handler as this process' // ptracer. e.g. if the handler has CAP_SYS_PTRACE or if this process is in a // user namespace and the handler's uid matches the uid of the process that // created the namespace. // pid > 0 directly indicates what the handler's pid is expected to be, so // retrieving this information from the handler is not necessary. bool Initialize(ScopedFileHandle sock, pid_t pid, const std::set* unhandled_signals) { ExceptionHandlerClient client(sock.get(), true); if (pid < 0) { ucred creds; if (!client.GetHandlerCredentials(&creds)) { return false; } pid = creds.pid; } if (pid > 0) { pthread_atfork(nullptr, nullptr, SetPtracerAtFork); if (prctl(PR_SET_PTRACER, pid, 0, 0, 0) != 0) { PLOG(WARNING) << "prctl"; } } sock_to_handler_.reset(sock.release()); handler_pid_ = pid; return Install(unhandled_signals); } bool GetHandlerSocket(int* sock, pid_t* pid) { if (!sock_to_handler_.is_valid()) { return false; } if (sock) { *sock = sock_to_handler_.get(); } if (pid) { *pid = handler_pid_; } return true; } void HandleCrashImpl() override { // Attempt to set the ptracer again, in case a crash occurs after a fork, // before SetPtracerAtFork() has been called. Ignore errors because the // system call may be disallowed if the sandbox is engaged. if (handler_pid_ > 0) { sys_prctl(PR_SET_PTRACER, handler_pid_, 0, 0, 0); } ExceptionHandlerProtocol::ClientInformation info = {}; info.exception_information_address = FromPointerCast(&GetExceptionInfo()); #if BUILDFLAG(IS_CHROMEOS_ASH) info.crash_loop_before_time = crash_loop_before_time_; #endif ExceptionHandlerClient client(sock_to_handler_.get(), true); client.RequestCrashDump(info); } #if BUILDFLAG(IS_CHROMEOS_ASH) void SetCrashLoopBefore(uint64_t crash_loop_before_time) { crash_loop_before_time_ = crash_loop_before_time; } #endif private: RequestCrashDumpHandler() = default; ~RequestCrashDumpHandler() = delete; static void SetPtracerAtFork() { auto handler = RequestCrashDumpHandler::Get(); if (handler->handler_pid_ > 0 && prctl(PR_SET_PTRACER, handler->handler_pid_, 0, 0, 0) != 0) { PLOG(WARNING) << "prctl"; } } ScopedFileHandle sock_to_handler_; pid_t handler_pid_ = -1; #if BUILDFLAG(IS_CHROMEOS_ASH) // An optional UNIX timestamp passed to us from Chrome. // This will pass to crashpad_handler and then to Chrome OS crash_reporter. // This should really be a time_t, but it's basically an opaque value (we // don't anything with it except pass it along). uint64_t crash_loop_before_time_ = 0; #endif }; } // namespace CrashpadClient::CrashpadClient() {} CrashpadClient::~CrashpadClient() {} bool CrashpadClient::StartHandler( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, bool restartable, bool asynchronous_start, const std::vector& attachments) { DCHECK(!asynchronous_start); ScopedFileHandle client_sock, handler_sock; if (!UnixCredentialSocket::CreateCredentialSocketpair(&client_sock, &handler_sock)) { return false; } std::vector argv = BuildHandlerArgvStrings( handler, database, metrics_dir, url, annotations, arguments, attachments); argv.push_back(FormatArgumentInt("initial-client-fd", handler_sock.get())); argv.push_back("--shared-client-connection"); if (!SpawnSubprocess(argv, nullptr, handler_sock.get(), false, nullptr)) { return false; } handler_sock.reset(); pid_t handler_pid = -1; if (!IsRegularFile(base::FilePath("/proc/sys/kernel/yama/ptrace_scope"))) { handler_pid = 0; } auto signal_handler = RequestCrashDumpHandler::Get(); return signal_handler->Initialize( std::move(client_sock), handler_pid, &unhandled_signals_); } #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) // static bool CrashpadClient::GetHandlerSocket(int* sock, pid_t* pid) { auto signal_handler = RequestCrashDumpHandler::Get(); return signal_handler->GetHandlerSocket(sock, pid); } bool CrashpadClient::SetHandlerSocket(ScopedFileHandle sock, pid_t pid) { auto signal_handler = RequestCrashDumpHandler::Get(); return signal_handler->Initialize(std::move(sock), pid, &unhandled_signals_); } // static bool CrashpadClient::InitializeSignalStackForThread() { stack_t stack; if (sigaltstack(nullptr, &stack) != 0) { PLOG(ERROR) << "sigaltstack"; return false; } DCHECK_EQ(stack.ss_flags & SS_ONSTACK, 0); const size_t page_size = getpagesize(); #if defined(ADDRESS_SANITIZER) const size_t kStackSize = 2 * ((SIGSTKSZ + page_size - 1) & ~(page_size - 1)); #else const size_t kStackSize = (SIGSTKSZ + page_size - 1) & ~(page_size - 1); #endif // ADDRESS_SANITIZER if (stack.ss_flags & SS_DISABLE || stack.ss_size < kStackSize) { const size_t kGuardPageSize = page_size; const size_t kStackAllocSize = kStackSize + 2 * kGuardPageSize; static void (*stack_destructor)(void*) = [](void* stack_mem) { const size_t page_size = getpagesize(); const size_t kGuardPageSize = page_size; #if defined(ADDRESS_SANITIZER) const size_t kStackSize = 2 * ((SIGSTKSZ + page_size - 1) & ~(page_size - 1)); #else const size_t kStackSize = (SIGSTKSZ + page_size - 1) & ~(page_size - 1); #endif // ADDRESS_SANITIZER const size_t kStackAllocSize = kStackSize + 2 * kGuardPageSize; stack_t stack; stack.ss_flags = SS_DISABLE; if (sigaltstack(&stack, &stack) != 0) { PLOG(ERROR) << "sigaltstack"; } else if (stack.ss_sp != static_cast(stack_mem) + kGuardPageSize) { PLOG_IF(ERROR, sigaltstack(&stack, nullptr) != 0) << "sigaltstack"; } if (munmap(stack_mem, kStackAllocSize) != 0) { PLOG(ERROR) << "munmap"; } }; static pthread_key_t stack_key; static int key_error = []() { errno = pthread_key_create(&stack_key, stack_destructor); PLOG_IF(ERROR, errno) << "pthread_key_create"; return errno; }(); if (key_error) { return false; } auto old_stack = static_cast(pthread_getspecific(stack_key)); if (old_stack) { stack.ss_sp = old_stack + kGuardPageSize; } else { ScopedMmap stack_mem; if (!stack_mem.ResetMmap(nullptr, kStackAllocSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) { return false; } if (mprotect(stack_mem.addr_as() + kGuardPageSize, kStackSize, PROT_READ | PROT_WRITE) != 0) { PLOG(ERROR) << "mprotect"; return false; } stack.ss_sp = stack_mem.addr_as() + kGuardPageSize; errno = pthread_setspecific(stack_key, stack_mem.release()); PCHECK(errno == 0) << "pthread_setspecific"; } stack.ss_size = kStackSize; stack.ss_flags = (stack.ss_flags & SS_DISABLE) ? 0 : stack.ss_flags & SS_AUTODISARM; if (sigaltstack(&stack, nullptr) != 0) { PLOG(ERROR) << "sigaltstack"; return false; } } return true; } #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || // BUILDFLAG(IS_CHROMEOS) #if BUILDFLAG(IS_ANDROID) bool CrashpadClient::StartJavaHandlerAtCrash( const std::string& class_name, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments) { std::vector argv = BuildAppProcessArgs(class_name, database, metrics_dir, url, annotations, arguments, kInvalidFileHandle); auto signal_handler = LaunchAtCrashHandler::Get(); return signal_handler->Initialize(&argv, env, &unhandled_signals_); } // static bool CrashpadClient::StartJavaHandlerForClient( const std::string& class_name, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { std::vector argv = BuildAppProcessArgs( class_name, database, metrics_dir, url, annotations, arguments, socket); return SpawnSubprocess(argv, env, socket, false, nullptr); } bool CrashpadClient::StartHandlerWithLinkerAtCrash( const std::string& handler_trampoline, const std::string& handler_library, bool is_64_bit, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments) { std::vector argv = BuildArgsToLaunchWithLinker(handler_trampoline, handler_library, is_64_bit, database, metrics_dir, url, annotations, arguments, kInvalidFileHandle); auto signal_handler = LaunchAtCrashHandler::Get(); return signal_handler->Initialize(&argv, env, &unhandled_signals_); } // static bool CrashpadClient::StartHandlerWithLinkerForClient( const std::string& handler_trampoline, const std::string& handler_library, bool is_64_bit, const std::vector* env, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { std::vector argv = BuildArgsToLaunchWithLinker(handler_trampoline, handler_library, is_64_bit, database, metrics_dir, url, annotations, arguments, socket); return SpawnSubprocess(argv, env, socket, false, nullptr); } #endif bool CrashpadClient::StartHandlerAtCrash( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, const std::vector& attachments) { std::vector argv = BuildHandlerArgvStrings( handler, database, metrics_dir, url, annotations, arguments, attachments); auto signal_handler = LaunchAtCrashHandler::Get(); return signal_handler->Initialize(&argv, nullptr, &unhandled_signals_); } // static bool CrashpadClient::StartHandlerForClient( const base::FilePath& handler, const base::FilePath& database, const base::FilePath& metrics_dir, const std::string& url, const std::map& annotations, const std::vector& arguments, int socket) { std::vector argv = BuildHandlerArgvStrings( handler, database, metrics_dir, url, annotations, arguments); argv.push_back(FormatArgumentInt("initial-client-fd", socket)); return SpawnSubprocess(argv, nullptr, socket, true, nullptr); } // static void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { if (!SignalHandler::Get()) { DLOG(ERROR) << "Crashpad isn't enabled"; return; } #if defined(ARCH_CPU_ARMEL) memset(context->uc_regspace, 0, sizeof(context->uc_regspace)); #elif defined(ARCH_CPU_ARM64) memset(context->uc_mcontext.__reserved, 0, sizeof(context->uc_mcontext.__reserved)); #endif siginfo_t siginfo; siginfo.si_signo = Signals::kSimulatedSigno; siginfo.si_errno = 0; siginfo.si_code = 0; SignalHandler::Get()->HandleCrash( siginfo.si_signo, &siginfo, reinterpret_cast(context)); } // static void CrashpadClient::CrashWithoutDump(const std::string& message) { SignalHandler::Disable(); LOG(FATAL) << message; } // static void CrashpadClient::SetFirstChanceExceptionHandler( FirstChanceHandlerLinux handler) { DCHECK(SignalHandler::Get()); SignalHandler::Get()->SetFirstChanceHandler(handler); } void CrashpadClient::SetUnhandledSignals(const std::set& signals) { DCHECK(!SignalHandler::Get()); unhandled_signals_ = signals; } #if BUILDFLAG(IS_CHROMEOS_ASH) // static void CrashpadClient::SetCrashLoopBefore(uint64_t crash_loop_before_time) { auto request_crash_dump_handler = RequestCrashDumpHandler::Get(); request_crash_dump_handler->SetCrashLoopBefore(crash_loop_before_time); } #endif } // namespace crashpad