From 06a688ddc1bc8be6f410e69e4fb413fc19594d04 Mon Sep 17 00:00:00 2001 From: Joshua Peraza Date: Wed, 15 Jul 2020 11:26:08 -0700 Subject: [PATCH] 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 Reviewed-by: Mark Mentovai --- client/BUILD.gn | 8 ++ client/crashpad_client.h | 23 ++++ client/crashpad_client_linux.cc | 94 ++++++++++++- client/crashpad_client_linux_test.cc | 189 ++++++++++++++++++++++++--- client/pthread_create_linux.cc | 71 ++++++++++ compat/linux/signal.h | 5 + handler/BUILD.gn | 4 + util/posix/scoped_mmap.cc | 7 + util/posix/scoped_mmap.h | 4 + util/posix/scoped_mmap_test.cc | 13 ++ 10 files changed, 396 insertions(+), 22 deletions(-) create mode 100644 client/pthread_create_linux.cc diff --git a/client/BUILD.gn b/client/BUILD.gn index 3dbf23c5..f448d35e 100644 --- a/client/BUILD.gn +++ b/client/BUILD.gn @@ -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" ] + } +} diff --git a/client/crashpad_client.h b/client/crashpad_client.h index 6f8c2fe0..62941ddf 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -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 diff --git a/client/crashpad_client_linux.cc b/client/crashpad_client_linux.cc index 4fb99d84..55d1fc62 100644 --- a/client/crashpad_client_linux.cc +++ b/client/crashpad_client_linux.cc @@ -14,8 +14,11 @@ #include "client/crashpad_client.h" +#include #include +#include #include +#include #include #include #include @@ -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* 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(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(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() + kGuardPageSize, + kStackSize, + PROT_READ | PROT_WRITE) != 0) { + PLOG(ERROR) << "mprotect"; + return false; + } + + stack.ss_sp = stack_mem.addr_as() + 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) diff --git a/client/crashpad_client_linux_test.cc b/client/crashpad_client_linux_test.cc index 47684b1a..33863b37 100644 --- a/client/crashpad_client_linux_test.cc +++ b/client/crashpad_client_linux_test.cc @@ -15,6 +15,7 @@ #include "client/crashpad_client.h" #include +#include #include #include #include @@ -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 @@ -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> { + : public testing::TestWithParam< + std::tuple> { 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 { diff --git a/client/pthread_create_linux.cc b/client/pthread_create_linux.cc new file mode 100644 index 00000000..d5a60fe1 --- /dev/null +++ b/client/pthread_create_linux.cc @@ -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 +#include + +#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( + 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(InitializeSignalStackAndStart), + params); + if (result != 0) { + delete params; + } + return result; +} + +} // extern "C" diff --git a/compat/linux/signal.h b/compat/linux/signal.h index 4e1cdc1f..a0072466 100644 --- a/compat/linux/signal.h +++ b/compat/linux/signal.h @@ -17,6 +17,11 @@ #include_next +// 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__) diff --git a/handler/BUILD.gn b/handler/BUILD.gn index 472c1477..4fb72649 100644 --- a/handler/BUILD.gn +++ b/handler/BUILD.gn @@ -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" ] + } } } diff --git a/util/posix/scoped_mmap.cc b/util/posix/scoped_mmap.cc index c7a1dd27..0c98ba24 100644 --- a/util/posix/scoped_mmap.cc +++ b/util/posix/scoped_mmap.cc @@ -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 diff --git a/util/posix/scoped_mmap.h b/util/posix/scoped_mmap.h index 9f22372b..b497d944 100644 --- a/util/posix/scoped_mmap.h +++ b/util/posix/scoped_mmap.h @@ -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; } diff --git a/util/posix/scoped_mmap_test.cc b/util/posix/scoped_mmap_test.cc index 0b3c1ce8..9f6efdf8 100644 --- a/util/posix/scoped_mmap_test.cc +++ b/util/posix/scoped_mmap_test.cc @@ -403,6 +403,19 @@ TEST(ScopedMmapDeathTest, Mprotect) { *addr = 2; } +TEST(ScopedMmapTest, Release) { + ScopedMmap mapping; + + const size_t kPageSize = base::checked_cast(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