mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-26 23:01:05 +08:00
6265674c98
This was tested locally by adding "-Wunreachable-code-aggressive" after making NOTREACHED() [[noreturn]] in mini_chromium and then getting that to compile. Bug: chromium:40580068 Change-Id: I7ec1c72be1d73436d128660a621e9060eaebaee8 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/5780891 Reviewed-by: Mark Mentovai <mark@chromium.org>
789 lines
25 KiB
C++
789 lines
25 KiB
C++
// 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();
|
|
}
|
|
|
|
[[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
|