From e0b4bb1b2e239f3ea694d6d33f577ff5257723e7 Mon Sep 17 00:00:00 2001 From: Mitch Phillips Date: Fri, 14 Feb 2020 14:54:31 -0800 Subject: [PATCH] [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 --- debuggerd/Android.bp | 10 +- debuggerd/client/debuggerd_client_test.cpp | 4 +- debuggerd/crash_dump.cpp | 20 +- debuggerd/handler/debuggerd_handler.cpp | 23 +- debuggerd/include/debuggerd/handler.h | 9 + debuggerd/libdebuggerd/gwp_asan.cpp | 273 ++++++++++++++++++ .../include/libdebuggerd/gwp_asan.h | 95 ++++++ .../include/libdebuggerd/tombstone.h | 3 +- .../libdebuggerd/test/tombstone_test.cpp | 134 +++++++++ debuggerd/libdebuggerd/tombstone.cpp | 38 ++- debuggerd/protocol.h | 6 + 11 files changed, 598 insertions(+), 17 deletions(-) create mode 100644 debuggerd/libdebuggerd/gwp_asan.cpp create mode 100644 debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp index e3ce5315f..f28c778fe 100644 --- a/debuggerd/Android.bp +++ b/debuggerd/Android.bp @@ -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: [ diff --git a/debuggerd/client/debuggerd_client_test.cpp b/debuggerd/client/debuggerd_client_test.cpp index 2545cd6e5..ebb8d86e0 100644 --- a/debuggerd/client/debuggerd_client_test.cpp +++ b/debuggerd/client/debuggerd_client_test.cpp @@ -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. diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp index e8f366fbe..3e998802b 100644 --- a/debuggerd/crash_dump.cpp +++ b/debuggerd/crash_dump.cpp @@ -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* 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::type buf; CrashInfo* crash_info = reinterpret_cast(&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); } } diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp index f8192b52d..8b4b6304f 100644 --- a/debuggerd/handler/debuggerd_handler.cpp +++ b/debuggerd/handler/debuggerd_handler.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include #include @@ -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(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(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(abort_message), .fdsan_table = reinterpret_cast(android_fdsan_get_fd_table()), + .gwp_asan_state = reinterpret_cast(gwp_asan_state), + .gwp_asan_metadata = reinterpret_cast(gwp_asan_metadata), }; // Set PR_SET_DUMPABLE to 1, so that crash_dump can ptrace us. diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h index cd6fc0510..4f24360dc 100644 --- a/debuggerd/include/debuggerd/handler.h +++ b/debuggerd/include/debuggerd/handler.h @@ -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); diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp new file mode 100644 index 000000000..53df783dc --- /dev/null +++ b/debuggerd/libdebuggerd/gwp_asan.cpp @@ -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 +#include +#include +#include + +// 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(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 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 :\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 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 :\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()); + } +} diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h new file mode 100644 index 000000000..aef4c62aa --- /dev/null +++ b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h @@ -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 +#include + +#include +#include + +#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 metadata_; +}; diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h index 7133f778d..291d994b3 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h @@ -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& 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 diff --git a/debuggerd/libdebuggerd/test/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp index b33adf310..eed95bc39 100644 --- a/debuggerd/libdebuggerd/test/tombstone_test.cpp +++ b/debuggerd/libdebuggerd/test/tombstone_test.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #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")); +} + diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp index 4e7f35c7f..fd52e8113 100644 --- a/debuggerd/libdebuggerd/tombstone.cpp +++ b/debuggerd/libdebuggerd/tombstone.cpp @@ -53,9 +53,13 @@ #include #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& 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) { diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h index bfd0fbbe2..bf5386412 100644 --- a/debuggerd/protocol.h +++ b/debuggerd/protocol.h @@ -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; };