summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sigchainlib/sigchain.cc59
-rw-r--r--sigchainlib/sigchain_test.cc29
2 files changed, 87 insertions, 1 deletions
diff --git a/sigchainlib/sigchain.cc b/sigchainlib/sigchain.cc
index f58fe213b3..1935d454f8 100644
--- a/sigchainlib/sigchain.cc
+++ b/sigchainlib/sigchain.cc
@@ -22,6 +22,10 @@
#include <stdlib.h>
#include <string.h>
+#if defined(__BIONIC__)
+#include <bionic/macros.h>
+#endif
+
#include <algorithm>
#include <initializer_list>
#include <mutex>
@@ -39,6 +43,9 @@
#define _XOPEN_SOURCE
#endif
+#define SA_UNSUPPORTED 0x00000400
+#define SA_EXPOSE_TAGBITS 0x00000800
+
#include <ucontext.h>
// libsigchain provides an interception layer for signal handlers, to allow ART and others to give
@@ -204,13 +211,50 @@ class SignalChain {
#endif
handler_action.sa_sigaction = SignalChain::Handler;
- handler_action.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
+ handler_action.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK |
+ SA_UNSUPPORTED | SA_EXPOSE_TAGBITS;
#if defined(__BIONIC__)
linked_sigaction64(signo, &handler_action, &action_);
+ linked_sigaction64(signo, nullptr, &handler_action);
#else
linked_sigaction(signo, &handler_action, &action_);
+ linked_sigaction(signo, nullptr, &handler_action);
+#endif
+
+ // Newer kernels clear unknown flags from sigaction.sa_flags in order to
+ // allow userspace to determine which flag bits are supported. We use this
+ // behavior in turn to implement the same flag bit support detection
+ // protocol regardless of kernel version. Due to the lack of a flag bit
+ // support detection protocol in older kernels we assume support for a base
+ // set of flags that have been supported since at least 2003 [1]. No flags
+ // were introduced since then until the introduction of SA_EXPOSE_TAGBITS
+ // handled below. glibc headers do not define SA_RESTORER so we define it
+ // ourselves.
+ //
+ // TODO(pcc): The new kernel behavior has been implemented in a kernel
+ // patch [2] that has not yet landed. Update the code if necessary once it
+ // lands.
+ //
+ // [1] https://github.com/mpe/linux-fullhistory/commit/c0f806c86fc8b07ad426df023f1a4bb0e53c64f6
+ // [2] https://lore.kernel.org/linux-arm-kernel/cover.1605235762.git.pcc@google.com/
+#if !defined(__BIONIC__)
+#define SA_RESTORER 0x04000000
#endif
+ kernel_supported_flags_ = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_SIGINFO |
+ SA_ONSTACK | SA_RESTART | SA_NODEFER |
+ SA_RESETHAND | SA_RESTORER;
+
+ // Determine whether the kernel supports SA_EXPOSE_TAGBITS. For newer
+ // kernels we use the flag support detection protocol described above. In
+ // order to allow userspace to distinguish old and new kernels,
+ // SA_UNSUPPORTED has been reserved as an unsupported flag. If the kernel
+ // did not clear it then we know that we have an old kernel that would not
+ // support SA_EXPOSE_TAGBITS anyway.
+ if (!(handler_action.sa_flags & SA_UNSUPPORTED) &&
+ (handler_action.sa_flags & SA_EXPOSE_TAGBITS)) {
+ kernel_supported_flags_ |= SA_EXPOSE_TAGBITS;
+ }
}
template <typename SigactionType>
@@ -244,6 +288,7 @@ class SignalChain {
memcpy(&action_.sa_mask, &new_action->sa_mask,
std::min(sizeof(action_.sa_mask), sizeof(new_action->sa_mask)));
}
+ action_.sa_flags &= kernel_supported_flags_;
}
void AddSpecialHandler(SigchainAction* sa) {
@@ -278,6 +323,7 @@ class SignalChain {
private:
bool claimed_;
+ int kernel_supported_flags_;
#if defined(__BIONIC__)
struct sigaction64 action_;
#else
@@ -342,6 +388,17 @@ void SignalChain::Handler(int signo, siginfo_t* siginfo, void* ucontext_raw) {
#endif
if ((handler_flags & SA_SIGINFO)) {
+ // If the chained handler is not expecting tag bits in the fault address,
+ // mask them out now.
+#if defined(__BIONIC__)
+ if (!(handler_flags & SA_EXPOSE_TAGBITS) &&
+ (signo == SIGILL || signo == SIGFPE || signo == SIGSEGV ||
+ signo == SIGBUS || signo == SIGTRAP) &&
+ siginfo->si_code > SI_USER && siginfo->si_code < SI_KERNEL &&
+ !(signo == SIGTRAP && siginfo->si_code == TRAP_HWBKPT)) {
+ siginfo->si_addr = untag_address(siginfo->si_addr);
+ }
+#endif
chains[signo].action_.sa_sigaction(signo, siginfo, ucontext_raw);
} else {
auto handler = chains[signo].action_.sa_handler;
diff --git a/sigchainlib/sigchain_test.cc b/sigchainlib/sigchain_test.cc
index bb997877a1..4ef98c2b8f 100644
--- a/sigchainlib/sigchain_test.cc
+++ b/sigchainlib/sigchain_test.cc
@@ -238,3 +238,32 @@ TEST_F(SigchainTest, EnsureFrontOfChain) {
ASSERT_EQ(1, called);
called = 0;
}
+
+TEST_F(SigchainTest, fault_address_tag) {
+#define SA_EXPOSE_TAGBITS 0x00000800
+#if defined(__aarch64__)
+ struct sigaction action = {};
+ action.sa_flags = SA_SIGINFO;
+ action.sa_sigaction = [](int, siginfo_t* siginfo, void*) {
+ _exit(reinterpret_cast<uintptr_t>(siginfo->si_addr) >> 56);
+ };
+ ASSERT_EQ(0, sigaction(SIGSEGV, &action, nullptr));
+
+ auto* tagged_null = reinterpret_cast<int*>(0x2bULL << 56);
+ EXPECT_EXIT({ volatile int load __attribute__((unused)) = *tagged_null; },
+ testing::ExitedWithCode(0), "");
+
+ // Our sigaction implementation always implements the "clear unknown bits"
+ // semantics for oldact.sa_flags regardless of kernel version so we rely on it
+ // here to test for kernel support for SA_EXPOSE_TAGBITS.
+ action.sa_flags = SA_SIGINFO | SA_EXPOSE_TAGBITS;
+ ASSERT_EQ(0, sigaction(SIGSEGV, &action, nullptr));
+ ASSERT_EQ(0, sigaction(SIGSEGV, nullptr, &action));
+ if (action.sa_flags & SA_EXPOSE_TAGBITS) {
+ EXPECT_EXIT({ volatile int load __attribute__((unused)) = *tagged_null; },
+ testing::ExitedWithCode(0x2b), "");
+ }
+#else
+ GTEST_SKIP() << "arm64 only";
+#endif
+}