// Copyright 2018 The Crashpad Authors. All rights reserved. // // 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 "client/crash_report_database.h" #include "client/simulate_crash.h" #include "gtest/gtest.h" #include "test/multiprocess_exec.h" #include "test/multiprocess.h" #include "test/scoped_temp_dir.h" #include "test/test_paths.h" #include "util/file/file_io.h" #include "util/linux/exception_handler_client.h" #include "util/linux/exception_information.h" #include "util/misc/address_types.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/signals.h" namespace crashpad { namespace test { namespace { bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) { return true; } TEST(CrashpadClient, SimulateCrash) { ScopedTempDir temp_dir; base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("crashpad_handler")); crashpad::CrashpadClient client; ASSERT_TRUE(client.StartHandlerAtCrash(handler_path, base::FilePath(temp_dir.path()), base::FilePath(), "", std::map(), std::vector())); auto database = CrashReportDatabase::InitializeWithoutCreating(temp_dir.path()); ASSERT_TRUE(database); { CrashpadClient::SetFirstChanceExceptionHandler(HandleCrashSuccessfully); CRASHPAD_SIMULATE_CRASH(); std::vector reports; ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 0u); reports.clear(); ASSERT_EQ(database->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 0u); } { CrashpadClient::SetFirstChanceExceptionHandler(nullptr); CRASHPAD_SIMULATE_CRASH(); std::vector reports; ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 0u); reports.clear(); ASSERT_EQ(database->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 1u); } } CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) { 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); base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("crashpad_handler")); crashpad::CrashpadClient client; if (!client.StartHandlerAtCrash(handler_path, base::FilePath(temp_dir), base::FilePath(), "", std::map(), std::vector())) { return EXIT_FAILURE; } __builtin_trap(); NOTREACHED(); return EXIT_SUCCESS; } class StartHandlerAtCrashTest : public MultiprocessExec { public: StartHandlerAtCrashTest() : MultiprocessExec() { SetChildTestMainFunction("StartHandlerAtCrashChild"); SetExpectedChildTerminationBuiltinTrap(); } 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)); // Wait for child to finish. CheckedReadFileAtEOF(ReadPipeHandle()); auto database = CrashReportDatabase::Initialize(temp_dir.path()); ASSERT_TRUE(database); std::vector reports; ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 0u); ASSERT_EQ(database->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 1u); } DISALLOW_COPY_AND_ASSIGN(StartHandlerAtCrashTest); }; TEST(CrashpadClient, StartHandlerAtCrash) { StartHandlerAtCrashTest test; test.Run(); } // Test state for starting the handler for another process. class StartHandlerForClientTest { public: StartHandlerForClientTest() = default; ~StartHandlerForClientTest() = default; bool Initialize() { int socks[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) != 0) { PLOG(ERROR) << "socketpair"; return false; } client_sock_.reset(socks[0]); server_sock_.reset(socks[1]); return true; } 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->GetPendingReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 0u); ASSERT_EQ(database->GetCompletedReports(&reports), CrashReportDatabase::kNoError); EXPECT_EQ(reports.size(), 1u); } bool InstallHandler() { auto signal_handler = SandboxedHandler::Get(); return signal_handler->Initialize(client_sock_.get()); } private: // A signal handler that defers handler process startup to another, presumably // more privileged, process. class SandboxedHandler { public: static SandboxedHandler* Get() { static SandboxedHandler* instance = new SandboxedHandler(); return instance; } bool Initialize(FileHandle client_sock) { client_sock_ = client_sock; 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; 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); ClientInformation info = {}; info.exception_information_address = FromPointerCast( &exception_information); ExceptionHandlerClient handler_client(state->client_sock_); CHECK_EQ(handler_client.RequestCrashDump(info), 0); Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); } FileHandle client_sock_; DISALLOW_COPY_AND_ASSIGN(SandboxedHandler); }; ScopedTempDir temp_dir_; ScopedFileHandle client_sock_; ScopedFileHandle server_sock_; DISALLOW_COPY_AND_ASSIGN(StartHandlerForClientTest); }; // Tests starting the handler for a child process. class StartHandlerForChildTest : public Multiprocess { public: StartHandlerForChildTest() = default; ~StartHandlerForChildTest() = default; bool Initialize() { SetExpectedChildTerminationBuiltinTrap(); return test_state_.Initialize(); } 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(); } StartHandlerForClientTest test_state_; DISALLOW_COPY_AND_ASSIGN(StartHandlerForChildTest); }; TEST(CrashpadClient, StartHandlerForChild) { StartHandlerForChildTest test; ASSERT_TRUE(test.Initialize()); test.Run(); } } // namespace } // namespace test } // namespace crashpad