mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
Reraise signals via rt_tgsigqueueinfo(2) on Linux.
Previously we would rely on implicit re-raising to deliver signals to the underlying handler on POSIX systems if the signal is detected as being re-raisable via WillSignalReraiseAutonomously(). This detection mechanism is imperfect, as it will misclassify signals delivered as a result of kill(2) when passing a signal number usually used for synchronous signals, but now also asynchronous MTE tag check faults, which are delivered as SIGSEGV signals on Linux. As a result, these signals would not be re-raised and therefore would be discarded. Although we could, for example, teach WillSignalReraiseAutonomously() about MTE faults, the signal would still be re-raised via raise(3) and therefore we would lose the information in siginfo. We can avoid discarding these signals on Linux while at the same time preserving the siginfo by making use of the syscall rt_tgsigqueueinfo(2) which delivers a signal together with a user-provided siginfo. The problem still exists on non-Linux POSIX systems because this syscall is Linux-specific. Change-Id: I6df58d9371e29f75e19b4f899b723d4047f12936 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3278691 Commit-Queue: Peter Collingbourne <pcc@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
c8d8dd9ccf
commit
04431eccfe
@ -22,6 +22,10 @@
|
||||
#include "base/cxx17_backports.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_CHROMEOS)
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
@ -280,6 +284,23 @@ void Signals::RestoreHandlerAndReraiseSignalOnReturn(
|
||||
_exit(kFailureExitCode);
|
||||
}
|
||||
|
||||
// If we can raise a signal with siginfo on this platform, do so. This ensures
|
||||
// that we preserve the siginfo information for asynchronous signals (i.e.
|
||||
// signals that do not re-raise autonomously), such as signals delivered via
|
||||
// kill() and asynchronous hardware faults such as SEGV_MTEAERR, which would
|
||||
// otherwise be lost when re-raising the signal via raise().
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_CHROMEOS)
|
||||
int retval = syscall(SYS_rt_tgsigqueueinfo,
|
||||
getpid(),
|
||||
syscall(SYS_gettid),
|
||||
siginfo->si_signo,
|
||||
siginfo);
|
||||
if (retval != 0) {
|
||||
_exit(kFailureExitCode);
|
||||
}
|
||||
return;
|
||||
#endif // defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_CHROMEOS)
|
||||
|
||||
// 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
|
||||
|
@ -34,15 +34,48 @@
|
||||
#include "test/scoped_temp_dir.h"
|
||||
#include "util/posix/scoped_mmap.h"
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_CHROMEOS)
|
||||
#include <sys/auxv.h>
|
||||
#include <sys/prctl.h>
|
||||
|
||||
#if defined(ARCH_CPU_ARM64)
|
||||
#ifndef HWCAP2_MTE
|
||||
#define HWCAP2_MTE (1 << 18)
|
||||
#endif
|
||||
#ifndef SEGV_MTEAERR
|
||||
#define SEGV_MTEAERR 8
|
||||
#endif
|
||||
#ifndef PROT_MTE
|
||||
#define PROT_MTE 0x20
|
||||
#endif
|
||||
#ifndef PR_SET_TAGGED_ADDR_CTRL
|
||||
#define PR_SET_TAGGED_ADDR_CTRL 55
|
||||
#endif
|
||||
#ifndef PR_TAGGED_ADDR_ENABLE
|
||||
#define PR_TAGGED_ADDR_ENABLE (1UL << 0)
|
||||
#endif
|
||||
#ifndef PR_MTE_TCF_ASYNC
|
||||
#define PR_MTE_TCF_ASYNC (1UL << 2)
|
||||
#endif
|
||||
#endif // defined(ARCH_CPU_ARM64)
|
||||
#endif // defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_CHROMEOS)
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
constexpr int kUnexpectedExitStatus = 3;
|
||||
|
||||
struct TestableSignal {
|
||||
int sig, code;
|
||||
};
|
||||
|
||||
// Keep synchronized with CauseSignal().
|
||||
bool CanCauseSignal(int sig) {
|
||||
return sig == SIGABRT || sig == SIGALRM || sig == SIGBUS ||
|
||||
std::vector<TestableSignal> TestableSignals() {
|
||||
std::vector<TestableSignal> signals;
|
||||
signals.push_back({SIGABRT, 0});
|
||||
signals.push_back({SIGALRM, 0});
|
||||
signals.push_back({SIGBUS, 0});
|
||||
/* According to DDI0487D (Armv8 Architecture Reference Manual) the expected
|
||||
* behavior for division by zero (Section 3.4.8) is: "... results in a
|
||||
* zero being written to the destination register, without any
|
||||
@ -50,24 +83,30 @@ bool CanCauseSignal(int sig) {
|
||||
* This applies to Armv8 (and not earlier) for both 32bit and 64bit app code.
|
||||
*/
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
sig == SIGFPE ||
|
||||
signals.push_back({SIGFPE, 0});
|
||||
#endif
|
||||
|
||||
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL)
|
||||
sig == SIGILL ||
|
||||
signals.push_back({SIGILL, 0});
|
||||
#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL)
|
||||
sig == SIGPIPE || sig == SIGSEGV ||
|
||||
signals.push_back({SIGPIPE, 0});
|
||||
signals.push_back({SIGSEGV, 0});
|
||||
#if (defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_CHROMEOS)) && \
|
||||
defined(ARCH_CPU_ARM64)
|
||||
if (getauxval(AT_HWCAP2) & HWCAP2_MTE) {
|
||||
signals.push_back({SIGSEGV, SEGV_MTEAERR});
|
||||
}
|
||||
#endif
|
||||
#if defined(OS_APPLE)
|
||||
sig == SIGSYS ||
|
||||
signals.push_back({SIGSYS, 0});
|
||||
#endif // OS_APPLE
|
||||
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
|
||||
sig == SIGTRAP ||
|
||||
signals.push_back({SIGTRAP, 0});
|
||||
#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
|
||||
false;
|
||||
return signals;
|
||||
}
|
||||
|
||||
// Keep synchronized with CanCauseSignal().
|
||||
void CauseSignal(int sig) {
|
||||
// Keep synchronized with TestableSignals().
|
||||
void CauseSignal(int sig, int code) {
|
||||
switch (sig) {
|
||||
case SIGABRT: {
|
||||
abort();
|
||||
@ -164,8 +203,37 @@ void CauseSignal(int sig) {
|
||||
}
|
||||
|
||||
case SIGSEGV: {
|
||||
volatile int* i = nullptr;
|
||||
*i = 0;
|
||||
switch (code) {
|
||||
case 0: {
|
||||
volatile int* i = nullptr;
|
||||
*i = 0;
|
||||
break;
|
||||
}
|
||||
#if (defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_CHROMEOS)) && \
|
||||
defined(ARCH_CPU_ARM64)
|
||||
case SEGV_MTEAERR: {
|
||||
ScopedMmap mapping;
|
||||
if (!mapping.ResetMmap(nullptr,
|
||||
getpagesize(),
|
||||
PROT_READ | PROT_WRITE | PROT_MTE,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0)) {
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
if (prctl(PR_SET_TAGGED_ADDR_CTRL,
|
||||
PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_ASYNC,
|
||||
0,
|
||||
0,
|
||||
0) != 0) {
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
mapping.addr_as<char*>()[1ULL << 56] = 0;
|
||||
break;
|
||||
}
|
||||
#endif // (defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_CHROMEOS)) &&
|
||||
// defined(ARCH_CPU_ARM64)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -218,9 +286,10 @@ class SignalsTest : public Multiprocess {
|
||||
};
|
||||
static constexpr int kExitingHandlerExitStatus = 2;
|
||||
|
||||
SignalsTest(TestType test_type, SignalSource signal_source, int sig)
|
||||
SignalsTest(TestType test_type, SignalSource signal_source, int sig, int code)
|
||||
: Multiprocess(),
|
||||
sig_(sig),
|
||||
code_(code),
|
||||
test_type_(test_type),
|
||||
signal_source_(signal_source) {}
|
||||
|
||||
@ -299,7 +368,7 @@ class SignalsTest : public Multiprocess {
|
||||
|
||||
switch (signal_source_) {
|
||||
case SignalSource::kCause:
|
||||
CauseSignal(sig_);
|
||||
CauseSignal(sig_, code_);
|
||||
break;
|
||||
case SignalSource::kRaise:
|
||||
raise(sig_);
|
||||
@ -310,6 +379,7 @@ class SignalsTest : public Multiprocess {
|
||||
}
|
||||
|
||||
int sig_;
|
||||
int code_;
|
||||
TestType test_type_;
|
||||
SignalSource signal_source_;
|
||||
static Signals::OldActions old_actions_;
|
||||
@ -352,32 +422,28 @@ TEST(Signals, WillSignalReraiseAutonomously) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
for (TestableSignal s : TestableSignals()) {
|
||||
SCOPED_TRACE(base::StringPrintf(
|
||||
"sig %d (%s), code %d", s.sig, strsignal(s.sig), s.code));
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kDefaultHandler,
|
||||
SignalsTest::SignalSource::kCause,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
|
||||
s.sig,
|
||||
s.code);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, s.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;
|
||||
}
|
||||
for (TestableSignal s : TestableSignals()) {
|
||||
SCOPED_TRACE(base::StringPrintf(
|
||||
"sig %d (%s), code %d", s.sig, strsignal(s.sig), s.code));
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerExits,
|
||||
SignalsTest::SignalSource::kCause,
|
||||
sig);
|
||||
s.sig,
|
||||
s.code);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
|
||||
SignalsTest::kExitingHandlerExitStatus);
|
||||
test.Run();
|
||||
@ -385,32 +451,28 @@ TEST(Signals, Cause_HandlerExits) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
for (TestableSignal s : TestableSignals()) {
|
||||
SCOPED_TRACE(base::StringPrintf(
|
||||
"sig %d (%s), code %d", s.sig, strsignal(s.sig), s.code));
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerReraisesToDefault,
|
||||
SignalsTest::SignalSource::kCause,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
|
||||
s.sig,
|
||||
s.code);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, s.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;
|
||||
}
|
||||
for (TestableSignal s : TestableSignals()) {
|
||||
SCOPED_TRACE(base::StringPrintf(
|
||||
"sig %d (%s), code %d", s.sig, strsignal(s.sig), s.code));
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerReraisesToPrevious,
|
||||
SignalsTest::SignalSource::kCause,
|
||||
sig);
|
||||
s.sig,
|
||||
s.code);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
|
||||
SignalsTest::kExitingHandlerExitStatus);
|
||||
test.Run();
|
||||
@ -427,7 +489,8 @@ TEST(Signals, Raise_DefaultHandler) {
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kDefaultHandler,
|
||||
SignalsTest::SignalSource::kRaise,
|
||||
sig);
|
||||
sig,
|
||||
0);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
|
||||
test.Run();
|
||||
}
|
||||
@ -443,7 +506,8 @@ TEST(Signals, Raise_HandlerExits) {
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerExits,
|
||||
SignalsTest::SignalSource::kRaise,
|
||||
sig);
|
||||
sig,
|
||||
0);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
|
||||
SignalsTest::kExitingHandlerExitStatus);
|
||||
test.Run();
|
||||
@ -475,7 +539,8 @@ TEST(Signals, Raise_HandlerReraisesToDefault) {
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerReraisesToDefault,
|
||||
SignalsTest::SignalSource::kRaise,
|
||||
sig);
|
||||
sig,
|
||||
0);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
|
||||
test.Run();
|
||||
}
|
||||
@ -506,7 +571,8 @@ TEST(Signals, Raise_HandlerReraisesToPrevious) {
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerReraisesToPrevious,
|
||||
SignalsTest::SignalSource::kRaise,
|
||||
sig);
|
||||
sig,
|
||||
0);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
|
||||
SignalsTest::kExitingHandlerExitStatus);
|
||||
test.Run();
|
||||
|
Loading…
x
Reference in New Issue
Block a user