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
This commit is contained in:
Peter Collingbourne 2020-04-07 14:07:32 -07:00
parent e0edc7ec32
commit f86225206d
10 changed files with 418 additions and 4 deletions

View File

@ -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 {

View File

@ -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:

View File

@ -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;

View File

@ -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.

View File

@ -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;
};

View File

@ -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;

View File

@ -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());
}
}
}

View File

@ -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<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 @@ 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());

View File

@ -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 @@ 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;

View File

@ -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 {