linux: setup a signal stack

Bug: crashpad:340
Change-Id: I035d988bc8e76dbf80c07f0c92b07dbefeba8bd1
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2209768
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2020-07-15 11:26:08 -07:00 committed by Commit Bot
parent ef8a063055
commit 06a688ddc1
10 changed files with 396 additions and 22 deletions

View File

@ -175,3 +175,11 @@ source_set("client_test") {
data_deps += [ "../handler:crashpad_handler_console" ]
}
}
if (crashpad_is_linux || crashpad_is_android) {
source_set("pthread_create") {
sources = [ "pthread_create_linux.cc" ]
deps = [ ":client" ]
}
}

View File

@ -143,6 +143,29 @@ class CrashpadClient {
//! handler as this process' ptracer. -1 indicates that the handler's
//! process ID should be determined by communicating over the socket.
bool SetHandlerSocket(ScopedFileHandle sock, pid_t pid);
//! \brief Uses `sigaltstack()` to allocate a signal stack for the calling
//! thread.
//!
//! This method allocates an alternate stack to handle signals delivered to
//! the calling thread and should be called early in the lifetime of each
//! thread. Installing an alternate stack allows signals to be delivered in
//! the event that the call stack's stack pointer points to invalid memory,
//! as in the case of stack overflow.
//!
//! This method is called automatically by SetHandlerSocket() and
//! the various StartHandler() methods. It is harmless to call multiple times.
//! A new signal stack will be allocated only if there is no existing stack or
//! the existing stack is too small. The stack will be automatically freed
//! when the thread exits.
//!
//! An application might choose to diligently call this method from the start
//! routine for each thread, call it from a `pthread_create()` wrapper which
//! the application provides, or link the provided "client:pthread_create"
//! target.
//!
//! \return `true` on success. Otherwise `false` with a message logged.
static bool InitializeSignalStackForThread();
#endif // OS_ANDROID || OS_LINUX || DOXYGEN
#if defined(OS_ANDROID) || DOXYGEN

View File

@ -14,8 +14,11 @@
#include "client/crashpad_client.h"
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
@ -36,6 +39,7 @@
#include "util/linux/socket.h"
#include "util/misc/from_pointer_cast.h"
#include "util/posix/double_fork_and_exec.h"
#include "util/posix/scoped_mmap.h"
#include "util/posix/signals.h"
namespace crashpad {
@ -167,10 +171,14 @@ class SignalHandler {
~SignalHandler() = default;
bool Install(const std::set<int>* unhandled_signals) {
bool signal_stack_initialized =
CrashpadClient::InitializeSignalStackForThread();
DCHECK(signal_stack_initialized);
DCHECK(!handler_);
handler_ = this;
return Signals::InstallCrashHandlers(
HandleOrReraiseSignal, 0, &old_actions_, unhandled_signals);
HandleOrReraiseSignal, SA_ONSTACK, &old_actions_, unhandled_signals);
}
const ExceptionInformation& GetExceptionInfo() {
@ -413,6 +421,90 @@ bool CrashpadClient::SetHandlerSocket(ScopedFileHandle sock, pid_t pid) {
auto signal_handler = RequestCrashDumpHandler::Get();
return signal_handler->Initialize(std::move(sock), pid, &unhandled_signals_);
}
// static
bool CrashpadClient::InitializeSignalStackForThread() {
stack_t stack;
if (sigaltstack(nullptr, &stack) != 0) {
PLOG(ERROR) << "sigaltstack";
return false;
}
DCHECK_EQ(stack.ss_flags & SS_ONSTACK, 0);
const size_t page_size = getpagesize();
const size_t kStackSize = (SIGSTKSZ + page_size - 1) & ~(page_size - 1);
if (stack.ss_flags & SS_DISABLE || stack.ss_size < kStackSize) {
const size_t kGuardPageSize = page_size;
const size_t kStackAllocSize = kStackSize + 2 * kGuardPageSize;
static void (*stack_destructor)(void*) = [](void* stack_mem) {
const size_t page_size = getpagesize();
const size_t kGuardPageSize = page_size;
const size_t kStackSize = (SIGSTKSZ + page_size - 1) & ~(page_size - 1);
const size_t kStackAllocSize = kStackSize + 2 * kGuardPageSize;
stack_t stack;
stack.ss_flags = SS_DISABLE;
if (sigaltstack(&stack, &stack) != 0) {
PLOG(ERROR) << "sigaltstack";
} else if (stack.ss_sp !=
static_cast<char*>(stack_mem) + kGuardPageSize) {
PLOG_IF(ERROR, sigaltstack(&stack, nullptr) != 0) << "sigaltstack";
}
if (munmap(stack_mem, kStackAllocSize) != 0) {
PLOG(ERROR) << "munmap";
}
};
static pthread_key_t stack_key;
static int key_error = []() {
errno = pthread_key_create(&stack_key, stack_destructor);
PLOG_IF(ERROR, errno) << "pthread_key_create";
return errno;
}();
if (key_error) {
return false;
}
auto old_stack = static_cast<char*>(pthread_getspecific(stack_key));
if (old_stack) {
stack.ss_sp = old_stack + kGuardPageSize;
} else {
ScopedMmap stack_mem;
if (!stack_mem.ResetMmap(nullptr,
kStackAllocSize,
PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0)) {
return false;
}
if (mprotect(stack_mem.addr_as<char*>() + kGuardPageSize,
kStackSize,
PROT_READ | PROT_WRITE) != 0) {
PLOG(ERROR) << "mprotect";
return false;
}
stack.ss_sp = stack_mem.addr_as<char*>() + kGuardPageSize;
errno = pthread_setspecific(stack_key, stack_mem.release());
PCHECK(errno == 0) << "pthread_setspecific";
}
stack.ss_size = kStackSize;
stack.ss_flags =
(stack.ss_flags & SS_DISABLE) ? 0 : stack.ss_flags & SS_AUTODISARM;
if (sigaltstack(&stack, nullptr) != 0) {
PLOG(ERROR) << "sigaltstack";
return false;
}
}
return true;
}
#endif // OS_ANDROID || OS_LINUX
#if defined(OS_ANDROID)

View File

@ -15,6 +15,7 @@
#include "client/crashpad_client.h"
#include <dlfcn.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/types.h>
@ -30,6 +31,7 @@
#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"
@ -41,7 +43,9 @@
#include "util/linux/socket.h"
#include "util/misc/address_types.h"
#include "util/misc/from_pointer_cast.h"
#include "util/posix/scoped_mmap.h"
#include "util/posix/signals.h"
#include "util/thread/thread.h"
#if defined(OS_ANDROID)
#include <android/set_abort_message.h>
@ -56,22 +60,33 @@ namespace crashpad {
namespace test {
namespace {
enum class CrashType : uint32_t {
kSimulated,
kBuiltinTrap,
kInfiniteRecursion,
};
struct StartHandlerForSelfTestOptions {
bool start_handler_at_crash;
bool simulate_crash;
bool set_first_chance_handler;
bool crash_non_main_thread;
bool client_uses_signals;
CrashType crash_type;
};
class StartHandlerForSelfTest
: public testing::TestWithParam<std::tuple<bool, bool, bool>> {
: public testing::TestWithParam<
std::tuple<bool, bool, bool, bool, CrashType>> {
public:
StartHandlerForSelfTest() = default;
~StartHandlerForSelfTest() = default;
void SetUp() override {
std::tie(options_.start_handler_at_crash,
options_.simulate_crash,
options_.set_first_chance_handler) = GetParam();
options_.set_first_chance_handler,
options_.crash_non_main_thread,
options_.client_uses_signals,
options_.crash_type) = GetParam();
}
const StartHandlerForSelfTestOptions& Options() const { return options_; }
@ -164,6 +179,100 @@ void ValidateDump(const CrashReportDatabase::UploadReport* report) {
ADD_FAILURE();
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winfinite-recursion"
int RecurseInfinitely(int* ptr) {
int buf[1 << 20];
return *ptr + RecurseInfinitely(buf);
}
#pragma clang diagnostic pop
void DoCrash(const StartHandlerForSelfTestOptions& options,
CrashpadClient* client) {
switch (options.crash_type) {
case CrashType::kSimulated:
if (options.set_first_chance_handler) {
client->SetFirstChanceExceptionHandler(HandleCrashSuccessfully);
}
CRASHPAD_SIMULATE_CRASH();
break;
case CrashType::kBuiltinTrap:
__builtin_trap();
break;
case CrashType::kInfiniteRecursion:
int val = 42;
exit(RecurseInfinitely(&val));
break;
}
}
class ScopedAltSignalStack {
public:
ScopedAltSignalStack() = default;
~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;
constexpr 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_;
DISALLOW_COPY_AND_ASSIGN(ScopedAltSignalStack);
};
class CrashThread : public Thread {
public:
CrashThread(const StartHandlerForSelfTestOptions& options,
CrashpadClient* client)
: client_signal_stack_(), options_(options), client_(client) {}
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_;
DISALLOW_COPY_AND_ASSIGN(CrashThread);
};
CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) {
FileHandle in = StdioFileHandle(StdioStream::kStandardInput);
@ -176,6 +285,25 @@ CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) {
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;
WriteFile(out, &c, sizeof(c));
Signals::RestoreHandlerAndReraiseSignalOnReturn(
siginfo, old_actions.ActionForSignal(signo));
};
CHECK(Signals::InstallCrashHandlers(
client_handler, SA_ONSTACK, &old_actions));
}
base::FilePath handler_path = TestPaths::Executable().DirName().Append(
FILE_PATH_LITERAL("crashpad_handler"));
@ -202,17 +330,14 @@ CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) {
}
#endif
if (options.simulate_crash) {
if (options.set_first_chance_handler) {
client.SetFirstChanceExceptionHandler(HandleCrashSuccessfully);
}
CRASHPAD_SIMULATE_CRASH();
return EXIT_SUCCESS;
if (options.crash_non_main_thread) {
CrashThread thread(options, &client);
thread.Start();
thread.Join();
} else {
DoCrash(options, &client);
}
__builtin_trap();
NOTREACHED();
return EXIT_SUCCESS;
}
@ -221,8 +346,17 @@ class StartHandlerForSelfInChildTest : public MultiprocessExec {
StartHandlerForSelfInChildTest(const StartHandlerForSelfTestOptions& options)
: MultiprocessExec(), options_(options) {
SetChildTestMainFunction("StartHandlerForSelfTestChild");
if (!options.simulate_crash) {
SetExpectedChildTerminationBuiltinTrap();
switch (options.crash_type) {
case CrashType::kSimulated:
// kTerminationNormal, EXIT_SUCCESS
break;
case CrashType::kBuiltinTrap:
SetExpectedChildTerminationBuiltinTrap();
break;
case CrashType::kInfiniteRecursion:
SetExpectedChildTermination(TerminationReason::kTerminationSignal,
SIGSEGV);
break;
}
}
@ -247,6 +381,13 @@ class StartHandlerForSelfInChildTest : public MultiprocessExec {
ASSERT_TRUE(
LoggingWriteFile(WritePipeHandle(), &options_, sizeof(options_)));
if (options_.client_uses_signals && !options_.set_first_chance_handler &&
options_.crash_type != CrashType::kSimulated) {
// Wait for child's client signal handler.
char c;
EXPECT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c)));
}
// Wait for child to finish.
CheckedReadFileAtEOF(ReadPipeHandle());
@ -279,7 +420,8 @@ class StartHandlerForSelfInChildTest : public MultiprocessExec {
};
TEST_P(StartHandlerForSelfTest, StartHandlerInChild) {
if (Options().set_first_chance_handler && !Options().simulate_crash) {
if (Options().set_first_chance_handler &&
Options().crash_type != CrashType::kSimulated) {
// TODO(jperaza): test first chance handlers with real crashes.
return;
}
@ -287,11 +429,16 @@ TEST_P(StartHandlerForSelfTest, StartHandlerInChild) {
test.Run();
}
INSTANTIATE_TEST_SUITE_P(StartHandlerForSelfTestSuite,
StartHandlerForSelfTest,
testing::Combine(testing::Bool(),
testing::Bool(),
testing::Bool()));
INSTANTIATE_TEST_SUITE_P(
StartHandlerForSelfTestSuite,
StartHandlerForSelfTest,
testing::Combine(testing::Bool(),
testing::Bool(),
testing::Bool(),
testing::Bool(),
testing::Values(CrashType::kSimulated,
CrashType::kBuiltinTrap,
CrashType::kInfiniteRecursion)));
// Test state for starting the handler for another process.
class StartHandlerForClientTest {

View File

@ -0,0 +1,71 @@
// Copyright 2020 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 <dlfcn.h>
#include <pthread.h>
#include "base/logging.h"
#include "client/crashpad_client.h"
namespace {
using StartRoutineType = void* (*)(void*);
struct StartParams {
StartRoutineType start_routine;
void* arg;
};
void* InitializeSignalStackAndStart(StartParams* params) {
crashpad::CrashpadClient::InitializeSignalStackForThread();
StartParams local_params = *params;
delete params;
return local_params.start_routine(local_params.arg);
}
} // namespace
extern "C" {
__attribute__((visibility("default"))) int pthread_create(
pthread_t* thread,
const pthread_attr_t* attr,
StartRoutineType start_routine,
void* arg) {
static const auto next_pthread_create = []() {
const auto next_pthread_create =
reinterpret_cast<decltype(pthread_create)*>(
dlsym(RTLD_NEXT, "pthread_create"));
CHECK(next_pthread_create) << "dlsym: " << dlerror();
return next_pthread_create;
}();
StartParams* params = new StartParams;
params->start_routine = start_routine;
params->arg = arg;
int result = next_pthread_create(
thread,
attr,
reinterpret_cast<StartRoutineType>(InitializeSignalStackAndStart),
params);
if (result != 0) {
delete params;
}
return result;
}
} // extern "C"

View File

@ -17,6 +17,11 @@
#include_next <signal.h>
// Missing from glibc and bionic
#if !defined(SS_AUTODISARM)
#define SS_AUTODISARM (1u << 31)
#endif
// Missing from glibc and bionic-x86_64
#if defined(__x86_64__) || defined(__i386__)

View File

@ -156,6 +156,10 @@ if (!crashpad_is_ios) {
[ "//third_party/mini_chromium/mini_chromium/build:win_windowed" ]
}
}
if (crashpad_is_linux) {
deps += [ "../client:pthread_create" ]
}
}
}

View File

@ -121,4 +121,11 @@ bool ScopedMmap::Mprotect(int prot) {
return true;
}
void* ScopedMmap::release() {
void* retval = addr_;
addr_ = MAP_FAILED;
len_ = 0;
return retval;
}
} // namespace crashpad

View File

@ -77,6 +77,10 @@ class ScopedMmap {
//! \return `true` on success. `false` on failure, with a message logged.
bool Mprotect(int prot);
//! \brief Returns the base address of the memory-mapped region and releases
//! ownership.
void* release();
//! \return Whether this object is managing a valid memory-mapped region.
bool is_valid() const { return addr_ != MAP_FAILED; }

View File

@ -403,6 +403,19 @@ TEST(ScopedMmapDeathTest, Mprotect) {
*addr = 2;
}
TEST(ScopedMmapTest, Release) {
ScopedMmap mapping;
const size_t kPageSize = base::checked_cast<size_t>(getpagesize());
ASSERT_TRUE(ScopedMmapResetMmap(&mapping, kPageSize));
ASSERT_TRUE(mapping.is_valid());
ScopedMmap mapping2;
ASSERT_TRUE(mapping2.ResetAddrLen(mapping.release(), kPageSize));
EXPECT_TRUE(mapping2.is_valid());
EXPECT_FALSE(mapping.is_valid());
}
} // namespace
} // namespace test
} // namespace crashpad