[GWP-ASan] Add GWP-ASan information to tombstones.
GWP-ASan can provide information about a crash that it caused. Grab the GWP-ASan regions from the globals shared by the linker for crash-handler purpopses, pull the information from GWP-ASan, and display it. This adds two regions: 1. Causality tracking by GWP-ASan. We now print a cause header about the crash, like `Cause: [GWP-ASan]: Use After Free on a 1-byte allocation at 0x7365bb3ff8` 2. Allocation and deallocation stack traces. Bug: 135634846 Test: atest debuggerd_test Change-Id: Id28d5400c9a9a053fcde83a4788f971e677d4643
This commit is contained in:
parent
5ed0698b41
commit
e0b4bb1b2e
11 changed files with 598 additions and 17 deletions
|
@ -171,6 +171,7 @@ cc_library_static {
|
|||
|
||||
srcs: [
|
||||
"libdebuggerd/backtrace.cpp",
|
||||
"libdebuggerd/gwp_asan.cpp",
|
||||
"libdebuggerd/open_files_list.cpp",
|
||||
"libdebuggerd/tombstone.cpp",
|
||||
"libdebuggerd/utility.cpp",
|
||||
|
@ -181,7 +182,10 @@ cc_library_static {
|
|||
|
||||
// Needed for private/bionic_fdsan.h
|
||||
include_dirs: ["bionic/libc"],
|
||||
header_libs: ["bionic_libc_platform_headers"],
|
||||
header_libs: [
|
||||
"bionic_libc_platform_headers",
|
||||
"gwp_asan_headers",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"libdexfile_support_static", // libunwindstack dependency
|
||||
|
@ -192,6 +196,8 @@ cc_library_static {
|
|||
"liblog",
|
||||
],
|
||||
|
||||
whole_static_libs: ["gwp_asan_crash_handler"],
|
||||
|
||||
target: {
|
||||
recovery: {
|
||||
exclude_static_libs: [
|
||||
|
@ -246,10 +252,12 @@ cc_test {
|
|||
|
||||
static_libs: [
|
||||
"libdebuggerd",
|
||||
"libgmock",
|
||||
],
|
||||
|
||||
header_libs: [
|
||||
"bionic_libc_platform_headers",
|
||||
"gwp_asan_headers",
|
||||
],
|
||||
|
||||
local_include_dirs: [
|
||||
|
|
|
@ -73,8 +73,8 @@ TEST(debuggerd_client, race) {
|
|||
unique_fd pipe_read, pipe_write;
|
||||
ASSERT_TRUE(Pipe(&pipe_read, &pipe_write));
|
||||
|
||||
// 64 MiB should be enough for everyone.
|
||||
constexpr int PIPE_SIZE = 64 * 1024 * 1024;
|
||||
// 16 MiB should be enough for everyone.
|
||||
constexpr int PIPE_SIZE = 16 * 1024 * 1024;
|
||||
ASSERT_EQ(PIPE_SIZE, fcntl(pipe_read.get(), F_SETPIPE_SZ, PIPE_SIZE));
|
||||
|
||||
// Wait for a bit to let the child spawn all of its threads.
|
||||
|
|
|
@ -255,7 +255,8 @@ static void ParseArgs(int argc, char** argv, pid_t* pseudothread_tid, DebuggerdD
|
|||
|
||||
static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
|
||||
std::unique_ptr<unwindstack::Regs>* regs, uintptr_t* abort_msg_address,
|
||||
uintptr_t* fdsan_table_address) {
|
||||
uintptr_t* fdsan_table_address, uintptr_t* gwp_asan_state,
|
||||
uintptr_t* gwp_asan_metadata) {
|
||||
std::aligned_storage<sizeof(CrashInfo) + 1, alignof(CrashInfo)>::type buf;
|
||||
CrashInfo* crash_info = reinterpret_cast<CrashInfo*>(&buf);
|
||||
ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), &buf, sizeof(buf)));
|
||||
|
@ -272,6 +273,10 @@ static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
|
|||
expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV2);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV3);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG(FATAL) << "unexpected CrashInfo version: " << crash_info->header.version;
|
||||
break;
|
||||
|
@ -284,7 +289,13 @@ static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
|
|||
}
|
||||
|
||||
*fdsan_table_address = 0;
|
||||
*gwp_asan_state = 0;
|
||||
*gwp_asan_metadata = 0;
|
||||
switch (crash_info->header.version) {
|
||||
case 3:
|
||||
*gwp_asan_state = crash_info->data.v3.gwp_asan_state;
|
||||
*gwp_asan_metadata = crash_info->data.v3.gwp_asan_metadata;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 2:
|
||||
*fdsan_table_address = crash_info->data.v2.fdsan_table_address;
|
||||
FALLTHROUGH_INTENDED;
|
||||
|
@ -416,6 +427,8 @@ int main(int argc, char** argv) {
|
|||
DebuggerdDumpType dump_type;
|
||||
uintptr_t abort_msg_address = 0;
|
||||
uintptr_t fdsan_table_address = 0;
|
||||
uintptr_t gwp_asan_state = 0;
|
||||
uintptr_t gwp_asan_metadata = 0;
|
||||
|
||||
Initialize(argv);
|
||||
ParseArgs(argc, argv, &pseudothread_tid, &dump_type);
|
||||
|
@ -477,7 +490,7 @@ int main(int argc, char** argv) {
|
|||
if (thread == g_target_thread) {
|
||||
// Read the thread's registers along with the rest of the crash info out of the pipe.
|
||||
ReadCrashInfo(input_pipe, &siginfo, &info.registers, &abort_msg_address,
|
||||
&fdsan_table_address);
|
||||
&fdsan_table_address, &gwp_asan_state, &gwp_asan_metadata);
|
||||
info.siginfo = &siginfo;
|
||||
info.signo = info.siginfo->si_signo;
|
||||
} else {
|
||||
|
@ -592,7 +605,8 @@ int main(int argc, char** argv) {
|
|||
{
|
||||
ATRACE_NAME("engrave_tombstone");
|
||||
engrave_tombstone(std::move(g_output_fd), &unwinder, thread_info, g_target_thread,
|
||||
abort_msg_address, &open_files, &amfd_data);
|
||||
abort_msg_address, &open_files, &amfd_data, gwp_asan_state,
|
||||
gwp_asan_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <async_safe/log.h>
|
||||
#include <bionic/reserved_signals.h>
|
||||
|
@ -298,6 +299,8 @@ struct debugger_thread_info {
|
|||
void* ucontext;
|
||||
uintptr_t abort_msg;
|
||||
uintptr_t fdsan_table;
|
||||
uintptr_t gwp_asan_state;
|
||||
uintptr_t gwp_asan_metadata;
|
||||
};
|
||||
|
||||
// Logging and contacting debuggerd requires free file descriptors, which we might not have.
|
||||
|
@ -342,23 +345,25 @@ static int debuggerd_dispatch_pseudothread(void* arg) {
|
|||
}
|
||||
|
||||
// ucontext_t is absurdly large on AArch64, so piece it together manually with writev.
|
||||
uint32_t version = 2;
|
||||
constexpr size_t expected = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV2);
|
||||
uint32_t version = 3;
|
||||
constexpr size_t expected = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV3);
|
||||
|
||||
errno = 0;
|
||||
if (fcntl(output_write.get(), F_SETPIPE_SZ, expected) < static_cast<int>(expected)) {
|
||||
fatal_errno("failed to set pipe buffer size");
|
||||
}
|
||||
|
||||
struct iovec iovs[5] = {
|
||||
struct iovec iovs[] = {
|
||||
{.iov_base = &version, .iov_len = sizeof(version)},
|
||||
{.iov_base = thread_info->siginfo, .iov_len = sizeof(siginfo_t)},
|
||||
{.iov_base = thread_info->ucontext, .iov_len = sizeof(ucontext_t)},
|
||||
{.iov_base = &thread_info->abort_msg, .iov_len = sizeof(uintptr_t)},
|
||||
{.iov_base = &thread_info->fdsan_table, .iov_len = sizeof(uintptr_t)},
|
||||
{.iov_base = &thread_info->gwp_asan_state, .iov_len = sizeof(uintptr_t)},
|
||||
{.iov_base = &thread_info->gwp_asan_metadata, .iov_len = sizeof(uintptr_t)},
|
||||
};
|
||||
|
||||
ssize_t rc = TEMP_FAILURE_RETRY(writev(output_write.get(), iovs, 5));
|
||||
ssize_t rc = TEMP_FAILURE_RETRY(writev(output_write.get(), iovs, arraysize(iovs)));
|
||||
if (rc == -1) {
|
||||
fatal_errno("failed to write crash info");
|
||||
} else if (rc != expected) {
|
||||
|
@ -485,6 +490,8 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
|
|||
}
|
||||
|
||||
void* abort_message = nullptr;
|
||||
const gwp_asan::AllocatorState* gwp_asan_state = nullptr;
|
||||
const gwp_asan::AllocationMetadata* gwp_asan_metadata = nullptr;
|
||||
uintptr_t si_val = reinterpret_cast<uintptr_t>(info->si_ptr);
|
||||
if (signal_number == BIONIC_SIGNAL_DEBUGGER) {
|
||||
if (info->si_code == SI_QUEUE && info->si_pid == __getpid()) {
|
||||
|
@ -499,6 +506,12 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
|
|||
if (g_callbacks.get_abort_message) {
|
||||
abort_message = g_callbacks.get_abort_message();
|
||||
}
|
||||
if (g_callbacks.get_gwp_asan_state) {
|
||||
gwp_asan_state = g_callbacks.get_gwp_asan_state();
|
||||
}
|
||||
if (g_callbacks.get_gwp_asan_metadata) {
|
||||
gwp_asan_metadata = g_callbacks.get_gwp_asan_metadata();
|
||||
}
|
||||
}
|
||||
|
||||
// If sival_int is ~0, it means that the fallback handler has been called
|
||||
|
@ -532,6 +545,8 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
|
|||
.ucontext = context,
|
||||
.abort_msg = reinterpret_cast<uintptr_t>(abort_message),
|
||||
.fdsan_table = reinterpret_cast<uintptr_t>(android_fdsan_get_fd_table()),
|
||||
.gwp_asan_state = reinterpret_cast<uintptr_t>(gwp_asan_state),
|
||||
.gwp_asan_metadata = reinterpret_cast<uintptr_t>(gwp_asan_metadata),
|
||||
};
|
||||
|
||||
// Set PR_SET_DUMPABLE to 1, so that crash_dump can ptrace us.
|
||||
|
|
|
@ -24,11 +24,20 @@
|
|||
|
||||
__BEGIN_DECLS
|
||||
|
||||
// Forward declare these classes so not everyone has to include GWP-ASan
|
||||
// headers.
|
||||
namespace gwp_asan {
|
||||
struct AllocatorState;
|
||||
struct AllocationMetadata;
|
||||
}; // namespace gwp_asan
|
||||
|
||||
// These callbacks are called in a signal handler, and thus must be async signal safe.
|
||||
// If null, the callbacks will not be called.
|
||||
typedef struct {
|
||||
struct abort_msg_t* (*get_abort_message)();
|
||||
void (*post_dump)();
|
||||
const struct gwp_asan::AllocatorState* (*get_gwp_asan_state)();
|
||||
const struct gwp_asan::AllocationMetadata* (*get_gwp_asan_metadata)();
|
||||
} debuggerd_callbacks_t;
|
||||
|
||||
void debuggerd_init(debuggerd_callbacks_t* callbacks);
|
||||
|
|
273
debuggerd/libdebuggerd/gwp_asan.cpp
Normal file
273
debuggerd/libdebuggerd/gwp_asan.cpp
Normal file
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
* 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/gwp_asan.h"
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
#include "gwp_asan/common.h"
|
||||
#include "gwp_asan/crash_handler.h"
|
||||
|
||||
#include <unwindstack/Maps.h>
|
||||
#include <unwindstack/Memory.h>
|
||||
#include <unwindstack/Regs.h>
|
||||
#include <unwindstack/Unwinder.h>
|
||||
|
||||
// Retrieve GWP-ASan state from `state_addr` inside the process at
|
||||
// `process_memory`. Place the state into `*state`.
|
||||
static bool retrieve_gwp_asan_state(unwindstack::Memory* process_memory, uintptr_t state_addr,
|
||||
gwp_asan::AllocatorState* state) {
|
||||
return process_memory->ReadFully(state_addr, state, sizeof(*state));
|
||||
}
|
||||
|
||||
// Retrieve the GWP-ASan metadata pool from `metadata_addr` inside the process
|
||||
// at `process_memory`. The number of metadata slots is retrieved from the
|
||||
// allocator state provided. This function returns a heap-allocated copy of the
|
||||
// metadata pool whose ownership should be managed by the caller. Returns
|
||||
// nullptr on failure.
|
||||
static const gwp_asan::AllocationMetadata* retrieve_gwp_asan_metadata(
|
||||
unwindstack::Memory* process_memory, const gwp_asan::AllocatorState& state,
|
||||
uintptr_t metadata_addr) {
|
||||
if (state.MaxSimultaneousAllocations > 1024) {
|
||||
ALOGE(
|
||||
"Error when retrieving GWP-ASan metadata, MSA from state (%zu) "
|
||||
"exceeds maximum allowed (1024).",
|
||||
state.MaxSimultaneousAllocations);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gwp_asan::AllocationMetadata* meta =
|
||||
new gwp_asan::AllocationMetadata[state.MaxSimultaneousAllocations];
|
||||
if (!process_memory->ReadFully(metadata_addr, meta,
|
||||
sizeof(*meta) * state.MaxSimultaneousAllocations)) {
|
||||
ALOGE(
|
||||
"Error when retrieving GWP-ASan metadata, could not retrieve %zu "
|
||||
"pieces of metadata.",
|
||||
state.MaxSimultaneousAllocations);
|
||||
delete[] meta;
|
||||
meta = nullptr;
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
GwpAsanCrashData::GwpAsanCrashData(unwindstack::Memory* process_memory,
|
||||
uintptr_t gwp_asan_state_ptr, uintptr_t gwp_asan_metadata_ptr,
|
||||
const ThreadInfo& thread_info) {
|
||||
if (!process_memory || !gwp_asan_metadata_ptr || !gwp_asan_state_ptr) return;
|
||||
// Extract the GWP-ASan regions from the dead process.
|
||||
if (!retrieve_gwp_asan_state(process_memory, gwp_asan_state_ptr, &state_)) return;
|
||||
metadata_.reset(retrieve_gwp_asan_metadata(process_memory, state_, gwp_asan_metadata_ptr));
|
||||
if (!metadata_.get()) return;
|
||||
|
||||
// Get the external crash address from the thread info.
|
||||
crash_address_ = 0u;
|
||||
if (signal_has_si_addr(thread_info.siginfo)) {
|
||||
crash_address_ = reinterpret_cast<uintptr_t>(thread_info.siginfo->si_addr);
|
||||
}
|
||||
|
||||
// Ensure the error belongs to GWP-ASan.
|
||||
if (!__gwp_asan_error_is_mine(&state_, crash_address_)) return;
|
||||
|
||||
is_gwp_asan_responsible_ = true;
|
||||
thread_id_ = thread_info.tid;
|
||||
|
||||
// Grab the internal error address, if it exists.
|
||||
uintptr_t internal_crash_address = __gwp_asan_get_internal_crash_address(&state_);
|
||||
if (internal_crash_address) {
|
||||
crash_address_ = internal_crash_address;
|
||||
}
|
||||
|
||||
// Get other information from the internal state.
|
||||
error_ = __gwp_asan_diagnose_error(&state_, metadata_.get(), crash_address_);
|
||||
error_string_ = gwp_asan::ErrorToString(error_);
|
||||
responsible_allocation_ = __gwp_asan_get_metadata(&state_, metadata_.get(), crash_address_);
|
||||
}
|
||||
|
||||
bool GwpAsanCrashData::CrashIsMine() const {
|
||||
return is_gwp_asan_responsible_;
|
||||
}
|
||||
|
||||
void GwpAsanCrashData::DumpCause(log_t* log) const {
|
||||
if (!CrashIsMine()) {
|
||||
ALOGE("Internal Error: DumpCause() on a non-GWP-ASan crash.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (error_ == gwp_asan::Error::UNKNOWN) {
|
||||
_LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: Unknown error occurred at 0x%" PRIxPTR ".\n",
|
||||
crash_address_);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!responsible_allocation_) {
|
||||
_LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: %s at 0x%" PRIxPTR ".\n", error_string_,
|
||||
crash_address_);
|
||||
return;
|
||||
}
|
||||
|
||||
uintptr_t alloc_address = __gwp_asan_get_allocation_address(responsible_allocation_);
|
||||
size_t alloc_size = __gwp_asan_get_allocation_size(responsible_allocation_);
|
||||
|
||||
if (crash_address_ == alloc_address) {
|
||||
// Use After Free on a 41-byte allocation at 0xdeadbeef.
|
||||
_LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: %s on a %zu-byte allocation at 0x%" PRIxPTR "\n",
|
||||
error_string_, alloc_size, alloc_address);
|
||||
return;
|
||||
}
|
||||
|
||||
uintptr_t diff;
|
||||
const char* location_str;
|
||||
|
||||
if (crash_address_ < alloc_address) {
|
||||
// Buffer Underflow, 6 bytes left of a 41-byte allocation at 0xdeadbeef.
|
||||
location_str = "left of";
|
||||
diff = alloc_address - crash_address_;
|
||||
} else if (crash_address_ - alloc_address < alloc_size) {
|
||||
// Use After Free, 40 bytes into a 41-byte allocation at 0xdeadbeef.
|
||||
location_str = "into";
|
||||
diff = crash_address_ - alloc_address;
|
||||
} else {
|
||||
// Buffer Overflow, 6 bytes right of a 41-byte allocation at 0xdeadbeef, or
|
||||
// Invalid Free, 47 bytes right of a 41-byte allocation at 0xdeadbeef.
|
||||
location_str = "right of";
|
||||
diff = crash_address_ - alloc_address;
|
||||
if (error_ == gwp_asan::Error::BUFFER_OVERFLOW) {
|
||||
diff -= alloc_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,
|
||||
"Cause: [GWP-ASan]: %s, %" PRIuPTR " byte%s %s a %zu-byte allocation at 0x%" PRIxPTR "\n",
|
||||
error_string_, diff, byte_suffix, location_str, alloc_size, alloc_address);
|
||||
}
|
||||
|
||||
// Build a frame for symbolization using the maps from the provided unwinder.
|
||||
// The constructed frame contains just enough information to be used to
|
||||
// symbolize a GWP-ASan stack trace.
|
||||
static unwindstack::FrameData BuildFrame(unwindstack::Unwinder* unwinder, uintptr_t pc,
|
||||
size_t frame_num) {
|
||||
unwindstack::FrameData frame;
|
||||
frame.num = frame_num;
|
||||
|
||||
unwindstack::Maps* maps = unwinder->GetMaps();
|
||||
unwindstack::MapInfo* map_info = maps->Find(pc);
|
||||
if (!map_info) {
|
||||
frame.rel_pc = pc;
|
||||
return frame;
|
||||
}
|
||||
|
||||
unwindstack::Elf* elf =
|
||||
map_info->GetElf(unwinder->GetProcessMemory(), unwindstack::Regs::CurrentArch());
|
||||
|
||||
uint64_t relative_pc = elf->GetRelPc(pc, map_info);
|
||||
|
||||
// Create registers just to get PC adjustment. Doesn't matter what they point
|
||||
// to.
|
||||
unwindstack::Regs* regs = unwindstack::Regs::CreateFromLocal();
|
||||
uint64_t pc_adjustment = regs->GetPcAdjustment(relative_pc, elf);
|
||||
relative_pc -= pc_adjustment;
|
||||
// The debug PC may be different if the PC comes from the JIT.
|
||||
uint64_t debug_pc = relative_pc;
|
||||
|
||||
// If we don't have a valid ELF file, check the JIT.
|
||||
if (!elf->valid()) {
|
||||
unwindstack::JitDebug jit_debug(unwinder->GetProcessMemory());
|
||||
uint64_t jit_pc = pc - pc_adjustment;
|
||||
unwindstack::Elf* jit_elf = jit_debug.GetElf(maps, jit_pc);
|
||||
if (jit_elf != nullptr) {
|
||||
debug_pc = jit_pc;
|
||||
elf = jit_elf;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all the things we need into the frame for symbolization.
|
||||
frame.rel_pc = relative_pc;
|
||||
frame.pc = pc - pc_adjustment;
|
||||
frame.map_name = map_info->name;
|
||||
frame.map_elf_start_offset = map_info->elf_start_offset;
|
||||
frame.map_exact_offset = map_info->offset;
|
||||
frame.map_start = map_info->start;
|
||||
frame.map_end = map_info->end;
|
||||
frame.map_flags = map_info->flags;
|
||||
frame.map_load_bias = elf->GetLoadBias();
|
||||
|
||||
if (!elf->GetFunctionName(relative_pc, &frame.function_name, &frame.function_offset)) {
|
||||
frame.function_name = "";
|
||||
frame.function_offset = 0;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
|
||||
|
||||
bool GwpAsanCrashData::HasDeallocationTrace() const {
|
||||
assert(CrashIsMine() && "HasDeallocationTrace(): Crash is not mine!");
|
||||
if (!responsible_allocation_ || !__gwp_asan_is_deallocated(responsible_allocation_)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GwpAsanCrashData::DumpDeallocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const {
|
||||
assert(HasDeallocationTrace() && "DumpDeallocationTrace(): No dealloc trace!");
|
||||
uint64_t thread_id = __gwp_asan_get_deallocation_thread_id(responsible_allocation_);
|
||||
|
||||
std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
|
||||
size_t num_frames =
|
||||
__gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
|
||||
|
||||
if (thread_id == gwp_asan::kInvalidThreadID) {
|
||||
_LOG(log, logtype::BACKTRACE, "\ndeallocated by thread <unknown>:\n");
|
||||
} else {
|
||||
_LOG(log, logtype::BACKTRACE, "\ndeallocated by thread %" PRIu64 ":\n", thread_id);
|
||||
}
|
||||
|
||||
unwinder->SetDisplayBuildID(true);
|
||||
for (size_t i = 0; i < num_frames; ++i) {
|
||||
unwindstack::FrameData frame_data = BuildFrame(unwinder, frames.get()[i], i);
|
||||
_LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool GwpAsanCrashData::HasAllocationTrace() const {
|
||||
assert(CrashIsMine() && "HasAllocationTrace(): Crash is not mine!");
|
||||
return responsible_allocation_ != nullptr;
|
||||
}
|
||||
|
||||
void GwpAsanCrashData::DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const {
|
||||
assert(HasAllocationTrace() && "DumpAllocationTrace(): No dealloc trace!");
|
||||
uint64_t thread_id = __gwp_asan_get_allocation_thread_id(responsible_allocation_);
|
||||
|
||||
std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
|
||||
size_t num_frames =
|
||||
__gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
|
||||
|
||||
if (thread_id == gwp_asan::kInvalidThreadID) {
|
||||
_LOG(log, logtype::BACKTRACE, "\nallocated by thread <unknown>:\n");
|
||||
} else {
|
||||
_LOG(log, logtype::BACKTRACE, "\nallocated by thread %" PRIu64 ":\n", thread_id);
|
||||
}
|
||||
|
||||
unwinder->SetDisplayBuildID(true);
|
||||
for (size_t i = 0; i < num_frames; ++i) {
|
||||
unwindstack::FrameData frame_data = BuildFrame(unwinder, frames.get()[i], i);
|
||||
_LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str());
|
||||
}
|
||||
}
|
95
debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
Normal file
95
debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <log/log.h>
|
||||
#include <unwindstack/Memory.h>
|
||||
|
||||
#include "gwp_asan/common.h"
|
||||
#include "types.h"
|
||||
#include "utility.h"
|
||||
|
||||
class GwpAsanCrashData {
|
||||
public:
|
||||
GwpAsanCrashData() = delete;
|
||||
~GwpAsanCrashData() = default;
|
||||
|
||||
// Construct the crash data object. Takes a handle to the object that can
|
||||
// supply the memory of the dead process, and pointers to the GWP-ASan state
|
||||
// and metadata regions within that process. Also takes the thread information
|
||||
// of the crashed process. If the process didn't crash via SEGV, GWP-ASan may
|
||||
// still be responsible, as it terminates when it detects an internal error
|
||||
// (double free, invalid free). In these cases, we will retrieve the fault
|
||||
// address from the GWP-ASan allocator's state.
|
||||
GwpAsanCrashData(unwindstack::Memory* process_memory, uintptr_t gwp_asan_state_ptr,
|
||||
uintptr_t gwp_asan_metadata_ptr, const ThreadInfo& thread_info);
|
||||
|
||||
// Is GWP-ASan responsible for this crash.
|
||||
bool CrashIsMine() const;
|
||||
|
||||
// Returns the fault address. The fault address may be the same as provided
|
||||
// during construction, or it may have been retrieved from GWP-ASan's internal
|
||||
// allocator crash state.
|
||||
uintptr_t GetFaultAddress() const;
|
||||
|
||||
// Dump the GWP-ASan stringified cause of this crash. May only be called if
|
||||
// CrashIsMine() returns true.
|
||||
void DumpCause(log_t* log) const;
|
||||
|
||||
// Returns whether this crash has a deallocation trace. May only be called if
|
||||
// CrashIsMine() returns true.
|
||||
bool HasDeallocationTrace() const;
|
||||
|
||||
// Dump the GWP-ASan deallocation trace for this crash. May only be called if
|
||||
// HasDeallocationTrace() returns true.
|
||||
void DumpDeallocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const;
|
||||
|
||||
// Returns whether this crash has a allocation trace. May only be called if
|
||||
// CrashIsMine() returns true.
|
||||
bool HasAllocationTrace() const;
|
||||
|
||||
// Dump the GWP-ASan allocation trace for this crash. May only be called if
|
||||
// HasAllocationTrace() returns true.
|
||||
void DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const;
|
||||
|
||||
protected:
|
||||
// Is GWP-ASan responsible for this crash.
|
||||
bool is_gwp_asan_responsible_ = false;
|
||||
|
||||
// Thread ID of the crash.
|
||||
size_t thread_id_;
|
||||
|
||||
// The type of error that GWP-ASan caused (and the stringified version),
|
||||
// Undefined if GWP-ASan isn't responsible for the crash.
|
||||
gwp_asan::Error error_;
|
||||
const char* error_string_;
|
||||
|
||||
// Pointer to the crash address. Holds the internal crash address if it
|
||||
// exists, otherwise the address provided at construction.
|
||||
uintptr_t crash_address_ = 0u;
|
||||
|
||||
// Pointer to the metadata for the responsible allocation, nullptr if it
|
||||
// doesn't exist.
|
||||
const gwp_asan::AllocationMetadata* responsible_allocation_ = nullptr;
|
||||
|
||||
// Internal state.
|
||||
gwp_asan::AllocatorState state_;
|
||||
std::unique_ptr<const gwp_asan::AllocationMetadata> metadata_;
|
||||
};
|
|
@ -55,6 +55,7 @@ void engrave_tombstone_ucontext(int tombstone_fd, uint64_t abort_msg_address, si
|
|||
void engrave_tombstone(android::base::unique_fd output_fd, unwindstack::Unwinder* unwinder,
|
||||
const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread,
|
||||
uint64_t abort_msg_address, OpenFilesList* open_files,
|
||||
std::string* amfd_data);
|
||||
std::string* amfd_data, uintptr_t gwp_asan_state,
|
||||
uintptr_t gwp_asan_metadata);
|
||||
|
||||
#endif // _DEBUGGERD_TOMBSTONE_H
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/properties.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
@ -31,8 +32,13 @@
|
|||
#include "host_signal_fixup.h"
|
||||
#include "log_fake.h"
|
||||
|
||||
// Include tombstone.cpp to define log_tag before GWP-ASan includes log.
|
||||
#include "tombstone.cpp"
|
||||
|
||||
#include "gwp_asan.cpp"
|
||||
|
||||
using ::testing::MatchesRegex;
|
||||
|
||||
class TombstoneTest : public ::testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
|
@ -359,3 +365,131 @@ TEST_F(TombstoneTest, dump_timestamp) {
|
|||
dump_timestamp(&log_, 0);
|
||||
ASSERT_STREQ("Timestamp: 1970-01-01 00:00:00+0000\n", amfd_data_.c_str());
|
||||
}
|
||||
|
||||
class GwpAsanCrashDataTest : public GwpAsanCrashData {
|
||||
public:
|
||||
GwpAsanCrashDataTest(
|
||||
gwp_asan::Error error,
|
||||
const gwp_asan::AllocationMetadata *responsible_allocation) :
|
||||
GwpAsanCrashData(nullptr, 0u, 0u, ThreadInfo{}) {
|
||||
is_gwp_asan_responsible_ = true;
|
||||
error_ = error;
|
||||
responsible_allocation_ = responsible_allocation;
|
||||
error_string_ = gwp_asan::ErrorToString(error_);
|
||||
}
|
||||
|
||||
void SetCrashAddress(uintptr_t crash_address) {
|
||||
crash_address_ = crash_address;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TombstoneTest, gwp_asan_cause_uaf_exact) {
|
||||
gwp_asan::AllocationMetadata meta;
|
||||
meta.Addr = 0x1000;
|
||||
meta.Size = 32;
|
||||
|
||||
GwpAsanCrashDataTest crash_data(gwp_asan::Error::USE_AFTER_FREE, &meta);
|
||||
crash_data.SetCrashAddress(0x1000);
|
||||
|
||||
crash_data.DumpCause(&log_);
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
|
||||
ASSERT_THAT(tombstone_contents,
|
||||
MatchesRegex("Cause: \\[GWP-ASan\\]: Use After Free on a 32-byte "
|
||||
"allocation at 0x[a-fA-F0-9]+\n"));
|
||||
}
|
||||
|
||||
TEST_F(TombstoneTest, gwp_asan_cause_double_free) {
|
||||
gwp_asan::AllocationMetadata meta;
|
||||
meta.Addr = 0x1000;
|
||||
meta.Size = 32;
|
||||
|
||||
GwpAsanCrashDataTest crash_data(gwp_asan::Error::DOUBLE_FREE, &meta);
|
||||
crash_data.SetCrashAddress(0x1000);
|
||||
|
||||
crash_data.DumpCause(&log_);
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
|
||||
ASSERT_THAT(tombstone_contents,
|
||||
MatchesRegex("Cause: \\[GWP-ASan\\]: Double Free on a 32-byte "
|
||||
"allocation at 0x[a-fA-F0-9]+\n"));
|
||||
}
|
||||
|
||||
TEST_F(TombstoneTest, gwp_asan_cause_overflow) {
|
||||
gwp_asan::AllocationMetadata meta;
|
||||
meta.Addr = 0x1000;
|
||||
meta.Size = 32;
|
||||
|
||||
GwpAsanCrashDataTest crash_data(gwp_asan::Error::BUFFER_OVERFLOW, &meta);
|
||||
crash_data.SetCrashAddress(0x1025);
|
||||
|
||||
crash_data.DumpCause(&log_);
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
|
||||
ASSERT_THAT(
|
||||
tombstone_contents,
|
||||
MatchesRegex(
|
||||
"Cause: \\[GWP-ASan\\]: Buffer Overflow, 5 bytes right of a 32-byte "
|
||||
"allocation at 0x[a-fA-F0-9]+\n"));
|
||||
}
|
||||
|
||||
TEST_F(TombstoneTest, gwp_asan_cause_underflow) {
|
||||
gwp_asan::AllocationMetadata meta;
|
||||
meta.Addr = 0x1000;
|
||||
meta.Size = 32;
|
||||
|
||||
GwpAsanCrashDataTest crash_data(gwp_asan::Error::BUFFER_UNDERFLOW, &meta);
|
||||
crash_data.SetCrashAddress(0xffe);
|
||||
|
||||
crash_data.DumpCause(&log_);
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
|
||||
ASSERT_THAT(
|
||||
tombstone_contents,
|
||||
MatchesRegex(
|
||||
"Cause: \\[GWP-ASan\\]: Buffer Underflow, 2 bytes left of a 32-byte "
|
||||
"allocation at 0x[a-fA-F0-9]+\n"));
|
||||
}
|
||||
|
||||
TEST_F(TombstoneTest, gwp_asan_cause_invalid_free_inside) {
|
||||
gwp_asan::AllocationMetadata meta;
|
||||
meta.Addr = 0x1000;
|
||||
meta.Size = 32;
|
||||
|
||||
GwpAsanCrashDataTest crash_data(gwp_asan::Error::INVALID_FREE, &meta);
|
||||
crash_data.SetCrashAddress(0x1001);
|
||||
|
||||
crash_data.DumpCause(&log_);
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
|
||||
ASSERT_THAT(
|
||||
tombstone_contents,
|
||||
MatchesRegex(
|
||||
"Cause: \\[GWP-ASan\\]: Invalid \\(Wild\\) Free, 1 byte into a 32-byte "
|
||||
"allocation at 0x[a-fA-F0-9]+\n"));
|
||||
}
|
||||
|
||||
TEST_F(TombstoneTest, gwp_asan_cause_invalid_free_outside) {
|
||||
gwp_asan::AllocationMetadata meta;
|
||||
meta.Addr = 0x1000;
|
||||
meta.Size = 32;
|
||||
|
||||
GwpAsanCrashDataTest crash_data(gwp_asan::Error::INVALID_FREE, &meta);
|
||||
crash_data.SetCrashAddress(0x1021);
|
||||
|
||||
crash_data.DumpCause(&log_);
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
|
||||
ASSERT_THAT(
|
||||
tombstone_contents,
|
||||
MatchesRegex(
|
||||
"Cause: \\[GWP-ASan\\]: Invalid \\(Wild\\) Free, 33 bytes right of a 32-byte "
|
||||
"allocation at 0x[a-fA-F0-9]+\n"));
|
||||
}
|
||||
|
||||
|
|
|
@ -53,9 +53,13 @@
|
|||
#include <unwindstack/Unwinder.h>
|
||||
|
||||
#include "libdebuggerd/backtrace.h"
|
||||
#include "libdebuggerd/gwp_asan.h"
|
||||
#include "libdebuggerd/open_files_list.h"
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
#include "gwp_asan/common.h"
|
||||
#include "gwp_asan/crash_handler.h"
|
||||
|
||||
using android::base::GetBoolProperty;
|
||||
using android::base::GetProperty;
|
||||
using android::base::StringPrintf;
|
||||
|
@ -372,7 +376,8 @@ void dump_memory_and_code(log_t* log, unwindstack::Maps* maps, unwindstack::Memo
|
|||
}
|
||||
|
||||
static bool dump_thread(log_t* log, unwindstack::Unwinder* unwinder, const ThreadInfo& thread_info,
|
||||
uint64_t abort_msg_address, bool primary_thread) {
|
||||
uint64_t abort_msg_address, bool primary_thread,
|
||||
const GwpAsanCrashData& gwp_asan_crash_data) {
|
||||
log->current_tid = thread_info.tid;
|
||||
if (!primary_thread) {
|
||||
_LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n");
|
||||
|
@ -381,7 +386,13 @@ static bool dump_thread(log_t* log, unwindstack::Unwinder* unwinder, const Threa
|
|||
|
||||
if (thread_info.siginfo) {
|
||||
dump_signal_info(log, thread_info, unwinder->GetProcessMemory().get());
|
||||
dump_probable_cause(log, thread_info.siginfo, unwinder->GetMaps(), thread_info.registers.get());
|
||||
}
|
||||
|
||||
if (primary_thread && gwp_asan_crash_data.CrashIsMine()) {
|
||||
gwp_asan_crash_data.DumpCause(log);
|
||||
} else if (thread_info.siginfo) {
|
||||
dump_probable_cause(log, thread_info.siginfo, unwinder->GetMaps(),
|
||||
thread_info.registers.get());
|
||||
}
|
||||
|
||||
if (primary_thread) {
|
||||
|
@ -402,6 +413,14 @@ static bool dump_thread(log_t* log, unwindstack::Unwinder* unwinder, const Threa
|
|||
}
|
||||
|
||||
if (primary_thread) {
|
||||
if (gwp_asan_crash_data.HasDeallocationTrace()) {
|
||||
gwp_asan_crash_data.DumpDeallocationTrace(log, unwinder);
|
||||
}
|
||||
|
||||
if (gwp_asan_crash_data.HasAllocationTrace()) {
|
||||
gwp_asan_crash_data.DumpAllocationTrace(log, unwinder);
|
||||
}
|
||||
|
||||
unwindstack::Maps* maps = unwinder->GetMaps();
|
||||
dump_memory_and_code(log, maps, unwinder->GetProcessMemory().get(),
|
||||
thread_info.registers.get());
|
||||
|
@ -583,13 +602,14 @@ void engrave_tombstone_ucontext(int tombstone_fd, uint64_t abort_msg_address, si
|
|||
}
|
||||
|
||||
engrave_tombstone(unique_fd(dup(tombstone_fd)), &unwinder, threads, tid, abort_msg_address,
|
||||
nullptr, nullptr);
|
||||
nullptr, nullptr, 0u, 0u);
|
||||
}
|
||||
|
||||
void engrave_tombstone(unique_fd output_fd, unwindstack::Unwinder* unwinder,
|
||||
const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
|
||||
uint64_t abort_msg_address, OpenFilesList* open_files,
|
||||
std::string* amfd_data) {
|
||||
std::string* amfd_data, uintptr_t gwp_asan_state_ptr,
|
||||
uintptr_t gwp_asan_metadata_ptr) {
|
||||
// don't copy log messages to tombstone unless this is a dev device
|
||||
bool want_logs = android::base::GetBoolProperty("ro.debuggable", false);
|
||||
|
||||
|
@ -607,7 +627,13 @@ void engrave_tombstone(unique_fd output_fd, unwindstack::Unwinder* unwinder,
|
|||
if (it == threads.end()) {
|
||||
LOG(FATAL) << "failed to find target thread";
|
||||
}
|
||||
dump_thread(&log, unwinder, it->second, abort_msg_address, true);
|
||||
|
||||
GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(),
|
||||
gwp_asan_state_ptr,
|
||||
gwp_asan_metadata_ptr, it->second);
|
||||
|
||||
dump_thread(&log, unwinder, it->second, abort_msg_address, true,
|
||||
gwp_asan_crash_data);
|
||||
|
||||
if (want_logs) {
|
||||
dump_logs(&log, it->second.pid, 50);
|
||||
|
@ -618,7 +644,7 @@ void engrave_tombstone(unique_fd output_fd, unwindstack::Unwinder* unwinder,
|
|||
continue;
|
||||
}
|
||||
|
||||
dump_thread(&log, unwinder, thread_info, 0, false);
|
||||
dump_thread(&log, unwinder, thread_info, 0, false, gwp_asan_crash_data);
|
||||
}
|
||||
|
||||
if (open_files) {
|
||||
|
|
|
@ -95,10 +95,16 @@ struct __attribute__((__packed__)) CrashInfoDataV2 : public CrashInfoDataV1 {
|
|||
uintptr_t fdsan_table_address;
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) CrashInfoDataV3 : public CrashInfoDataV2 {
|
||||
uintptr_t gwp_asan_state;
|
||||
uintptr_t gwp_asan_metadata;
|
||||
};
|
||||
|
||||
struct __attribute__((__packed__)) CrashInfo {
|
||||
CrashInfoHeader header;
|
||||
union {
|
||||
CrashInfoDataV1 v1;
|
||||
CrashInfoDataV2 v2;
|
||||
CrashInfoDataV3 v3;
|
||||
} data;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue