Add support for MTE error reports in tombstones.
Teach debuggerd to use the new scudo APIs proposed in
https://reviews.llvm.org/D77283 for extracing MTE error reports from crashed
processes, and include those reports in tombstones if possible.
Bug: 135772972
Change-Id: I082dfd0ac9d781cfed2b8c34cc73562614bb0dbb
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index d67b522..31c2d5d 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -169,6 +169,7 @@
"libdebuggerd/backtrace.cpp",
"libdebuggerd/gwp_asan.cpp",
"libdebuggerd/open_files_list.cpp",
+ "libdebuggerd/scudo.cpp",
"libdebuggerd/tombstone.cpp",
"libdebuggerd/utility.cpp",
],
@@ -176,8 +177,13 @@
local_include_dirs: ["libdebuggerd/include"],
export_include_dirs: ["libdebuggerd/include"],
- // Needed for private/bionic_fdsan.h
- include_dirs: ["bionic/libc"],
+ include_dirs: [
+ // Needed for private/bionic_fdsan.h
+ "bionic/libc",
+
+ // Needed for scudo/interface.h
+ "external/scudo/standalone/include",
+ ],
header_libs: [
"bionic_libc_platform_headers",
"gwp_asan_headers",
@@ -192,7 +198,10 @@
"liblog",
],
- whole_static_libs: ["gwp_asan_crash_handler"],
+ whole_static_libs: [
+ "gwp_asan_crash_handler",
+ "libscudo",
+ ],
target: {
recovery: {
@@ -206,6 +215,9 @@
debuggable: {
cflags: ["-DROOT_POSSIBLE"],
},
+ experimental_mte: {
+ cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
+ },
},
}
@@ -256,6 +268,10 @@
"gwp_asan_headers",
],
+ include_dirs: [
+ "external/scudo/standalone/include",
+ ],
+
local_include_dirs: [
"libdebuggerd",
],
@@ -271,6 +287,12 @@
},
test_suites: ["device-tests"],
+
+ product_variables: {
+ experimental_mte: {
+ cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
+ },
+ },
}
cc_benchmark {
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 0cd2350..d7cb972 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -289,6 +289,8 @@
process_info->fdsan_table_address = crash_info->data.d.fdsan_table_address;
process_info->gwp_asan_state = crash_info->data.d.gwp_asan_state;
process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata;
+ process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot;
+ process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
FALLTHROUGH_INTENDED;
case 1:
case 2:
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 054f836..25417a9 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -31,6 +31,9 @@
#include <android/fdsan.h>
#include <android/set_abort_message.h>
+#include <bionic/malloc.h>
+#include <bionic/mte.h>
+#include <bionic/mte_kernel.h>
#include <bionic/reserved_signals.h>
#include <android-base/cmsg.h>
@@ -331,6 +334,173 @@
R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr (0x100000000000dead|0xdead))");
}
+#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+static void SetTagCheckingLevelSync() {
+ int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+ if (tagged_addr_ctrl < 0) {
+ abort();
+ }
+
+ tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_SYNC;
+ if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) != 0) {
+ abort();
+ }
+
+ HeapTaggingLevel heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
+ if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level))) {
+ abort();
+ }
+}
+#endif
+
+TEST_F(CrasherTest, mte_uaf) {
+#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([]() {
+ SetTagCheckingLevelSync();
+ volatile int* p = (volatile int*)malloc(16);
+ free((void *)p);
+ p[0] = 42;
+ });
+
+ 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"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a 16-byte allocation)");
+#else
+ GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+#endif
+}
+
+TEST_F(CrasherTest, mte_overflow) {
+#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([]() {
+ SetTagCheckingLevelSync();
+ volatile int* p = (volatile int*)malloc(16);
+ p[4] = 42;
+ });
+
+ 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"(signal 11 \(SIGSEGV\))");
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)");
+#else
+ GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+#endif
+}
+
+TEST_F(CrasherTest, mte_underflow) {
+#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([]() {
+ SetTagCheckingLevelSync();
+ volatile int* p = (volatile int*)malloc(16);
+ p[-1] = 42;
+ });
+
+ 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"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a 16-byte allocation)");
+#else
+ GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+#endif
+}
+
+TEST_F(CrasherTest, mte_multiple_causes) {
+#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+ if (!mte_supported()) {
+ GTEST_SKIP() << "Requires MTE";
+ }
+
+ int intercept_result;
+ unique_fd output_fd;
+ StartProcess([]() {
+ SetTagCheckingLevelSync();
+
+ // Make two allocations with the same tag and close to one another. Check for both properties
+ // with a bounds check -- this relies on the fact that only if the allocations have the same tag
+ // would they be measured as closer than 128 bytes to each other. Otherwise they would be about
+ // (some non-zero value << 56) apart.
+ //
+ // The out-of-bounds access will be considered either an overflow of one or an underflow of the
+ // other.
+ std::set<uintptr_t> allocs;
+ for (int i = 0; i != 4096; ++i) {
+ uintptr_t alloc = reinterpret_cast<uintptr_t>(malloc(16));
+ auto it = allocs.insert(alloc).first;
+ if (it != allocs.begin() && *std::prev(it) + 128 > alloc) {
+ *reinterpret_cast<int*>(*std::prev(it) + 16) = 42;
+ }
+ if (std::next(it) != allocs.end() && alloc + 128 > *std::next(it)) {
+ *reinterpret_cast<int*>(alloc + 16) = 42;
+ }
+ }
+ });
+
+ 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"(signal 11 \(SIGSEGV\))");
+ ASSERT_MATCH(
+ result,
+ R"(Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability.)");
+
+ // Adjacent untracked allocations may cause us to see the wrong underflow here (or only
+ // overflows), so we can't match explicitly for an underflow message.
+ ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)");
+#else
+ GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+#endif
+}
+
TEST_F(CrasherTest, LD_PRELOAD) {
int intercept_result;
unique_fd output_fd;
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index 6650294..254ed4f 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -40,6 +40,8 @@
void* fdsan_table;
const gwp_asan::AllocatorState* gwp_asan_state;
const gwp_asan::AllocationMetadata* gwp_asan_metadata;
+ const char* scudo_stack_depot;
+ const char* scudo_region_info;
};
// These callbacks are called in a signal handler, and thus must be async signal safe.
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
new file mode 100644
index 0000000..4d00ece
--- /dev/null
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "types.h"
+#include "utility.h"
+
+#include <memory.h>
+
+#include "scudo/interface.h"
+
+class ScudoCrashData {
+ public:
+ ScudoCrashData() = delete;
+ ~ScudoCrashData() = default;
+ ScudoCrashData(unwindstack::Memory* process_memory, const ProcessInfo& process_info);
+
+ bool CrashIsMine() const;
+
+ void DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const;
+
+ private:
+ scudo_error_info error_info_ = {};
+ uintptr_t untagged_fault_addr_;
+
+ void DumpReport(const scudo_error_report* report, log_t* log,
+ unwindstack::Unwinder* unwinder) const;
+};
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index 35c3fd6..04c4b5c 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -41,6 +41,8 @@
uintptr_t fdsan_table_address = 0;
uintptr_t gwp_asan_state = 0;
uintptr_t gwp_asan_metadata = 0;
+ uintptr_t scudo_stack_depot = 0;
+ uintptr_t scudo_region_info = 0;
bool has_fault_address = false;
uintptr_t fault_address = 0;
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
new file mode 100644
index 0000000..f8bfe07
--- /dev/null
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "libdebuggerd/scudo.h"
+#include "libdebuggerd/gwp_asan.h"
+
+#include "unwindstack/Memory.h"
+#include "unwindstack/Unwinder.h"
+
+#include <bionic/macros.h>
+
+std::unique_ptr<char[]> AllocAndReadFully(unwindstack::Memory* process_memory, uint64_t addr,
+ size_t size) {
+ auto buf = std::make_unique<char[]>(size);
+ if (!process_memory->ReadFully(addr, buf.get(), size)) {
+ return std::unique_ptr<char[]>();
+ }
+ return buf;
+}
+
+static const uintptr_t kTagGranuleSize = 16;
+
+ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory,
+ const ProcessInfo& process_info) {
+ if (!process_info.has_fault_address) {
+ return;
+ }
+
+ auto stack_depot = AllocAndReadFully(process_memory, process_info.scudo_stack_depot,
+ __scudo_get_stack_depot_size());
+ auto region_info = AllocAndReadFully(process_memory, process_info.scudo_region_info,
+ __scudo_get_region_info_size());
+
+ untagged_fault_addr_ = untag_address(process_info.fault_address);
+ uintptr_t fault_page = untagged_fault_addr_ & ~(PAGE_SIZE - 1);
+
+ uintptr_t memory_begin = fault_page - PAGE_SIZE * 16;
+ if (memory_begin > fault_page) {
+ return;
+ }
+
+ uintptr_t memory_end = fault_page + PAGE_SIZE * 16;
+ if (memory_end < fault_page) {
+ return;
+ }
+
+ auto memory = std::make_unique<char[]>(memory_end - memory_begin);
+ for (auto i = memory_begin; i != memory_end; i += PAGE_SIZE) {
+ process_memory->ReadFully(i, memory.get() + i - memory_begin, PAGE_SIZE);
+ }
+
+ auto memory_tags = std::make_unique<char[]>((memory_end - memory_begin) / kTagGranuleSize);
+ for (auto i = memory_begin; i != memory_end; i += kTagGranuleSize) {
+ memory_tags[(i - memory_begin) / kTagGranuleSize] = process_memory->ReadTag(i);
+ }
+
+ __scudo_get_error_info(&error_info_, process_info.fault_address, stack_depot.get(),
+ region_info.get(), memory.get(), memory_tags.get(), memory_begin,
+ memory_end - memory_begin);
+}
+
+bool ScudoCrashData::CrashIsMine() const {
+ return error_info_.reports[0].error_type != UNKNOWN;
+}
+
+void ScudoCrashData::DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const {
+ if (error_info_.reports[1].error_type != UNKNOWN) {
+ _LOG(log, logtype::HEADER,
+ "\nNote: multiple potential causes for this crash were detected, listing them in "
+ "decreasing order of probability.\n");
+ }
+
+ size_t report_num = 0;
+ while (report_num < sizeof(error_info_.reports) / sizeof(error_info_.reports[0]) &&
+ error_info_.reports[report_num].error_type != UNKNOWN) {
+ DumpReport(&error_info_.reports[report_num++], log, unwinder);
+ }
+}
+
+void ScudoCrashData::DumpReport(const scudo_error_report* report, log_t* log,
+ unwindstack::Unwinder* unwinder) const {
+ const char *error_type_str;
+ switch (report->error_type) {
+ case USE_AFTER_FREE:
+ error_type_str = "Use After Free";
+ break;
+ case BUFFER_OVERFLOW:
+ error_type_str = "Buffer Overflow";
+ break;
+ case BUFFER_UNDERFLOW:
+ error_type_str = "Buffer Underflow";
+ break;
+ default:
+ error_type_str = "Unknown";
+ break;
+ }
+
+ uintptr_t diff;
+ const char* location_str;
+
+ if (untagged_fault_addr_ < report->allocation_address) {
+ // Buffer Underflow, 6 bytes left of a 41-byte allocation at 0xdeadbeef.
+ location_str = "left of";
+ diff = report->allocation_address - untagged_fault_addr_;
+ } else if (untagged_fault_addr_ - report->allocation_address < report->allocation_size) {
+ // Use After Free, 40 bytes into a 41-byte allocation at 0xdeadbeef.
+ location_str = "into";
+ diff = untagged_fault_addr_ - report->allocation_address;
+ } else {
+ // Buffer Overflow, 6 bytes right of a 41-byte allocation at 0xdeadbeef.
+ location_str = "right of";
+ diff = untagged_fault_addr_ - report->allocation_address - report->allocation_size;
+ }
+
+ // Suffix of 'bytes', i.e. 4 bytes' vs. '1 byte'.
+ const char* byte_suffix = "s";
+ if (diff == 1) {
+ byte_suffix = "";
+ }
+ _LOG(log, logtype::HEADER,
+ "\nCause: [MTE]: %s, %" PRIuPTR " byte%s %s a %zu-byte allocation at 0x%" PRIxPTR "\n",
+ error_type_str, diff, byte_suffix, location_str, report->allocation_size,
+ report->allocation_address);
+
+ if (report->allocation_trace[0]) {
+ _LOG(log, logtype::BACKTRACE, "\nallocated by thread %u:\n", report->allocation_tid);
+ unwinder->SetDisplayBuildID(true);
+ for (size_t i = 0; i < 64 && report->allocation_trace[i]; ++i) {
+ unwindstack::FrameData frame_data =
+ unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]);
+ frame_data.num = i;
+ _LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str());
+ }
+ }
+
+ if (report->deallocation_trace[0]) {
+ _LOG(log, logtype::BACKTRACE, "\ndeallocated by thread %u:\n", report->deallocation_tid);
+ unwinder->SetDisplayBuildID(true);
+ for (size_t i = 0; i < 64 && report->deallocation_trace[i]; ++i) {
+ unwindstack::FrameData frame_data =
+ unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]);
+ frame_data.num = i;
+ _LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str());
+ }
+ }
+}
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index d6b2e25..ab65dd1 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -56,6 +56,7 @@
#include "libdebuggerd/backtrace.h"
#include "libdebuggerd/gwp_asan.h"
#include "libdebuggerd/open_files_list.h"
+#include "libdebuggerd/scudo.h"
#include "libdebuggerd/utility.h"
#include "gwp_asan/common.h"
@@ -389,14 +390,17 @@
}
std::unique_ptr<GwpAsanCrashData> gwp_asan_crash_data;
+ std::unique_ptr<ScudoCrashData> scudo_crash_data;
if (primary_thread) {
gwp_asan_crash_data = std::make_unique<GwpAsanCrashData>(unwinder->GetProcessMemory().get(),
process_info, thread_info);
+ scudo_crash_data =
+ std::make_unique<ScudoCrashData>(unwinder->GetProcessMemory().get(), process_info);
}
if (primary_thread && gwp_asan_crash_data->CrashIsMine()) {
gwp_asan_crash_data->DumpCause(log);
- } else if (thread_info.siginfo) {
+ } else if (thread_info.siginfo && !(primary_thread && scudo_crash_data->CrashIsMine())) {
dump_probable_cause(log, thread_info.siginfo, unwinder->GetMaps(),
thread_info.registers.get());
}
@@ -427,6 +431,8 @@
gwp_asan_crash_data->DumpAllocationTrace(log, unwinder);
}
+ scudo_crash_data->DumpCause(log, unwinder);
+
unwindstack::Maps* maps = unwinder->GetMaps();
dump_memory_and_code(log, maps, unwinder->GetProcessMemory().get(),
thread_info.registers.get());
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 3bf28b6..c8a3431 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -35,6 +35,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include <bionic/mte_kernel.h>
#include <bionic/reserved_signals.h>
#include <debuggerd/handler.h>
#include <log/log.h>
@@ -374,6 +375,12 @@
return "SEGV_ADIDERR";
case SEGV_ADIPERR:
return "SEGV_ADIPERR";
+#if defined(ANDROID_EXPERIMENTAL_MTE)
+ case SEGV_MTEAERR:
+ return "SEGV_MTEAERR";
+ case SEGV_MTESERR:
+ return "SEGV_MTESERR";
+#endif
}
static_assert(NSIGSEGV == SEGV_ADIPERR, "missing SEGV_* si_code");
break;
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index e85660c..53a76ea 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -95,6 +95,8 @@
uintptr_t fdsan_table_address;
uintptr_t gwp_asan_state;
uintptr_t gwp_asan_metadata;
+ uintptr_t scudo_stack_depot;
+ uintptr_t scudo_region_info;
};
struct __attribute__((__packed__)) CrashInfo {