[MTE] Add a HWASan-style tag dump to tombstones.
We already dump the tags in the regigster dump section by appending the
tag to the memory address. You only get 2 granules before each register
and 13 after.
The HWASan-style tag dump is extremely useful for debugging, as it gives
a pretty comprehensive overview of the memory subsystem. It also
provides enough context bytes (256) to give you a reasonable intuition
about a particular bug.
The tag dump shows up only if PTRACE_PEEKTAGS returns at least one value
in the 256 requested. If the start of end of the region is untagged,
it's omitted. The tag dump looks like this:
Change-Id: Icc33fb97542d9b1fa3ae9e58aba34d524c6ba7b5
---
Memory tags around the fault address (0x60000704414d340), one tag per 16 bytes:
0x704414d000: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0x704414d100: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0x704414d200: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
=>0x704414d300: 0 0 0 0 [2] 2 0 0 0 0 0 0 0 0 0 0
0x704414d400: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0x704414d500: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0x704414d600: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0x704414d700: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0x704414d800: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0x704414d900: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0x704414da00: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
---
Bug: 183992164
Test: atest debuggerd_test on MTE+QEMU and sunfish.
Change-Id: I8d5842e4803ca30b407e866c99eef56f2cb36600
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 24804d0..abda071 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -58,6 +58,7 @@
#include <scoped_minijail.h>
#include "debuggerd/handler.h"
+#include "libdebuggerd/utility.h"
#include "protocol.h"
#include "tombstoned/tombstoned.h"
#include "util.h"
@@ -526,6 +527,8 @@
std::vector<std::string> log_sources(2);
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
+ // Tag dump only available in the tombstone, not logcat.
+ ASSERT_MATCH(log_sources[0], "Memory tags around the fault address");
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
@@ -597,6 +600,12 @@
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
+ // Tag dump only in tombstone, not logcat, and tagging is not used for
+ // overflow protection in the scudo secondary (guard pages are used instead).
+ if (GetParam() < 0x10000) {
+ ASSERT_MATCH(log_sources[0], "Memory tags around the fault address");
+ }
+
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
@@ -637,6 +646,7 @@
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"((^|\s)allocated by thread .*
#00 pc)");
+ ASSERT_MATCH(result, "Memory tags around the fault address");
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@@ -686,6 +696,9 @@
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
+ // Tag dump only in the tombstone, not logcat.
+ ASSERT_MATCH(log_sources[0], "Memory tags around the fault address");
+
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_THAT(result, HasSubstr("Note: multiple potential causes for this crash were detected, "
@@ -706,21 +719,26 @@
#if defined(__aarch64__)
static uintptr_t CreateTagMapping() {
- uintptr_t mapping =
- reinterpret_cast<uintptr_t>(mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE | PROT_MTE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
- if (reinterpret_cast<void*>(mapping) == MAP_FAILED) {
+ // Some of the MTE tag dump tests assert that there is an inaccessible page to the left and right
+ // of the PROT_MTE page, so map three pages and set the two guard pages to PROT_NONE.
+ size_t page_size = getpagesize();
+ void* mapping = mmap(nullptr, page_size * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ uintptr_t mapping_uptr = reinterpret_cast<uintptr_t>(mapping);
+ if (mapping == MAP_FAILED) {
return 0;
}
- __asm__ __volatile__(".arch_extension mte; stg %0, [%0]"
- :
- : "r"(mapping + (1ULL << 56))
- : "memory");
- return mapping;
+ mprotect(reinterpret_cast<void*>(mapping_uptr + page_size), page_size,
+ PROT_READ | PROT_WRITE | PROT_MTE);
+ // Stripe the mapping, where even granules get tag '1', and odd granules get tag '0'.
+ for (uintptr_t offset = 0; offset < page_size; offset += 2 * kTagGranuleSize) {
+ uintptr_t tagged_addr = mapping_uptr + page_size + offset + (1ULL << 56);
+ __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : : "r"(tagged_addr) : "memory");
+ }
+ return mapping_uptr + page_size;
}
#endif
-TEST_F(CrasherTest, mte_tag_dump) {
+TEST_F(CrasherTest, mte_register_tag_dump) {
#if defined(__aarch64__)
if (!mte_supported()) {
GTEST_SKIP() << "Requires MTE";
@@ -753,6 +771,107 @@
#endif
}
+TEST_F(CrasherTest, mte_fault_tag_dump_front_truncated) {
+#if defined(__aarch64__)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([&]() {
+ SetTagCheckingLevelSync();
+ volatile char* p = reinterpret_cast<char*>(CreateTagMapping());
+ p[0] = 0; // Untagged pointer, tagged memory.
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ ASSERT_MATCH(result, R"(Memory tags around the fault address.*
+\s*=>0x[0-9a-f]+000:\[1\] 0 1 0)");
+#else
+ GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
+TEST_F(CrasherTest, mte_fault_tag_dump) {
+#if defined(__aarch64__)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([&]() {
+ SetTagCheckingLevelSync();
+ volatile char* p = reinterpret_cast<char*>(CreateTagMapping());
+ p[320] = 0; // Untagged pointer, tagged memory.
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ ASSERT_MATCH(result, R"(Memory tags around the fault address.*
+\s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
+\s*=>0x[0-9a-f]+: 1 0 1 0 \[1\] 0 1 0 1 0 1 0 1 0 1 0
+\s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
+)");
+#else
+ GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
+TEST_F(CrasherTest, mte_fault_tag_dump_rear_truncated) {
+#if defined(__aarch64__)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([&]() {
+ SetTagCheckingLevelSync();
+ size_t page_size = getpagesize();
+ volatile char* p = reinterpret_cast<char*>(CreateTagMapping());
+ p[page_size - kTagGranuleSize * 2] = 0; // Untagged pointer, tagged memory.
+ });
+
+ StartIntercept(&output_fd);
+ FinishCrasher();
+ AssertDeath(SIGSEGV);
+ FinishIntercept(&intercept_result);
+
+ ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+ std::string result;
+ ConsumeFd(std::move(output_fd), &result);
+
+ ASSERT_MATCH(result, R"(Memory tags around the fault address)");
+ ASSERT_MATCH(result,
+ R"(\s*0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
+\s*=>0x[0-9a-f]+: 1 0 1 0 1 0 1 0 1 0 1 0 1 0 \[1\] 0
+
+)"); // Ensure truncation happened and there's a newline after the tag fault.
+#else
+ GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
TEST_F(CrasherTest, LD_PRELOAD) {
int intercept_result;
unique_fd output_fd;