summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Peter Collingbourne <pcc@google.com> 2020-11-13 10:54:13 -0800
committer Peter Collingbourne <pcc@google.com> 2020-11-16 23:09:16 +0000
commitcfc6e9d55263d49e2d8d19a4a3a7d38567bb4fb9 (patch)
tree65fa28b14ebe58f52eb5d00e8ebf6c270cbff216
parent07b62e3d0808cc0890717a3a589c1f7b1ac51e5c (diff)
Switch to the new kernel API for obtaining fault address tag bits.
The discussion on LKML is converging on v16 of the fault address tag bits patch [1]. In this version of the patch the presence of the tag bits in si_addr is controlled by a sa_flags bit, and a protocol is introduced to allow userspace to detect kernel support for sa_flags bits. Update the tombstone signal handler to use this API to read the tag bits, update the interceptors in libsigchain to implement the flag support detection protocol and hide the tag bits in si_addr from chained signal handlers that did not request them to match the kernel behavior. [1] https://lore.kernel.org/linux-arm-kernel/cover.1605235762.git.pcc@google.com/ Change-Id: I57f24c07c01ceb3e5b81cfc15edf559ef7dfc740
-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
+}