crashpad/handler/linux/exception_handler_server_test.cc

384 lines
11 KiB
C++
Raw Normal View History

// Copyright 2017 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 "handler/linux/exception_handler_server.h"
#include <sys/types.h>
#include <unistd.h>
#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"
Use BUILDFLAG for OS checking Use BUILDFLAG(IS_*) instead of defined(OS_*). This was generated mostly mechnically by performing the following steps: - sed -i '' -E -e 's/defined\(OS_/BUILDFLAG(IS_/g' \ -e 's%([ !])OS_([A-Z]+)%\1BUILDFLAG(IS_\2)%g' \ $(git grep -l 'OS_' '**/*.c' '**/*.cc' '**/*.h' '**/*.m' '**/*.mm') - sed -i '' -e 's/#ifdef BUILDFLAG(/#if BUILDFLAG(/' \ $(git grep -l '#ifdef BUILDFLAG(' '**/*.c' '**/*.cc' '**/*.h' '**/*.m' '**/*.mm') - gsed -i -z -E -e \ 's%(.*)#include "%\1#include "build/buildflag.h"\n#include "%' \ $(git grep -l 'BUILDFLAG(IS_' '**/*.c' '**/*.cc' '**/*.h' '**/*.m' '**/*.mm') - Spot checks to move #include "build/buildflag.h" to the correct parts of files. - sed -i '' -E -e \ 's%^(#include "build/buildflag.h")$%#include "build/build_config.h"\n\1%' \ $(grep -L '^#include "build/build_config.h"$' $(git grep -l 'BUILDFLAG(IS_' '**/*.c' '**/*.cc' '**/*.h' '**/*.m' '**/*.mm')) - Add “clang-format off” around tool usage messages. - git cl format - Update mini_chromium to 85ba51f98278 (intermediate step). TESTING ONLY). - for f in $(git grep -l '^#include "build/buildflag.h"$' '**/*.c' '**/*.cc' '**/*.h' '**/*.m' '**/*.mm'); do \ grep -v '^#include "build/buildflag.h"$' "${f}" > /tmp/z; \ cp /tmp/z "${f}"; done - git cl format - Update mini_chromium to 735143774c5f (intermediate step). - Update mini_chromium to f41420eb45fa (as checked in). - Update mini_chromium to 6e2f204b4ae1 (as checked in). For ease of review and inspection, each of these steps is uploaded as a new patch set in a review series. This includes an update of mini_chromium to 6e2f204b4ae1: f41420eb45fa Use BUILDFLAG for OS checking 6e2f204b4ae1 Include what you use: string_util.h uses build_config.h Bug: chromium:1234043 Change-Id: Ieef86186f094c64e59b853729737e36982f8cf69 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3400258 Reviewed-by: Joshua Peraza <jperaza@chromium.org> Commit-Queue: Mark Mentovai <mark@chromium.org>
2022-01-19 15:00:24 -05:00
#if BUILDFLAG(IS_ANDROID)
#include <android/api-level.h>
#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(const RunServerThread&) = delete;
RunServerThread& operator=(const RunServerThread&) = delete;
~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_;
};
class ScopedStopServerAndJoinThread {
public:
ScopedStopServerAndJoinThread(ExceptionHandlerServer* server,
RunServerThread* thread)
: server_(server), thread_(thread) {}
ScopedStopServerAndJoinThread(const ScopedStopServerAndJoinThread&) = delete;
ScopedStopServerAndJoinThread& operator=(
const ScopedStopServerAndJoinThread&) = delete;
~ScopedStopServerAndJoinThread() {
server_->Stop();
EXPECT_TRUE(thread_->JoinWithTimeout(5.0));
}
private:
ExceptionHandlerServer* server_;
RunServerThread* thread_;
};
class TestDelegate : public ExceptionHandlerServer::Delegate {
public:
TestDelegate()
: Delegate(), last_exception_address_(0), last_client_(-1), sem_(0) {}
TestDelegate(const TestDelegate&) = delete;
TestDelegate& operator=(const TestDelegate&) = delete;
~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_;
};
class MockPtraceStrategyDecider : public PtraceStrategyDecider {
public:
MockPtraceStrategyDecider(PtraceStrategyDecider::Strategy strategy)
: PtraceStrategyDecider(), strategy_(strategy) {}
MockPtraceStrategyDecider(const MockPtraceStrategyDecider&) = delete;
MockPtraceStrategyDecider& operator=(const MockPtraceStrategyDecider&) =
delete;
~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_;
};
class ExceptionHandlerServerTest : public testing::TestWithParam<bool> {
public:
ExceptionHandlerServerTest()
: server_(),
delegate_(),
server_thread_(&server_, &delegate_),
sock_to_handler_(),
use_multi_client_socket_(GetParam()) {}
ExceptionHandlerServerTest(const ExceptionHandlerServerTest&) = delete;
ExceptionHandlerServerTest& operator=(const ExceptionHandlerServerTest&) =
delete;
~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(const CrashDumpTest&) = delete;
CrashDumpTest& operator=(const CrashDumpTest&) = delete;
~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_;
};
void ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy strategy,
bool succeeds) {
Server()->SetPtraceStrategyDecider(
std::make_unique<MockPtraceStrategyDecider>(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_;
};
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