| // 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 <dlfcn.h> |
| #include <setjmp.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <sys/syscall.h> |
| #include <sys/types.h> |
| #include <sys/utsname.h> |
| #include <unistd.h> |
| |
| #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 <android/set_abort_message.h> |
| #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<bool, bool, bool, bool, bool, bool, CrashType>> { |
| 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<base::FilePath>& attachments) { |
| return start_at_crash |
| ? client->StartHandlerAtCrash(handler_path, |
| database_path, |
| base::FilePath(), |
| "", |
| std::map<std::string, std::string>(), |
| std::vector<std::string>(), |
| attachments) |
| : client->StartHandler(handler_path, |
| database_path, |
| base::FilePath(), |
| "", |
| std::map<std::string, std::string>(), |
| std::vector<std::string>(), |
| 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<const MemorySnapshot*> 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) != |
| Annotation::Type::kString) { |
| continue; |
| } |
| |
| if (annotation.name == kTestAnnotationName) { |
| std::string value( |
| reinterpret_cast<const char*>(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<base::FilePath> 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<CrashReportDatabase::Report> 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<const CrashReportDatabase::UploadReport> 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::string, std::string>(), |
| std::vector<std::string>(), |
| server_sock_.get())) { |
| ADD_FAILURE(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void ExpectReport() { |
| auto database = |
| CrashReportDatabase::InitializeWithoutCreating(temp_dir_.path()); |
| ASSERT_TRUE(database); |
| |
| std::vector<CrashReportDatabase::Report> 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<decltype(exception_information.siginfo_address)>( |
| siginfo); |
| exception_information.context_address = |
| FromPointerCast<decltype(exception_information.context_address)>( |
| context); |
| exception_information.thread_id = syscall(SYS_gettid); |
| |
| ExceptionHandlerProtocol::ClientInformation info; |
| info.exception_information_address = |
| FromPointerCast<decltype(info.exception_information_address)>( |
| &exception_information); |
| |
| SanitizationInformation sanitization_info = {}; |
| if (state->sanitize_) { |
| info.sanitization_information_address = |
| FromPointerCast<VMAddress>(&sanitization_info); |
| // Target a non-module address to prevent a crash dump. |
| sanitization_info.target_module_address = |
| FromPointerCast<VMAddress>(&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(); |
| } |
| |
| void MultiprocessChild() { |
| CHECK(test_state_.InstallHandler()); |
| |
| __builtin_trap(); |
| |
| NOTREACHED_IN_MIGRATION(); |
| } |
| |
| 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 |