// Copyright 2017 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 "handler/linux/exception_handler_server.h" #include #include #include "build/build_config.h" #include "gtest/gtest.h" #include "snapshot/linux/process_snapshot_linux.h" #include "test/errors.h" #include "test/multiprocess.h" #include "util/linux/direct_ptrace_connection.h" #include "util/linux/exception_handler_client.h" #include "util/linux/ptrace_client.h" #include "util/linux/scoped_pr_set_ptracer.h" #include "util/misc/uuid.h" #include "util/synchronization/semaphore.h" #include "util/thread/thread.h" #if defined(OS_ANDROID) #include #endif namespace crashpad { namespace test { namespace { // Runs the ExceptionHandlerServer on a background thread. class RunServerThread : public Thread { public: RunServerThread(ExceptionHandlerServer* server, ExceptionHandlerServer::Delegate* delegate) : server_(server), delegate_(delegate), join_sem_(0) {} ~RunServerThread() override {} bool JoinWithTimeout(double timeout) { if (!join_sem_.TimedWait(timeout)) { return false; } Join(); return true; } private: // Thread: void ThreadMain() override { server_->Run(delegate_); join_sem_.Signal(); } ExceptionHandlerServer* server_; ExceptionHandlerServer::Delegate* delegate_; Semaphore join_sem_; DISALLOW_COPY_AND_ASSIGN(RunServerThread); }; class ScopedStopServerAndJoinThread { public: ScopedStopServerAndJoinThread(ExceptionHandlerServer* server, RunServerThread* thread) : server_(server), thread_(thread) {} ~ScopedStopServerAndJoinThread() { server_->Stop(); EXPECT_TRUE(thread_->JoinWithTimeout(5.0)); } private: ExceptionHandlerServer* server_; RunServerThread* thread_; DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread); }; class TestDelegate : public ExceptionHandlerServer::Delegate { public: TestDelegate() : Delegate(), last_exception_address_(0), last_client_(-1), sem_(0) {} ~TestDelegate() {} bool WaitForException(double timeout_seconds, pid_t* last_client, VMAddress* last_address) { if (sem_.TimedWait(timeout_seconds)) { *last_client = last_client_; *last_address = last_exception_address_; return true; } return false; } bool HandleException(pid_t client_process_id, uid_t client_uid, const ExceptionHandlerProtocol::ClientInformation& info, VMAddress requesting_thread_stack_address, pid_t* requesting_thread_id = nullptr, UUID* local_report_id = nullptr) override { DirectPtraceConnection connection; bool connected = connection.Initialize(client_process_id); EXPECT_TRUE(connected); last_exception_address_ = info.exception_information_address; last_client_ = client_process_id; sem_.Signal(); if (!connected) { return false; } if (requesting_thread_id) { if (requesting_thread_stack_address) { ProcessSnapshotLinux process_snapshot; if (!process_snapshot.Initialize(&connection)) { ADD_FAILURE(); return false; } *requesting_thread_id = process_snapshot.FindThreadWithStackAddress( requesting_thread_stack_address); } else { *requesting_thread_id = -1; } } return true; } bool HandleExceptionWithBroker( pid_t client_process_id, uid_t client_uid, const ExceptionHandlerProtocol::ClientInformation& info, int broker_sock, UUID* local_report_id = nullptr) override { PtraceClient client; bool connected = client.Initialize(broker_sock, client_process_id); EXPECT_TRUE(connected); last_exception_address_ = info.exception_information_address, last_client_ = client_process_id; sem_.Signal(); return connected; } private: VMAddress last_exception_address_; pid_t last_client_; Semaphore sem_; DISALLOW_COPY_AND_ASSIGN(TestDelegate); }; class MockPtraceStrategyDecider : public PtraceStrategyDecider { public: MockPtraceStrategyDecider(PtraceStrategyDecider::Strategy strategy) : PtraceStrategyDecider(), strategy_(strategy) {} ~MockPtraceStrategyDecider() {} Strategy ChooseStrategy(int sock, bool multiple_clients, const ucred& client_credentials) override { if (strategy_ == Strategy::kUseBroker) { ExceptionHandlerProtocol::ServerToClientMessage message = {}; message.type = ExceptionHandlerProtocol::ServerToClientMessage::kTypeForkBroker; ExceptionHandlerProtocol::Errno status; bool result = LoggingWriteFile(sock, &message, sizeof(message)) && LoggingReadFileExactly(sock, &status, sizeof(status)); EXPECT_TRUE(result); if (!result) { return Strategy::kError; } if (status != 0) { errno = status; ADD_FAILURE() << ErrnoMessage("Handler Client ForkBroker"); return Strategy::kNoPtrace; } } return strategy_; } private: Strategy strategy_; DISALLOW_COPY_AND_ASSIGN(MockPtraceStrategyDecider); }; class ExceptionHandlerServerTest : public testing::TestWithParam { public: ExceptionHandlerServerTest() : server_(), delegate_(), server_thread_(&server_, &delegate_), sock_to_handler_(), use_multi_client_socket_(GetParam()) {} ~ExceptionHandlerServerTest() = default; int SockToHandler() { return sock_to_handler_.get(); } TestDelegate* Delegate() { return &delegate_; } void Hangup() { sock_to_handler_.reset(); } RunServerThread* ServerThread() { return &server_thread_; } ExceptionHandlerServer* Server() { return &server_; } class CrashDumpTest : public Multiprocess { public: CrashDumpTest(ExceptionHandlerServerTest* server_test, bool succeeds) : Multiprocess(), server_test_(server_test), succeeds_(succeeds) {} ~CrashDumpTest() = default; void MultiprocessParent() override { ExceptionHandlerProtocol::ClientInformation info; ASSERT_TRUE( LoggingReadFileExactly(ReadPipeHandle(), &info, sizeof(info))); if (succeeds_) { VMAddress last_address; pid_t last_client; ASSERT_TRUE(server_test_->Delegate()->WaitForException( 5.0, &last_client, &last_address)); EXPECT_EQ(last_address, info.exception_information_address); EXPECT_EQ(last_client, ChildPID()); } else { CheckedReadFileAtEOF(ReadPipeHandle()); } } void MultiprocessChild() override { ASSERT_EQ(close(server_test_->sock_to_client_), 0); ExceptionHandlerProtocol::ClientInformation info; info.exception_information_address = 42; ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &info, sizeof(info))); // If the current ptrace_scope is restricted, the broker needs to be set // as the ptracer for this process. Setting this process as its own // ptracer allows the broker to inherit this condition. ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ true); ExceptionHandlerClient client(server_test_->SockToHandler(), server_test_->use_multi_client_socket_); ASSERT_EQ(client.RequestCrashDump(info), 0); } private: ExceptionHandlerServerTest* server_test_; bool succeeds_; DISALLOW_COPY_AND_ASSIGN(CrashDumpTest); }; void ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy strategy, bool succeeds) { Server()->SetPtraceStrategyDecider( std::make_unique(strategy)); ScopedStopServerAndJoinThread stop_server(Server(), ServerThread()); ServerThread()->Start(); CrashDumpTest test(this, succeeds); test.Run(); } bool UsingMultiClientSocket() const { return use_multi_client_socket_; } protected: void SetUp() override { int socks[2]; ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, socks), 0); sock_to_handler_.reset(socks[0]); sock_to_client_ = socks[1]; ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1]), use_multi_client_socket_)); } private: ExceptionHandlerServer server_; TestDelegate delegate_; RunServerThread server_thread_; ScopedFileHandle sock_to_handler_; int sock_to_client_; bool use_multi_client_socket_; DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest); }; TEST_P(ExceptionHandlerServerTest, ShutdownWithNoClients) { ServerThread()->Start(); Hangup(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } TEST_P(ExceptionHandlerServerTest, StopWithClients) { ServerThread()->Start(); Server()->Stop(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } TEST_P(ExceptionHandlerServerTest, StopBeforeRun) { Server()->Stop(); ServerThread()->Start(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } TEST_P(ExceptionHandlerServerTest, MultipleStops) { ServerThread()->Start(); Server()->Stop(); Server()->Stop(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDefault) { ScopedStopServerAndJoinThread stop_server(Server(), ServerThread()); ServerThread()->Start(); CrashDumpTest test(this, true); test.Run(); } TEST_P(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) { ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kNoPtrace, false); } TEST_P(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) { if (UsingMultiClientSocket()) { // The broker is not supported with multiple clients connected on a single // socket. return; } ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kUseBroker, true); } TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) { ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kDirectPtrace, true); } TEST_P(ExceptionHandlerServerTest, RequestCrashDumpError) { ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kError, false); } INSTANTIATE_TEST_SUITE_P(ExceptionHandlerServerTestSuite, ExceptionHandlerServerTest, testing::Bool() ); } // namespace } // namespace test } // namespace crashpad