diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp index d67b52277..31c2d5d64 100644 --- a/debuggerd/Android.bp +++ b/debuggerd/Android.bp @@ -169,6 +169,7 @@ cc_library_static { "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 @@ cc_library_static { 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 @@ cc_library_static { "liblog", ], - whole_static_libs: ["gwp_asan_crash_handler"], + whole_static_libs: [ + "gwp_asan_crash_handler", + "libscudo", + ], target: { recovery: { @@ -206,6 +215,9 @@ cc_library_static { debuggable: { cflags: ["-DROOT_POSSIBLE"], }, + experimental_mte: { + cflags: ["-DANDROID_EXPERIMENTAL_MTE"], + }, }, } @@ -256,6 +268,10 @@ cc_test { "gwp_asan_headers", ], + include_dirs: [ + "external/scudo/standalone/include", + ], + local_include_dirs: [ "libdebuggerd", ], @@ -271,6 +287,12 @@ cc_test { }, 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 0cd2350a8..d7cb9725f 100644 --- a/debuggerd/crash_dump.cpp +++ b/debuggerd/crash_dump.cpp @@ -289,6 +289,8 @@ static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo, 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 054f83675..25417a985 100644 --- a/debuggerd/debuggerd_test.cpp +++ b/debuggerd/debuggerd_test.cpp @@ -31,6 +31,9 @@ #include #include +#include +#include +#include #include #include @@ -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 allocs; + for (int i = 0; i != 4096; ++i) { + uintptr_t alloc = reinterpret_cast(malloc(16)); + auto it = allocs.insert(alloc).first; + if (it != allocs.begin() && *std::prev(it) + 128 > alloc) { + *reinterpret_cast(*std::prev(it) + 16) = 42; + } + if (std::next(it) != allocs.end() && alloc + 128 > *std::next(it)) { + *reinterpret_cast(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 665029468..254ed4f7b 100644 --- a/debuggerd/include/debuggerd/handler.h +++ b/debuggerd/include/debuggerd/handler.h @@ -40,6 +40,8 @@ struct debugger_process_info { 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 000000000..4d00ecede --- /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 + +#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 35c3fd646..04c4b5c58 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h @@ -41,6 +41,8 @@ struct ProcessInfo { 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 000000000..f8bfe07ce --- /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 + +std::unique_ptr AllocAndReadFully(unwindstack::Memory* process_memory, uint64_t addr, + size_t size) { + auto buf = std::make_unique(size); + if (!process_memory->ReadFully(addr, buf.get(), size)) { + return std::unique_ptr(); + } + 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(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((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 d6b2e2537..ab65dd15a 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 @@ static bool dump_thread(log_t* log, unwindstack::Unwinder* unwinder, const Threa } std::unique_ptr gwp_asan_crash_data; + std::unique_ptr scudo_crash_data; if (primary_thread) { gwp_asan_crash_data = std::make_unique(unwinder->GetProcessMemory().get(), process_info, thread_info); + scudo_crash_data = + std::make_unique(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 @@ static bool dump_thread(log_t* log, unwindstack::Unwinder* unwinder, const Threa 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 3bf28b6c2..c8a3431b7 100644 --- a/debuggerd/libdebuggerd/utility.cpp +++ b/debuggerd/libdebuggerd/utility.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -374,6 +375,12 @@ const char* get_sigcode(const siginfo_t* si) { 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 e85660c85..53a76ea71 100644 --- a/debuggerd/protocol.h +++ b/debuggerd/protocol.h @@ -95,6 +95,8 @@ struct __attribute__((__packed__)) CrashInfoDataDynamic : public CrashInfoDataSt 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 {