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:
Peter Collingbourne 2021-11-12 17:31:20 -08:00 committed by Crashpad LUCI CQ
parent c8d8dd9ccf
commit 04431eccfe
2 changed files with 136 additions and 49 deletions

View File

@ -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

View File

@ -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();