// 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 "base/check_op.h" #include "base/notreached.h" #include "build/build_config.h" #include "client/annotation.h" #include "client/annotation_list.h" #include "client/crash_report_database.h" #include "client/crashpad_info.h" #include "client/simulate_crash.h" #include "gtest/gtest.h" #include "snapshot/annotation_snapshot.h" #include "snapshot/minidump/process_snapshot_minidump.h" #include "snapshot/sanitized/sanitization_information.h" #include "test/errors.h" #include "test/multiprocess.h" #include "test/multiprocess_exec.h" #include "test/scoped_temp_dir.h" #include "test/test_paths.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/socket.h" #include "util/misc/address_sanitizer.h" #include "util/misc/address_types.h" #include "util/misc/from_pointer_cast.h" #include "util/misc/memory_sanitizer.h" #include "util/posix/scoped_mmap.h" #include "util/posix/signals.h" #include "util/thread/thread.h" #if BUILDFLAG(IS_ANDROID) #include #include "dlfcn_internal.h" // Normally this comes from set_abort_message.h, but only at API level 21. extern "C" void android_set_abort_message(const char* msg) __attribute__((weak)); #endif namespace crashpad { namespace test { namespace { enum class CrashType : uint32_t { kSimulated, kBuiltinTrap, kInfiniteRecursion, kSegvWithTagBits, // kFakeSegv is meant to simulate a MTE segv error. kFakeSegv, }; struct StartHandlerForSelfTestOptions { bool start_handler_at_crash; bool set_first_chance_handler; bool set_last_chance_handler; bool crash_non_main_thread; bool client_uses_signals; bool gather_indirectly_referenced_memory; CrashType crash_type; }; class StartHandlerForSelfTest : public testing::TestWithParam< std::tuple> { public: StartHandlerForSelfTest() = default; StartHandlerForSelfTest(const StartHandlerForSelfTest&) = delete; StartHandlerForSelfTest& operator=(const StartHandlerForSelfTest&) = delete; ~StartHandlerForSelfTest() = default; void SetUp() override { // MSAN requires that padding bytes have been initialized for structs that // are written to files. memset(&options_, 0, sizeof(options_)); std::tie(options_.start_handler_at_crash, options_.set_first_chance_handler, options_.set_last_chance_handler, options_.crash_non_main_thread, options_.client_uses_signals, options_.gather_indirectly_referenced_memory, options_.crash_type) = GetParam(); } const StartHandlerForSelfTestOptions& Options() const { return options_; } private: StartHandlerForSelfTestOptions options_; }; bool InstallHandler(CrashpadClient* client, bool start_at_crash, const base::FilePath& handler_path, const base::FilePath& database_path, const std::vector& attachments) { return start_at_crash ? client->StartHandlerAtCrash(handler_path, database_path, base::FilePath(), "", std::map(), std::vector(), attachments) : client->StartHandler(handler_path, database_path, base::FilePath(), "", std::map(), std::vector(), false, false, attachments); } constexpr char kTestAnnotationName[] = "name_of_annotation"; constexpr char kTestAnnotationValue[] = "value_of_annotation"; constexpr char kTestAttachmentName[] = "test_attachment"; constexpr char kTestAttachmentContent[] = "attachment_content"; #if BUILDFLAG(IS_ANDROID) constexpr char kTestAbortMessage[] = "test abort message"; #endif void ValidateAttachment(const CrashReportDatabase::UploadReport* report) { auto attachments = report->GetAttachments(); ASSERT_EQ(attachments.size(), 1u); char buf[sizeof(kTestAttachmentContent)]; attachments.at(kTestAttachmentName)->Read(buf, sizeof(buf)); ASSERT_EQ(memcmp(kTestAttachmentContent, buf, sizeof(kTestAttachmentContent)), 0); } void ValidateExtraMemory(const StartHandlerForSelfTestOptions& options, const ProcessSnapshotMinidump& minidump) { // Verify that if we have an exception, then the code around the instruction // pointer is included in the extra memory. const ExceptionSnapshot* exception = minidump.Exception(); if (exception == nullptr) return; uint64_t pc = exception->Context()->InstructionPointer(); std::vector snippets = minidump.ExtraMemory(); bool pc_found = false; for (const MemorySnapshot* snippet : snippets) { uint64_t start = snippet->Address(); uint64_t end = start + snippet->Size(); if (pc >= start && pc < end) { pc_found = true; break; } } EXPECT_EQ(pc_found, options.gather_indirectly_referenced_memory); if (options.crash_type == CrashType::kSegvWithTagBits) { EXPECT_EQ(exception->ExceptionAddress(), 0xefull << 56); } } void ValidateDump(const StartHandlerForSelfTestOptions& options, const CrashReportDatabase::UploadReport* report) { ProcessSnapshotMinidump minidump_snapshot; ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader())); #if BUILDFLAG(IS_ANDROID) // This part of the test requires Q. The API level on Q devices will be 28 // until the API is finalized, so we can't check API level yet. For now, test // for the presence of a libc symbol which was introduced in Q. if (crashpad::internal::Dlsym(RTLD_DEFAULT, "android_fdsan_close_with_tag")) { const auto& annotations = minidump_snapshot.AnnotationsSimpleMap(); auto abort_message = annotations.find("abort_message"); ASSERT_NE(annotations.end(), abort_message); EXPECT_EQ(kTestAbortMessage, abort_message->second); } #endif ValidateAttachment(report); ValidateExtraMemory(options, minidump_snapshot); for (const ModuleSnapshot* module : minidump_snapshot.Modules()) { for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) { if (static_cast(annotation.type) != Annotation::Type::kString) { continue; } if (annotation.name == kTestAnnotationName) { std::string value( reinterpret_cast(annotation.value.data()), annotation.value.size()); EXPECT_EQ(value, kTestAnnotationValue); return; } } } ADD_FAILURE(); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winfinite-recursion" // Clang (masquerading as gcc) is too smart, and removes the recursion // otherwise. May need to change if either clang or another compiler becomes // smarter. #if defined(COMPILER_GCC) __attribute__((noinline)) #endif #if defined(__clang__) __attribute__((optnone)) #endif int RecurseInfinitely(int* ptr) { int buf[1 << 20]; return *ptr + RecurseInfinitely(buf); } #pragma clang diagnostic pop sigjmp_buf do_crash_sigjmp_env; bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) { siglongjmp(do_crash_sigjmp_env, 1); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code-return" return true; #pragma clang diagnostic pop } bool HandleCrashSuccessfullyAfterReporting(int, siginfo_t*, ucontext_t*) { return true; } void DoCrash(const StartHandlerForSelfTestOptions& options, CrashpadClient* client) { if (sigsetjmp(do_crash_sigjmp_env, 1) != 0) { return; } switch (options.crash_type) { case CrashType::kSimulated: { CRASHPAD_SIMULATE_CRASH(); break; } case CrashType::kBuiltinTrap: { __builtin_trap(); } case CrashType::kInfiniteRecursion: { int val = 42; exit(RecurseInfinitely(&val)); } case CrashType::kSegvWithTagBits: { volatile char* x = nullptr; #ifdef __aarch64__ x += 0xefull << 56; #endif // __aarch64__ *x; break; } case CrashType::kFakeSegv: { // With a regular SIGSEGV like null dereference, the signal gets reraised // automatically, causing HandleOrReraiseSignal() to be called a second // time, terminating the process with the signal regardless of the last // chance handler. raise(SIGSEGV); break; } } } class ScopedAltSignalStack { public: ScopedAltSignalStack() = default; ScopedAltSignalStack(const ScopedAltSignalStack&) = delete; ScopedAltSignalStack& operator=(const ScopedAltSignalStack&) = delete; ~ScopedAltSignalStack() { if (stack_mem_.is_valid()) { stack_t stack; stack.ss_flags = SS_DISABLE; if (sigaltstack(&stack, nullptr) != 0) { ADD_FAILURE() << ErrnoMessage("sigaltstack"); } } } void Initialize() { ScopedMmap local_stack_mem; const size_t stack_size = MINSIGSTKSZ; ASSERT_TRUE(local_stack_mem.ResetMmap(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); stack_t stack; stack.ss_sp = local_stack_mem.addr(); stack.ss_size = stack_size; stack.ss_flags = 0; ASSERT_EQ(sigaltstack(&stack, nullptr), 0) << ErrnoMessage("sigaltstack"); stack_mem_.ResetAddrLen(local_stack_mem.release(), stack_size); } private: ScopedMmap stack_mem_; }; class CrashThread : public Thread { public: CrashThread(const StartHandlerForSelfTestOptions& options, CrashpadClient* client) : client_signal_stack_(), options_(options), client_(client) {} CrashThread(const CrashThread&) = delete; CrashThread& operator=(const CrashThread&) = delete; private: void ThreadMain() override { // It is only necessary to call InitializeSignalStackForThread() once, but // should be harmless to call multiple times and durable against the client // using sigaltstack() either before or after it is called. CrashpadClient::InitializeSignalStackForThread(); if (options_.client_uses_signals) { client_signal_stack_.Initialize(); } CrashpadClient::InitializeSignalStackForThread(); DoCrash(options_, client_); } ScopedAltSignalStack client_signal_stack_; const StartHandlerForSelfTestOptions& options_; CrashpadClient* client_; }; CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) { FileHandle in = StdioFileHandle(StdioStream::kStandardInput); VMSize temp_dir_length; CheckedReadFileExactly(in, &temp_dir_length, sizeof(temp_dir_length)); std::string temp_dir(temp_dir_length, '\0'); CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length); StartHandlerForSelfTestOptions options; CheckedReadFileExactly(in, &options, sizeof(options)); ScopedAltSignalStack client_signal_stack; if (options.client_uses_signals) { client_signal_stack.Initialize(); static Signals::OldActions old_actions; static Signals::Handler client_handler = [](int signo, siginfo_t* siginfo, void*) { FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); char c = 0; WriteFile(out, &c, sizeof(c)); Signals::RestoreHandlerAndReraiseSignalOnReturn( siginfo, old_actions.ActionForSignal(signo)); }; CHECK(Signals::InstallCrashHandlers( client_handler, SA_ONSTACK, &old_actions)); } if (options.gather_indirectly_referenced_memory) { CrashpadInfo::GetCrashpadInfo()->set_gather_indirectly_referenced_memory( TriState::kEnabled, 1024 * 1024 * 4); } base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("crashpad_handler")); crashpad::AnnotationList::Register(); static StringAnnotation<32> test_annotation(kTestAnnotationName); test_annotation.Set(kTestAnnotationValue); const std::vector attachments = { base::FilePath(temp_dir).Append(kTestAttachmentName)}; crashpad::CrashpadClient client; if (!InstallHandler(&client, options.start_handler_at_crash, handler_path, base::FilePath(temp_dir), attachments)) { return EXIT_FAILURE; } if (options.set_first_chance_handler) { client.SetFirstChanceExceptionHandler(HandleCrashSuccessfully); } if (options.set_last_chance_handler) { client.SetLastChanceExceptionHandler(HandleCrashSuccessfullyAfterReporting); } #if BUILDFLAG(IS_ANDROID) if (android_set_abort_message) { android_set_abort_message(kTestAbortMessage); } #endif if (options.crash_non_main_thread) { CrashThread thread(options, &client); thread.Start(); thread.Join(); } else { DoCrash(options, &client); } return EXIT_SUCCESS; } class StartHandlerForSelfInChildTest : public MultiprocessExec { public: StartHandlerForSelfInChildTest(const StartHandlerForSelfTestOptions& options) : MultiprocessExec(), options_(options) { SetChildTestMainFunction("StartHandlerForSelfTestChild"); if (!options.set_first_chance_handler) { switch (options.crash_type) { case CrashType::kSimulated: // kTerminationNormal, EXIT_SUCCESS break; case CrashType::kBuiltinTrap: SetExpectedChildTerminationBuiltinTrap(); break; case CrashType::kInfiniteRecursion: SetExpectedChildTermination(TerminationReason::kTerminationSignal, SIGSEGV); break; case CrashType::kSegvWithTagBits: SetExpectedChildTermination(TerminationReason::kTerminationSignal, SIGSEGV); break; case CrashType::kFakeSegv: if (!options.set_last_chance_handler) { SetExpectedChildTermination(TerminationReason::kTerminationSignal, SIGSEGV); } else { SetExpectedChildTermination(TerminationReason::kTerminationNormal, EXIT_SUCCESS); } break; } } } StartHandlerForSelfInChildTest(const StartHandlerForSelfInChildTest&) = delete; StartHandlerForSelfInChildTest& operator=( const StartHandlerForSelfInChildTest&) = delete; private: void MultiprocessParent() override { ScopedTempDir temp_dir; VMSize temp_dir_length = temp_dir.path().value().size(); ASSERT_TRUE(LoggingWriteFile( WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length))); ASSERT_TRUE(LoggingWriteFile( WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length)); ASSERT_TRUE( LoggingWriteFile(WritePipeHandle(), &options_, sizeof(options_))); FileWriter writer; base::FilePath test_attachment_path = temp_dir.path().Append(kTestAttachmentName); bool is_created = writer.Open(test_attachment_path, FileWriteMode::kCreateOrFail, FilePermissions::kOwnerOnly); ASSERT_TRUE(is_created); writer.Write(kTestAttachmentContent, sizeof(kTestAttachmentContent)); writer.Close(); if (options_.client_uses_signals && !options_.set_first_chance_handler && options_.crash_type != CrashType::kSimulated && // The last chance handler will prevent the client handler from being // called if crash type is kFakeSegv. (!options_.set_last_chance_handler || options_.crash_type != CrashType::kFakeSegv)) { // Wait for child's client signal handler. char c; EXPECT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c))); } // Wait for child to finish. CheckedReadFileAtEOF(ReadPipeHandle()); auto database = CrashReportDatabase::Initialize(temp_dir.path()); ASSERT_TRUE(database); std::vector reports; ASSERT_EQ(database->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 0u); reports.clear(); ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); bool report_expected = !options_.set_first_chance_handler || options_.crash_type == CrashType::kSimulated; ASSERT_EQ(reports.size(), report_expected ? 1u : 0u); if (!report_expected) { return; } std::unique_ptr report; ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report), CrashReportDatabase::kNoError); ValidateDump(options_, report.get()); } StartHandlerForSelfTestOptions options_; }; TEST_P(StartHandlerForSelfTest, StartHandlerInChild) { #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ defined(UNDEFINED_SANITIZER) if (Options().crash_type == CrashType::kInfiniteRecursion) { GTEST_SKIP(); } #endif // defined(ADDRESS_SANITIZER) // kFakeSegv does raise(SIGSEGV) to simulate a MTE error which is a SEGSEGV // that doesn't get reraised automatically, but this causes the child process // to flakily terminate normally on some bots (e.g. android-nougat-x86-rel) // for some reason so this is skipped. if (!Options().set_last_chance_handler && Options().crash_type == CrashType::kFakeSegv) { GTEST_SKIP(); } if (Options().crash_type == CrashType::kSegvWithTagBits) { #if !defined(ARCH_CPU_ARM64) GTEST_SKIP() << "Testing for tag bits only exists on aarch64."; #else struct utsname uname_info; ASSERT_EQ(uname(&uname_info), 0); ASSERT_NE(uname_info.release, nullptr); char* release = uname_info.release; unsigned major = strtoul(release, &release, 10); ASSERT_EQ(*release++, '.'); unsigned minor = strtoul(release, nullptr, 10); if (major < 5 || (major == 5 && minor < 11)) { GTEST_SKIP() << "Linux kernel v" << uname_info.release << " does not support SA_EXPOSE_TAGBITS"; } #endif // !defined(ARCH_CPU_ARM64) } StartHandlerForSelfInChildTest test(Options()); test.Run(); } INSTANTIATE_TEST_SUITE_P( StartHandlerForSelfTestSuite, StartHandlerForSelfTest, testing::Combine(testing::Bool(), testing::Bool(), testing::Bool(), testing::Bool(), testing::Bool(), testing::Bool(), testing::Values(CrashType::kSimulated, CrashType::kBuiltinTrap, CrashType::kInfiniteRecursion, CrashType::kSegvWithTagBits, CrashType::kFakeSegv))); // Test state for starting the handler for another process. class StartHandlerForClientTest { public: StartHandlerForClientTest() = default; StartHandlerForClientTest(const StartHandlerForClientTest&) = delete; StartHandlerForClientTest& operator=(const StartHandlerForClientTest&) = delete; ~StartHandlerForClientTest() = default; bool Initialize(bool sanitize) { sanitize_ = sanitize; return UnixCredentialSocket::CreateCredentialSocketpair(&client_sock_, &server_sock_); } bool StartHandlerOnDemand() { char c; if (!LoggingReadFileExactly(server_sock_.get(), &c, sizeof(c))) { ADD_FAILURE(); return false; } base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("crashpad_handler")); CrashpadClient client; if (!client.StartHandlerForClient(handler_path, temp_dir_.path(), base::FilePath(), "", std::map(), std::vector(), server_sock_.get())) { ADD_FAILURE(); return false; } return true; } void ExpectReport() { auto database = CrashReportDatabase::InitializeWithoutCreating(temp_dir_.path()); ASSERT_TRUE(database); std::vector reports; ASSERT_EQ(database->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 0u); reports.clear(); ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); if (sanitize_) { EXPECT_EQ(reports.size(), 0u); } else { EXPECT_EQ(reports.size(), 1u); } } bool InstallHandler() { auto signal_handler = SandboxedHandler::Get(); return signal_handler->Initialize(client_sock_.get(), sanitize_); } private: // A signal handler that defers handler process startup to another, presumably // more privileged, process. class SandboxedHandler { public: SandboxedHandler(const SandboxedHandler&) = delete; SandboxedHandler& operator=(const SandboxedHandler&) = delete; static SandboxedHandler* Get() { static SandboxedHandler* instance = new SandboxedHandler(); return instance; } bool Initialize(FileHandle client_sock, bool sanitize) { client_sock_ = client_sock; sanitize_ = sanitize; return Signals::InstallCrashHandlers(HandleCrash, 0, nullptr); } private: SandboxedHandler() = default; ~SandboxedHandler() = delete; static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { auto state = Get(); char c = 0; CHECK(LoggingWriteFile(state->client_sock_, &c, sizeof(c))); ExceptionInformation exception_information; exception_information.siginfo_address = FromPointerCast( siginfo); exception_information.context_address = FromPointerCast( context); exception_information.thread_id = syscall(SYS_gettid); ExceptionHandlerProtocol::ClientInformation info; info.exception_information_address = FromPointerCast( &exception_information); SanitizationInformation sanitization_info = {}; if (state->sanitize_) { info.sanitization_information_address = FromPointerCast(&sanitization_info); // Target a non-module address to prevent a crash dump. sanitization_info.target_module_address = FromPointerCast(&sanitization_info); } ExceptionHandlerClient handler_client(state->client_sock_, false); CHECK_EQ(handler_client.RequestCrashDump(info), 0); Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); } FileHandle client_sock_; bool sanitize_; }; ScopedTempDir temp_dir_; ScopedFileHandle client_sock_; ScopedFileHandle server_sock_; bool sanitize_; }; // Tests starting the handler for a child process. class StartHandlerForChildTest : public Multiprocess { public: StartHandlerForChildTest() = default; StartHandlerForChildTest(const StartHandlerForChildTest&) = delete; StartHandlerForChildTest& operator=(const StartHandlerForChildTest&) = delete; ~StartHandlerForChildTest() = default; bool Initialize(bool sanitize) { SetExpectedChildTerminationBuiltinTrap(); return test_state_.Initialize(sanitize); } private: void MultiprocessParent() { ASSERT_TRUE(test_state_.StartHandlerOnDemand()); // Wait for chlid to finish. CheckedReadFileAtEOF(ReadPipeHandle()); test_state_.ExpectReport(); } [[noreturn]] void MultiprocessChild() { CHECK(test_state_.InstallHandler()); __builtin_trap(); NOTREACHED(); } StartHandlerForClientTest test_state_; }; TEST(CrashpadClient, StartHandlerForChild) { StartHandlerForChildTest test; ASSERT_TRUE(test.Initialize(/* sanitize= */ false)); test.Run(); } TEST(CrashpadClient, SanitizedChild) { StartHandlerForChildTest test; ASSERT_TRUE(test.Initialize(/* sanitize= */ true)); test.Run(); } } // namespace } // namespace test } // namespace crashpad