From bf2c5155d28f4756a242ead9b6cac25d958e0a91 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Wed, 1 Mar 2017 10:00:59 -0500 Subject: [PATCH] Add Signals, utilities for signal handling Use these utilities for signal handling in crashpad_handler BUG=crashpad:30 TEST=crashpad_util_test Signals.* Change-Id: I6c9a1de35c4a81b58d77768c4753bdba5ebea4df Reviewed-on: https://chromium-review.googlesource.com/446917 Commit-Queue: Mark Mentovai Reviewed-by: Robert Sesek --- handler/handler_main.cc | 127 +------- util/posix/signals.cc | 281 ++++++++++++++++++ util/posix/signals.h | 228 +++++++++++++++ util/posix/signals_test.cc | 579 +++++++++++++++++++++++++++++++++++++ util/util.gyp | 2 + util/util_test.gyp | 1 + 6 files changed, 1102 insertions(+), 116 deletions(-) create mode 100644 util/posix/signals.cc create mode 100644 util/posix/signals.h create mode 100644 util/posix/signals_test.cc diff --git a/handler/handler_main.cc b/handler/handler_main.cc index 5006cf67..54b857bc 100644 --- a/handler/handler_main.cc +++ b/handler/handler_main.cc @@ -59,6 +59,7 @@ #include "util/mach/child_port_handshake.h" #include "util/mach/mach_extensions.h" #include "util/posix/close_stdio.h" +#include "util/posix/signals.h" #elif defined(OS_WIN) #include @@ -145,29 +146,6 @@ class CallMetricsRecordNormalExit { #if defined(OS_MACOSX) -void InstallSignalHandler(const std::vector& signals, - void (*handler)(int, siginfo_t*, void*)) { - struct sigaction sa = {}; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - sa.sa_sigaction = handler; - - for (int sig : signals) { - int rv = sigaction(sig, &sa, nullptr); - PCHECK(rv == 0) << "sigaction " << sig; - } -} - -void RestoreDefaultSignalHandler(int sig) { - struct sigaction sa = {}; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sa.sa_handler = SIG_DFL; - int rv = sigaction(sig, &sa, nullptr); - DPLOG_IF(ERROR, rv != 0) << "sigaction " << sig; - ALLOW_UNUSED_LOCAL(rv); -} - void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) { MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed); @@ -202,99 +180,19 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) { } Metrics::HandlerCrashed(metrics_code); - RestoreDefaultSignalHandler(sig); - - // If the signal was received synchronously resulting from a hardware fault, - // returning from the signal handler will cause the kernel to re-raise it, - // because this handler hasn’t done anything to alleviate the condition that - // caused the signal to be raised in the first place. With the default signal - // handler in place, it will cause the same behavior to be taken as though - // this signal handler had never been installed at all (expected to be a - // crash). This is ideal, because the signal is re-raised with the same - // properties and from the same context that initially triggered it, providing - // the best debugging experience. - - if ((sig != SIGILL && sig != SIGFPE && sig != SIGBUS && sig != SIGSEGV) || - !si_code_valid) { - // Signals received other than via hardware faults, such as those raised - // asynchronously via kill() and raise(), and those arising via hardware - // traps such as int3 (resulting in SIGTRAP but advancing the instruction - // pointer), will not reoccur on their own when returning from the signal - // handler. Re-raise them. - // - // Unfortunately, when SIGBUS is received asynchronously via kill(), - // siginfo->si_code makes it appear as though it was actually received via a - // hardware fault. See 10.12.3 xnu-3789.41.3/bsd/dev/i386/unix_signal.c - // sendsig(). An asynchronous SIGBUS will thus cause the handler-crashed - // metric to be logged but will not cause the process to terminate. This - // isn’t ideal, but asynchronous SIGBUS is an unexpected condition. The - // alternative, to re-raise here on any SIGBUS, is a bad idea because it - // would lose properties associated with the the original signal, which are - // very valuable for debugging and are visible to a Mach exception handler. - // Since SIGBUS is normally received synchronously in response to a hardware - // fault, don’t sweat the unexpected asynchronous case. - // - // Because this signal handler executes with the signal blocked, this - // raise() cannot immediately deliver the signal. Delivery is deferred until - // this signal handler returns and the signal becomes unblocked. The - // re-raised signal will appear with the same context as where it was - // initially triggered. - int rv = raise(sig); - DPLOG_IF(ERROR, rv != 0) << "raise"; - ALLOW_UNUSED_LOCAL(rv); - } + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); } void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) { MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated); - - RestoreDefaultSignalHandler(sig); - - // Re-raise the signal. See the explanation in HandleCrashSignal(). Note that - // no checks for signals arising from synchronous hardware faults are made - // because termination signals never originate in that way. - int rv = raise(sig); - DPLOG_IF(ERROR, rv != 0) << "raise"; - ALLOW_UNUSED_LOCAL(rv); + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); } void InstallCrashHandler() { - // These are the core-generating signals from 10.12.3 - // xnu-3789.41.3/bsd/sys/signalvar.h sigprop: entries with SA_CORE are in the - // set. - const int kCrashSignals[] = {SIGQUIT, - SIGILL, - SIGTRAP, - SIGABRT, - SIGEMT, - SIGFPE, - SIGBUS, - SIGSEGV, - SIGSYS}; - InstallSignalHandler( - std::vector(&kCrashSignals[0], - &kCrashSignals[arraysize(kCrashSignals)]), - HandleCrashSignal); + Signals::InstallCrashHandlers(HandleCrashSignal, 0, nullptr); - // Not a crash handler, but close enough. These are non-core-generating but - // terminating signals from 10.12.3 xnu-3789.41.3/bsd/sys/signalvar.h sigprop: - // entries with SA_KILL but not SA_CORE are in the set. SIGKILL is excluded - // because it is uncatchable. - const int kTerminateSignals[] = {SIGHUP, - SIGINT, - SIGPIPE, - SIGALRM, - SIGTERM, - SIGXCPU, - SIGXFSZ, - SIGVTALRM, - SIGPROF, - SIGUSR1, - SIGUSR2}; - InstallSignalHandler( - std::vector(&kTerminateSignals[0], - &kTerminateSignals[arraysize(kTerminateSignals)]), - HandleTerminateSignal); + // Not a crash handler, but close enough. + Signals::InstallTerminateHandlers(HandleTerminateSignal, 0, nullptr); } struct ResetSIGTERMTraits { @@ -617,7 +515,7 @@ int HandlerMain(int argc, char* argv[]) { base::AutoReset reset_g_exception_handler_server( &g_exception_handler_server, &exception_handler_server); - struct sigaction old_sa; + struct sigaction old_sigterm_action; ScopedResetSIGTERM reset_sigterm; if (!options.mach_service.empty()) { // When running from launchd, no no-senders notification could ever be @@ -627,13 +525,10 @@ int HandlerMain(int argc, char* argv[]) { // // Set up a SIGTERM handler that will call exception_handler_server.Stop(). // This replaces the HandleTerminateSignal handler for SIGTERM. - struct sigaction sa = {}; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - sa.sa_sigaction = HandleSIGTERM; - int rv = sigaction(SIGTERM, &sa, &old_sa); - PCHECK(rv == 0) << "sigaction"; - reset_sigterm.reset(&old_sa); + if (Signals::InstallHandler( + SIGTERM, HandleSIGTERM, 0, &old_sigterm_action)) { + reset_sigterm.reset(&old_sigterm_action); + } } #elif defined(OS_WIN) // Shut down as late as possible relative to programs we're watching. diff --git a/util/posix/signals.cc b/util/posix/signals.cc new file mode 100644 index 00000000..a19fbafb --- /dev/null +++ b/util/posix/signals.cc @@ -0,0 +1,281 @@ +// 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 "util/posix/signals.h" + +#include + +#include + +#include "base/logging.h" + +namespace crashpad { + +namespace { + +// These are the core-generating signals. +// +// On macOS, these come from 10.12.3 xnu-3789.41.3/bsd/sys/signalvar.h sigprop: +// entries with SA_CORE are in the set. +// +// For Linux, see linux-4.4.52/kernel/signal.c get_signal() and +// linux-4.4.52/include/linux/signal.h sig_kernel_coredump(): signals in +// SIG_KERNEL_COREDUMP_MASK are in the set. +constexpr int kCrashSignals[] = { + SIGABRT, + SIGBUS, + SIGFPE, + SIGILL, + SIGQUIT, + SIGSEGV, + SIGSYS, + SIGTRAP, +#if defined(SIGEMT) + SIGEMT, +#endif // defined(SIGEMT) +#if defined(OS_LINUX) + SIGXCPU, + SIGXFSZ, +#endif // defined(OS_LINUX) +}; + +// These are the non-core-generating but terminating signals. +// +// On macOS, these come from 10.12.3 xnu-3789.41.3/bsd/sys/signalvar.h sigprop: +// entries with SA_KILL but not SA_CORE are in the set. SIGKILL is excluded +// because it is uncatchable. +// +// For Linux, see linux-4.4.52/kernel/signal.c get_signal() and +// linux-4.4.52/include/linux/signal.h sig_kernel_coredump(), +// sig_kernel_ignore(), and sig_kernel_stop(): signals not in +// SIG_KERNEL_COREDUMP_MASK, SIG_KERNEL_IGNORE_MASK, or SIG_KERNEL_STOP_MASK are +// in the set. SIGKILL is excluded because it is uncatchable (it’s in +// SIG_KERNEL_ONLY_MASK and qualifies for sig_kernel_only()). Real-time signals +// in the range [SIGRTMIN, SIGRTMAX) also have termination as the default +// action, although they are not listed here. +constexpr int kTerminateSignals[] = { + SIGALRM, + SIGHUP, + SIGINT, + SIGPIPE, + SIGPROF, + SIGTERM, + SIGUSR1, + SIGUSR2, + SIGVTALRM, +#if defined(SIGPWR) + SIGPWR, +#endif // defined(SIGPWR) +#if defined(SIGSTKFLT) + SIGSTKFLT, +#endif // defined(SIGSTKFLT) +#if defined(OS_MACOSX) + SIGXCPU, + SIGXFSZ, +#endif // defined(OS_MACOSX) +#if defined(OS_LINUX) + SIGIO, +#endif // defined(OS_LINUX) +}; + +bool InstallHandlers(const std::vector& signals, + Signals::Handler handler, + int flags, + Signals::OldActions* old_actions) { + bool success = true; + for (int sig : signals) { + success &= Signals::InstallHandler( + sig, + handler, + flags, + old_actions ? old_actions->ActionForSignal(sig) : nullptr); + } + return success; +} + +bool IsSignalInSet(int sig, const int* set, size_t set_size) { + for (size_t index = 0; index < set_size; ++index) { + if (sig == set[index]) { + return true; + } + } + return false; +} + +} // namespace + +struct sigaction* Signals::OldActions::ActionForSignal(int sig) { + DCHECK_GT(sig, 0); + const size_t slot = sig - 1; + DCHECK_LT(slot, arraysize(actions_)); + return &actions_[slot]; +} + +// static +bool Signals::InstallHandler(int sig, + Handler handler, + int flags, + struct sigaction* old_action) { + struct sigaction action; + sigemptyset(&action.sa_mask); + action.sa_flags = flags | SA_SIGINFO; + action.sa_sigaction = handler; + if (sigaction(sig, &action, old_action) != 0) { + PLOG(ERROR) << "sigaction " << sig; + return false; + } + return true; +} + +// static +bool Signals::InstallCrashHandlers(Handler handler, + int flags, + OldActions* old_actions) { + return InstallHandlers( + std::vector(kCrashSignals, kCrashSignals + arraysize(kCrashSignals)), + handler, + flags, + old_actions); +} + +// static +bool Signals::InstallTerminateHandlers(Handler handler, + int flags, + OldActions* old_actions) { + return InstallHandlers( + std::vector(kTerminateSignals, + kTerminateSignals + arraysize(kTerminateSignals)), + handler, + flags, + old_actions); +} + +// static +bool Signals::WillSignalReraiseAutonomously(const siginfo_t* siginfo) { + // Signals received other than via hardware faults, such as those raised + // asynchronously via kill() and raise(), and those arising via hardware traps + // such as int3 on x86 (resulting in SIGTRAP but advancing the instruction + // pointer), will not reoccur on their own when returning from the signal + // handler. + // + // Unfortunately, on macOS, when SIGBUS is received asynchronously via kill(), + // siginfo->si_code makes it appear as though it was actually received via a + // hardware fault. See 10.12.3 xnu-3789.41.3/bsd/dev/i386/unix_signal.c + // sendsig(). Asynchronous SIGBUS will not re-raise itself autonomously, but + // this function (acting on information from the kernel) behaves as though it + // will. This isn’t ideal, but asynchronous SIGBUS is an unexpected condition. + // The alternative, to never treat SIGBUS as autonomously re-raising, is a bad + // idea because the explicit re-raise would lose properties associated with + // the the original signal, which are valuable for debugging and are visible + // to a Mach exception handler. Since SIGBUS is normally received + // synchronously in response to a hardware fault, don’t sweat the unexpected + // asynchronous case. + // + // SIGSEGV on macOS originating from a general protection fault is a more + // difficult case: si_code is cleared, making the signal appear asynchronous. + // See 10.12.3 xnu-3789.41.3/bsd/dev/i386/unix_signal.c sendsig(). + const int sig = siginfo->si_signo; + const int code = siginfo->si_code; + + // Only these signals can be generated from hardware faults and can re-raise + // autonomously. + return (sig == SIGBUS || + sig == SIGFPE || + sig == SIGILL || + sig == SIGSEGV) && + + // The signal was only generated from a hardware fault if the code is a + // positive number not matching one of these SI_* constants. See + // “Signal Actions” under XRAT “Rationale”/B.2.4 “Signal Concepts” in + // POSIX.1-2008, 2016 Edition, regarding si_code. The historical + // behavior does not use these SI_* constants and signals generated + // asynchronously show up with a code of 0. On macOS, the SI_* + // constants are defined but never used, and the historical value of 0 + // remains. See 10.12.3 xnu-3789.41.3/bsd/kern/kern_sig.c + // psignal_internal(). + (code > 0 && + code != SI_ASYNCIO && + code != SI_MESGQ && + code != SI_QUEUE && + code != SI_TIMER && + code != SI_USER && +#if defined(SI_DETHREAD) + code != SI_DETHREAD && +#endif // defiend(SI_DETHREAD) +#if defined(SI_KERNEL) + // In Linux, SI_KERNEL is used for signals that are raised by the + // kernel in software, opposing SI_USER. See + // linux-4.4.52/kernel/signal.c __send_signal(). Signals originating + // from hardware faults do not use this SI_KERNEL, but a proper signal + // code translated in architecture-specific code from the + // characteristics of the hardware fault. + code != SI_KERNEL && +#endif // defined(SI_KERNEL) +#if defined(SI_SIGIO) + code != SI_SIGIO && +#endif // defined(SI_SIGIO) +#if defined(SI_TKILL) + code != SI_TKILL && +#endif // defined(SI_TKILL) + true); +} + +// static +void Signals::RestoreHandlerAndReraiseSignalOnReturn( + const siginfo_t* siginfo, + const struct sigaction* old_action) { + // Failures in this function should _exit(kFailureExitCode). This is a quick + // and quiet failure. This function runs in signal handler context, and it’s + // difficult to safely be loud from a signal handler. + const int kFailureExitCode = 191; + + struct sigaction default_action; + sigemptyset(&default_action.sa_mask); + default_action.sa_flags = 0; + default_action.sa_handler = SIG_DFL; + + const struct sigaction* restore_action = + old_action ? old_action : &default_action; + + // Try to restore restore_action. If that fails and restore_action was + // old_action, the problem may have been that old_action was bogus, so try to + // set the default action. + const int sig = siginfo->si_signo; + if (sigaction(sig, restore_action, nullptr) != 0 && old_action && + sigaction(sig, &default_action, nullptr) != 0) { + _exit(kFailureExitCode); + } + + // Explicitly re-raise the signal if it will not re-raise itself. Because + // signal handlers normally execute with their signal blocked, this raise() + // cannot immediately deliver the signal. Delivery is deferred until the + // signal handler returns and the signal becomes unblocked. The re-raised + // signal will appear with the same context as where it was initially + // triggered. + if (!WillSignalReraiseAutonomously(siginfo) && raise(sig) != 0) { + _exit(kFailureExitCode); + } +} + +// static +bool Signals::IsCrashSignal(int sig) { + return IsSignalInSet(sig, kCrashSignals, arraysize(kCrashSignals)); +} + +// static +bool Signals::IsTerminateSignal(int sig) { + return IsSignalInSet(sig, kTerminateSignals, arraysize(kTerminateSignals)); +} + +} // namespace crashpad diff --git a/util/posix/signals.h b/util/posix/signals.h new file mode 100644 index 00000000..36d33cd6 --- /dev/null +++ b/util/posix/signals.h @@ -0,0 +1,228 @@ +// 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. + +#ifndef CRASHPAD_UTIL_POSIX_SIGNALS_H_ +#define CRASHPAD_UTIL_POSIX_SIGNALS_H_ + +#include + +#include "base/macros.h" + +namespace crashpad { + +//! \brief Utilities for handling POSIX signals. +class Signals { + public: + //! \brief The type used for `struct sigaction::sa_sigaction`. + using Handler = void (*)(int, siginfo_t*, void*); + + //! \brief A group of `struct sigaction` structures corresponding to a set + //! of signals’ previous actions, addressable by signal number. + //! + //! This type is used to store previous signal actions when new actions are + //! installed in batch by InstallCrashHandlers() or + //! InstallTerminateHandlers(). + //! + //! This object is not initialized by any constructor. Its expected initial + //! state is to have its contents filled with zeroes. Because signal handlers + //! are stateless (there is no “context” parameter), any state must be + //! accessed via objects of static storage duration, and it is expected that + //! objects of this class will only ever exist with static storage duration, + //! which in the absence of a constructor will be zero-initialized as + //! expected. In the event that an object of this class must exist with a + //! different storage duration, such as automatic or dynamic storage duration, + //! it must be explicitly initialized. For example: `OldActions old_actions = + //! {};`. + class OldActions { + public: + // DISALLOW_COPY_AND_ASSIGN removes the default constructor, so explicitly + // opt for it. This should not result in any static initialization code even + // when an object of this class is given static storage duration. + OldActions() = default; + + //! \brief Returns a `struct sigaction` structure corresponding to the + //! given signal. + //! + //! \note This method is safe to call from a signal handler. + struct sigaction* ActionForSignal(int sig); + + private: + // As a small storage optimization, don’t waste any space on a slot for + // signal 0, because there is no signal 0. + struct sigaction actions_[NSIG - 1]; + + DISALLOW_COPY_AND_ASSIGN(OldActions); + }; + + //! \brief Installs a new signal handler. + //! + //! \param[in] sig The signal number to handle. + //! \param[in] handler A signal-handling function to execute, used as the + //! `struct sigaction::sa_sigaction` field when calling `sigaction()`. + //! \param[in] flags Flags to pass to `sigaction()` in the `struct + //! sigaction::sa_flags` field. `SA_SIGINFO` will be specified implicitly. + //! \param[out] old_action The previous action for the signal, replaced by the + //! new action. May be `nullptr` if not needed. + //! + //! \return `true` on success. `false` on failure with a message logged. + //! + //! \warning This function may not be called from a signal handler because of + //! its use of logging. See RestoreHandlerAndReraiseSignalOnReturn() + //! instead. + static bool InstallHandler(int sig, + Handler handler, + int flags, + struct sigaction* old_action); + + //! \brief Installs a new signal handler for all signals associated with + //! crashes. + //! + //! Signals associated with crashes are those whose default dispositions + //! involve creating a core dump. The precise set of signals involved varies + //! between operating systems. + //! + //! A single signal may either be associated with a crash or with termination + //! (see InstallTerminateHandlers()), and perhaps neither, but never both. + //! + //! \param[in] handler A signal-handling function to execute, used as the + //! `struct sigaction::sa_sigaction` field when calling `sigaction()`. + //! \param[in] flags Flags to pass to `sigaction()` in the `struct + //! sigaction::sa_flags` field. `SA_SIGINFO` will be specified implicitly. + //! \param[out] old_actions The previous actions for the signals, replaced by + //! the new action. May be `nullptr` if not needed. The same \a + //! old_actions object may be used for calls to both this function and + //! InstallTerminateHandlers(). + //! + //! \return `true` on success. `false` on failure with a message logged. + //! + //! \warning This function may not be called from a signal handler because of + //! its use of logging. See RestoreHandlerAndReraiseSignalOnReturn() + //! instead. + static bool InstallCrashHandlers(Handler handler, + int flags, + OldActions* old_actions); + + //! \brief Installs a new signal handler for all signals associated with + //! termination. + //! + //! Signals associated with termination are those whose default dispositions + //! involve terminating the process without creating a core dump. The precise + //! set of signals involved varies between operating systems. + //! + //! A single signal may either be associated with termination or with a + //! crash (see InstalCrashHandlers()), and perhaps neither, but never both. + //! + //! \param[in] handler A signal-handling function to execute, used as the + //! `struct sigaction::sa_sigaction` field when calling `sigaction()`. + //! \param[in] flags Flags to pass to `sigaction()` in the `struct + //! sigaction::sa_flags` field. `SA_SIGINFO` will be specified implicitly. + //! \param[out] old_actions The previous actions for the signals, replaced by + //! the new action. May be `nullptr` if not needed. The same \a + //! old_actions object may be used for calls to both this function and + //! InstallCrashHandlers(). + //! + //! \return `true` on success. `false` on failure with a message logged. + //! + //! \warning This function may not be called from a signal handler because of + //! its use of logging. See RestoreHandlerAndReraiseSignalOnReturn() + //! instead. + static bool InstallTerminateHandlers(Handler handler, + int flags, + OldActions* old_actions); + + //! \brief Determines whether a signal will be re-raised autonomously upon + //! return from a signal handler. + //! + //! Certain signals, when generated synchronously in response to a hardware + //! fault, are unrecoverable. Upon return from the signal handler, the same + //! action that triggered the signal to be raised initially will be retried, + //! and unless the signal handler took action to mitigate this error, the same + //! signal will be re-raised. As an example, a CPU will not be able to read + //! unmapped memory (causing `SIGSEGV`), thus the signal will be re-raised + //! upon return from the signal handler unless the signal handler established + //! a memory mapping where required. + //! + //! It is important to distinguish between these synchronous signals generated + //! in response to a hardware fault and signals generated asynchronously or in + //! software. As an example, `SIGSEGV` will not re-raise autonomously if sent + //! by `kill()`. + //! + //! This function distinguishes between signals that can re-raise + //! autonomously, and for those that can, between instances of the signal that + //! were generated synchronously in response to a hardware fault and instances + //! that were generated by other means. + //! + //! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal + //! handler. + //! + //! \return `true` if the signal being handled will re-raise itself + //! autonomously upon return from a signal handler. `false` if it will + //! not. When this function returns `false`, a signal can still be + //! re-raised upon return from a signal handler by calling `raise()` from + //! within the signal handler. + //! + //! \note This function is safe to call from a signal handler. + static bool WillSignalReraiseAutonomously(const siginfo_t* siginfo); + + //! \brief Restores a previous signal action and arranges to re-raise a signal + //! on return from a signal handler. + //! + //! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal + //! handler. + //! \param[in] old_action The previous action for the signal, which will be + //! re-established as the signal’s action. May be `nullptr`, which directs + //! the default action for the signal to be used. + //! + //! If this function fails, it will immediately call `_exit()` and set an exit + //! status of `191`. + //! + //! \note This function may only be called from a signal handler. + static void RestoreHandlerAndReraiseSignalOnReturn( + const siginfo_t* siginfo, + const struct sigaction* old_action); + + //! \brief Determines whether a signal is associated with a crash. + //! + //! Signals associated with crashes are those whose default dispositions + //! involve creating a core dump. The precise set of signals involved varies + //! between operating systems. + //! + //! \param[in] sig The signal to test. + //! + //! \return `true` if \a sig is associated with a crash. `false` otherwise. + //! + //! \note This function is safe to call from a signal handler. + static bool IsCrashSignal(int sig); + + //! \brief Determines whether a signal is associated with termination. + //! + //! Signals associated with termination are those whose default dispositions + //! involve terminating the process without creating a core dump. The precise + //! set of signals involved varies between operating systems. + //! + //! \param[in] sig The signal to test. + //! + //! \return `true` if \a sig is associated with termination. `false` + //! otherwise. + //! + //! \note This function is safe to call from a signal handler. + static bool IsTerminateSignal(int sig); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Signals); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_POSIX_SIGNALS_H_ diff --git a/util/posix/signals_test.cc b/util/posix/signals_test.cc new file mode 100644 index 00000000..7d4fb756 --- /dev/null +++ b/util/posix/signals_test.cc @@ -0,0 +1,579 @@ +// 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 "util/posix/signals.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "base/compiler_specific.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "test/errors.h" +#include "test/multiprocess.h" +#include "test/scoped_temp_dir.h" + +namespace crashpad { +namespace test { +namespace { + +constexpr int kUnexpectedExitStatus = 3; + +// Keep synchronized with CauseSignal(). +bool CanCauseSignal(int sig) { + return sig == SIGABRT || + sig == SIGALRM || + sig == SIGBUS || +#if !defined(ARCH_CPU_ARM64) + sig == SIGFPE || +#endif // !defined(ARCH_CPU_ARM64) +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL) + sig == SIGILL || +#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL + sig == SIGPIPE || + sig == SIGSEGV || +#if defined(OS_MACOSX) + sig == SIGSYS || +#endif // OS_MACOSX +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64) + sig == SIGTRAP || +#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64) + false; +} + +// Keep synchronized with CanCauseSignal(). +void CauseSignal(int sig) { + switch (sig) { + case SIGABRT: { + abort(); + break; + } + + case SIGALRM: { + struct itimerval itimer = {}; + itimer.it_value.tv_usec = 1E3; // 1 millisecond + if (setitimer(ITIMER_REAL, &itimer, nullptr) != 0) { + PLOG(ERROR) << "setitimer"; + _exit(kUnexpectedExitStatus); + } + + while (true) { + sleep(std::numeric_limits::max()); + } + } + + case SIGBUS: { + char* mapped; + { + base::ScopedFD fd; + { + ScopedTempDir temp_dir; + fd.reset(open(temp_dir.path().Append("empty").value().c_str(), + O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_CLOEXEC, + 0644)); + if (fd.get() < 0) { + PLOG(ERROR) << "open"; + } + } + if (fd.get() < 0) { + _exit(kUnexpectedExitStatus); + } + + mapped = reinterpret_cast(mmap(nullptr, + getpagesize(), + PROT_READ | PROT_WRITE, + MAP_PRIVATE, + fd.get(), + 0)); + if (mapped == MAP_FAILED) { + PLOG(ERROR) << "mmap"; + } + } + if (mapped == MAP_FAILED) { + _exit(kUnexpectedExitStatus); + } + + *mapped = 0; + + _exit(kUnexpectedExitStatus); + break; + } + +#if !defined(ARCH_CPU_ARM64) + // ARM64 has hardware integer division instructions that don’t generate a + // trap for divide-by-zero, so this doesn’t produce SIGFPE. + case SIGFPE: { + // Optimization makes this tricky, so get zero from a system call likely + // to succeed, and try to do something with the result. + struct stat stat_buf; + int zero = stat("/", &stat_buf); + if (zero == -1) { + // It’s important to check |== -1| and not |!= 0|. An optimizer is free + // to discard an |== 0| branch entirely, because division by zero is + // undefined behavior. + PLOG(ERROR) << "stat"; + _exit(kUnexpectedExitStatus); + } + + int quotient = 2 / zero; + fstat(quotient, &stat_buf); + break; + } +#endif // ARCH_CPU_ARM64 + +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL) + case SIGILL: { + // __builtin_trap() causes SIGTRAP on arm64 on Android. + __builtin_trap(); + break; + } +#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL) + + case SIGPIPE: { + int pipe_fds[2]; + if (pipe(pipe_fds) != 0) { + PLOG(ERROR) << "pipe"; + _exit(kUnexpectedExitStatus); + } + + if (close(pipe_fds[0]) != 0) { + PLOG(ERROR) << "close"; + _exit(kUnexpectedExitStatus); + } + + char c = 0; + ssize_t rv = write(pipe_fds[1], &c, sizeof(c)); + if (rv < 0) { + PLOG(ERROR) << "write"; + _exit(kUnexpectedExitStatus); + } else if (rv != sizeof(c)) { + LOG(ERROR) << "write"; + _exit(kUnexpectedExitStatus); + } + break; + } + + case SIGSEGV: { + volatile int* i = nullptr; + *i = 0; + break; + } + +#if defined(OS_MACOSX) + case SIGSYS: { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + int rv = syscall(0); +#pragma clang diagnostic pop + if (rv != 0) { + PLOG(ERROR) << "syscall"; + _exit(kUnexpectedExitStatus); + } + break; + } +#endif // OS_MACOSX + +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64) + case SIGTRAP: { +#if defined(ARCH_CPU_X86_FAMILY) + asm("int3"); +#elif defined(ARCH_CPU_ARM64) + // bkpt #0 should work for 32-bit ARCH_CPU_ARMEL, but according to + // https://crrev.com/f53167270c44, it only causes SIGTRAP on Linux under a + // 64-bit kernel. For a pure 32-bit armv7 system, it generates SIGBUS. + asm("brk #0"); +#endif + break; + } +#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64) + + default: { + LOG(ERROR) << "unexpected signal " << sig; + _exit(kUnexpectedExitStatus); + break; + } + } +} + +class SignalsTest : public Multiprocess { + public: + enum class SignalSource { + kCause, + kRaise, + }; + enum class TestType { + kDefaultHandler, + kHandlerExits, + kHandlerReraisesToDefault, + kHandlerReraisesToPrevious, + }; + static constexpr int kExitingHandlerExitStatus = 2; + + SignalsTest(TestType test_type, SignalSource signal_source, int sig) + : Multiprocess(), + sig_(sig), + test_type_(test_type), + signal_source_(signal_source) {} + ~SignalsTest() {} + + private: + static void SignalHandler_Exit(int sig, siginfo_t* siginfo, void* context) { + _exit(kExitingHandlerExitStatus); + } + + static void SignalHandler_ReraiseToDefault(int sig, + siginfo_t* siginfo, + void* context) { + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); + } + + static void SignalHandler_ReraiseToPrevious(int sig, + siginfo_t* siginfo, + void* context) { + Signals::RestoreHandlerAndReraiseSignalOnReturn( + siginfo, old_actions_.ActionForSignal(sig)); + } + + // Multiprocess: + void MultiprocessParent() override {} + + void MultiprocessChild() override { + bool (*install_handlers)(Signals::Handler, int, Signals::OldActions*); + if (Signals::IsCrashSignal(sig_)) { + install_handlers = Signals::InstallCrashHandlers; + } else if (Signals::IsTerminateSignal(sig_)) { + install_handlers = Signals::InstallTerminateHandlers; + } else { + _exit(kUnexpectedExitStatus); + } + + switch (test_type_) { + case TestType::kDefaultHandler: { + // Don’t rely on the default handler being active. Something may have + // changed it (particularly on Android). + struct sigaction action; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = SIG_DFL; + ASSERT_EQ(0, sigaction(sig_, &action, nullptr)) + << ErrnoMessage("sigaction"); + break; + } + + case TestType::kHandlerExits: { + ASSERT_TRUE(install_handlers(SignalHandler_Exit, 0, nullptr)); + break; + } + + case TestType::kHandlerReraisesToDefault: { + ASSERT_TRUE( + install_handlers(SignalHandler_ReraiseToDefault, 0, nullptr)); + break; + } + + case TestType::kHandlerReraisesToPrevious: { + ASSERT_TRUE(install_handlers(SignalHandler_Exit, 0, nullptr)); + ASSERT_TRUE(install_handlers( + SignalHandler_ReraiseToPrevious, 0, &old_actions_)); + break; + } + } + + switch (signal_source_) { + case SignalSource::kCause: + CauseSignal(sig_); + break; + case SignalSource::kRaise: + raise(sig_); + break; + } + + _exit(kUnexpectedExitStatus); + } + + int sig_; + TestType test_type_; + SignalSource signal_source_; + static Signals::OldActions old_actions_; + + DISALLOW_COPY_AND_ASSIGN(SignalsTest); +}; + +Signals::OldActions SignalsTest::old_actions_; + +bool ShouldTestSignal(int sig) { + return Signals::IsCrashSignal(sig) || Signals::IsTerminateSignal(sig); +} + +TEST(Signals, WillSignalReraiseAutonomously) { + const struct { + int sig; + int code; + bool result; + } kTestData[] = { + {SIGBUS, BUS_ADRALN, true}, + {SIGFPE, FPE_FLTDIV, true}, + {SIGILL, ILL_ILLOPC, true}, + {SIGSEGV, SEGV_MAPERR, true}, + {SIGBUS, 0, false}, + {SIGFPE, -1, false}, + {SIGILL, SI_USER, false}, + {SIGSEGV, SI_QUEUE, false}, + {SIGTRAP, TRAP_BRKPT, false}, + {SIGHUP, SEGV_MAPERR, false}, + {SIGINT, SI_USER, false}, + }; + for (size_t index = 0; index < arraysize(kTestData); ++index) { + const auto test_data = kTestData[index]; + SCOPED_TRACE(base::StringPrintf( + "index %zu, sig %d, code %d", index, test_data.sig, test_data.code)); + siginfo_t siginfo = {}; + siginfo.si_signo = test_data.sig; + siginfo.si_code = test_data.code; + EXPECT_EQ(test_data.result, + Signals::WillSignalReraiseAutonomously(&siginfo)); + } +} + +TEST(Signals, Cause_DefaultHandler) { + for (int sig = 1; sig < NSIG; ++sig) { + SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig))); + + if (!CanCauseSignal(sig)) { + continue; + } + + SignalsTest test(SignalsTest::TestType::kDefaultHandler, + SignalsTest::SignalSource::kCause, + sig); + test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig); + test.Run(); + } +} + +TEST(Signals, Cause_HandlerExits) { + for (int sig = 1; sig < NSIG; ++sig) { + SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig))); + + if (!CanCauseSignal(sig)) { + continue; + } + + SignalsTest test(SignalsTest::TestType::kHandlerExits, + SignalsTest::SignalSource::kCause, + sig); + test.SetExpectedChildTermination(Multiprocess::kTerminationNormal, + SignalsTest::kExitingHandlerExitStatus); + test.Run(); + } +} + +TEST(Signals, Cause_HandlerReraisesToDefault) { + for (int sig = 1; sig < NSIG; ++sig) { + SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig))); + + if (!CanCauseSignal(sig)) { + continue; + } + + SignalsTest test(SignalsTest::TestType::kHandlerReraisesToDefault, + SignalsTest::SignalSource::kCause, + sig); + test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig); + test.Run(); + } +} + +TEST(Signals, Cause_HandlerReraisesToPrevious) { + for (int sig = 1; sig < NSIG; ++sig) { + SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig))); + + if (!CanCauseSignal(sig)) { + continue; + } + + SignalsTest test(SignalsTest::TestType::kHandlerReraisesToPrevious, + SignalsTest::SignalSource::kCause, + sig); + test.SetExpectedChildTermination(Multiprocess::kTerminationNormal, + SignalsTest::kExitingHandlerExitStatus); + test.Run(); + } +} + +TEST(Signals, Raise_DefaultHandler) { + for (int sig = 1; sig < NSIG; ++sig) { + SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig))); + + if (!ShouldTestSignal(sig)) { + continue; + } + + SignalsTest test(SignalsTest::TestType::kDefaultHandler, + SignalsTest::SignalSource::kRaise, + sig); + test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig); + test.Run(); + } +} + +TEST(Signals, Raise_HandlerExits) { + for (int sig = 1; sig < NSIG; ++sig) { + SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig))); + + if (!ShouldTestSignal(sig)) { + continue; + } + + SignalsTest test(SignalsTest::TestType::kHandlerExits, + SignalsTest::SignalSource::kRaise, + sig); + test.SetExpectedChildTermination(Multiprocess::kTerminationNormal, + SignalsTest::kExitingHandlerExitStatus); + test.Run(); + } +} + +TEST(Signals, Raise_HandlerReraisesToDefault) { + for (int sig = 1; sig < NSIG; ++sig) { + SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig))); + + if (!ShouldTestSignal(sig)) { + continue; + } + +#if defined(OS_MACOSX) + if (sig == SIGBUS) { + // Signal handlers can’t distinguish between SIGBUS arising out of a + // hardware fault and SIGBUS raised asynchronously. + // Signals::RestoreHandlerAndReraiseSignalOnReturn() assumes that SIGBUS + // comes from a hardware fault, but this test uses raise(), so the + // re-raise test must be skipped. + continue; + } +#endif // defined(OS_MACOSX) + + SignalsTest test(SignalsTest::TestType::kHandlerReraisesToDefault, + SignalsTest::SignalSource::kRaise, + sig); + test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig); + test.Run(); + } +} + +TEST(Signals, Raise_HandlerReraisesToPrevious) { + for (int sig = 1; sig < NSIG; ++sig) { + SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig))); + + if (!ShouldTestSignal(sig)) { + continue; + } + +#if defined(OS_MACOSX) + if (sig == SIGBUS) { + // Signal handlers can’t distinguish between SIGBUS arising out of a + // hardware fault and SIGBUS raised asynchronously. + // Signals::RestoreHandlerAndReraiseSignalOnReturn() assumes that SIGBUS + // comes from a hardware fault, but this test uses raise(), so the + // re-raise test must be skipped. + continue; + } +#endif // defined(OS_MACOSX) + + SignalsTest test(SignalsTest::TestType::kHandlerReraisesToPrevious, + SignalsTest::SignalSource::kRaise, + sig); + test.SetExpectedChildTermination(Multiprocess::kTerminationNormal, + SignalsTest::kExitingHandlerExitStatus); + test.Run(); + } +} + +TEST(Signals, IsCrashSignal) { + // Always crash signals. + EXPECT_TRUE(Signals::IsCrashSignal(SIGABRT)); + EXPECT_TRUE(Signals::IsCrashSignal(SIGBUS)); + EXPECT_TRUE(Signals::IsCrashSignal(SIGFPE)); + EXPECT_TRUE(Signals::IsCrashSignal(SIGILL)); + EXPECT_TRUE(Signals::IsCrashSignal(SIGQUIT)); + EXPECT_TRUE(Signals::IsCrashSignal(SIGSEGV)); + EXPECT_TRUE(Signals::IsCrashSignal(SIGSYS)); + EXPECT_TRUE(Signals::IsCrashSignal(SIGTRAP)); + + // Always terminate signals. + EXPECT_FALSE(Signals::IsCrashSignal(SIGALRM)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGHUP)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGINT)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGPIPE)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGPROF)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGTERM)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGUSR1)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGUSR2)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGVTALRM)); + + // Never crash or terminate signals. + EXPECT_FALSE(Signals::IsCrashSignal(SIGCHLD)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGCONT)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGTSTP)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGTTIN)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGTTOU)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGURG)); + EXPECT_FALSE(Signals::IsCrashSignal(SIGWINCH)); +} + +TEST(Signals, IsTerminateSignal) { + // Always terminate signals. + EXPECT_TRUE(Signals::IsTerminateSignal(SIGALRM)); + EXPECT_TRUE(Signals::IsTerminateSignal(SIGHUP)); + EXPECT_TRUE(Signals::IsTerminateSignal(SIGINT)); + EXPECT_TRUE(Signals::IsTerminateSignal(SIGPIPE)); + EXPECT_TRUE(Signals::IsTerminateSignal(SIGPROF)); + EXPECT_TRUE(Signals::IsTerminateSignal(SIGTERM)); + EXPECT_TRUE(Signals::IsTerminateSignal(SIGUSR1)); + EXPECT_TRUE(Signals::IsTerminateSignal(SIGUSR2)); + EXPECT_TRUE(Signals::IsTerminateSignal(SIGVTALRM)); + + // Always crash signals. + EXPECT_FALSE(Signals::IsTerminateSignal(SIGABRT)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGBUS)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGFPE)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGILL)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGQUIT)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGSEGV)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGSYS)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGTRAP)); + + // Never crash or terminate signals. + EXPECT_FALSE(Signals::IsTerminateSignal(SIGCHLD)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGCONT)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGTSTP)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGTTIN)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGTTOU)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGURG)); + EXPECT_FALSE(Signals::IsTerminateSignal(SIGWINCH)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/util.gyp b/util/util.gyp index b27337c6..0d1d0b26 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -135,6 +135,8 @@ 'posix/drop_privileges.h', 'posix/process_info.h', 'posix/process_info_mac.cc', + 'posix/signals.cc', + 'posix/signals.h', 'posix/symbolic_constants_posix.cc', 'posix/symbolic_constants_posix.h', 'stdlib/aligned_allocator.cc', diff --git a/util/util_test.gyp b/util/util_test.gyp index e16a292f..a33e474e 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -74,6 +74,7 @@ 'numeric/in_range_cast_test.cc', 'numeric/int128_test.cc', 'posix/process_info_test.cc', + 'posix/signals_test.cc', 'posix/symbolic_constants_posix_test.cc', 'stdlib/aligned_allocator_test.cc', 'stdlib/map_insert_test.cc',