diff options
author | Peter Collingbourne <pcc@google.com> | 2020-04-07 14:07:32 -0700 |
---|---|---|
committer | Peter Collingbourne <pcc@google.com> | 2020-04-27 13:15:49 -0700 |
commit | f86225206df162b43b5a0518a9eaf747d60303c5 (patch) | |
tree | 7bf781dd784e55d63b5275f089e300d77e6a0877 /debuggerd/debuggerd_test.cpp | |
parent | e0edc7ec328e6775e081c30b42837eb84ff282a4 (diff) |
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
Diffstat (limited to 'debuggerd/debuggerd_test.cpp')
-rw-r--r-- | debuggerd/debuggerd_test.cpp | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp index 054f83675..25417a985 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 @@ TEST_F(CrasherTest, tagged_fault_addr) { 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; |