[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:
Mitch Phillips 2020-02-14 14:54:31 -08:00
parent 5ed0698b41
commit e0b4bb1b2e
11 changed files with 598 additions and 17 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

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

View 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_;
};

View file

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

View file

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

View file

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

View file

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