Merge "[GWP-ASan] Add GWP-ASan information to tombstones."

This commit is contained in:
Mitch Phillips 2020-02-19 20:35:10 +00:00 committed by Gerrit Code Review
commit 2dd81da3b4
11 changed files with 598 additions and 17 deletions

View file

@ -171,6 +171,7 @@ cc_library_static {
srcs: [ srcs: [
"libdebuggerd/backtrace.cpp", "libdebuggerd/backtrace.cpp",
"libdebuggerd/gwp_asan.cpp",
"libdebuggerd/open_files_list.cpp", "libdebuggerd/open_files_list.cpp",
"libdebuggerd/tombstone.cpp", "libdebuggerd/tombstone.cpp",
"libdebuggerd/utility.cpp", "libdebuggerd/utility.cpp",
@ -181,7 +182,10 @@ cc_library_static {
// Needed for private/bionic_fdsan.h // Needed for private/bionic_fdsan.h
include_dirs: ["bionic/libc"], include_dirs: ["bionic/libc"],
header_libs: ["bionic_libc_platform_headers"], header_libs: [
"bionic_libc_platform_headers",
"gwp_asan_headers",
],
static_libs: [ static_libs: [
"libdexfile_support_static", // libunwindstack dependency "libdexfile_support_static", // libunwindstack dependency
@ -192,6 +196,8 @@ cc_library_static {
"liblog", "liblog",
], ],
whole_static_libs: ["gwp_asan_crash_handler"],
target: { target: {
recovery: { recovery: {
exclude_static_libs: [ exclude_static_libs: [
@ -246,10 +252,12 @@ cc_test {
static_libs: [ static_libs: [
"libdebuggerd", "libdebuggerd",
"libgmock",
], ],
header_libs: [ header_libs: [
"bionic_libc_platform_headers", "bionic_libc_platform_headers",
"gwp_asan_headers",
], ],
local_include_dirs: [ local_include_dirs: [

View file

@ -73,8 +73,8 @@ TEST(debuggerd_client, race) {
unique_fd pipe_read, pipe_write; unique_fd pipe_read, pipe_write;
ASSERT_TRUE(Pipe(&pipe_read, &pipe_write)); ASSERT_TRUE(Pipe(&pipe_read, &pipe_write));
// 64 MiB should be enough for everyone. // 16 MiB should be enough for everyone.
constexpr int PIPE_SIZE = 64 * 1024 * 1024; constexpr int PIPE_SIZE = 16 * 1024 * 1024;
ASSERT_EQ(PIPE_SIZE, fcntl(pipe_read.get(), F_SETPIPE_SZ, PIPE_SIZE)); 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. // 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, static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
std::unique_ptr<unwindstack::Regs>* regs, uintptr_t* abort_msg_address, 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; std::aligned_storage<sizeof(CrashInfo) + 1, alignof(CrashInfo)>::type buf;
CrashInfo* crash_info = reinterpret_cast<CrashInfo*>(&buf); CrashInfo* crash_info = reinterpret_cast<CrashInfo*>(&buf);
ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), &buf, sizeof(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); expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV2);
break; break;
case 3:
expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV3);
break;
default: default:
LOG(FATAL) << "unexpected CrashInfo version: " << crash_info->header.version; LOG(FATAL) << "unexpected CrashInfo version: " << crash_info->header.version;
break; break;
@ -284,7 +289,13 @@ static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
} }
*fdsan_table_address = 0; *fdsan_table_address = 0;
*gwp_asan_state = 0;
*gwp_asan_metadata = 0;
switch (crash_info->header.version) { 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: case 2:
*fdsan_table_address = crash_info->data.v2.fdsan_table_address; *fdsan_table_address = crash_info->data.v2.fdsan_table_address;
FALLTHROUGH_INTENDED; FALLTHROUGH_INTENDED;
@ -416,6 +427,8 @@ int main(int argc, char** argv) {
DebuggerdDumpType dump_type; DebuggerdDumpType dump_type;
uintptr_t abort_msg_address = 0; uintptr_t abort_msg_address = 0;
uintptr_t fdsan_table_address = 0; uintptr_t fdsan_table_address = 0;
uintptr_t gwp_asan_state = 0;
uintptr_t gwp_asan_metadata = 0;
Initialize(argv); Initialize(argv);
ParseArgs(argc, argv, &pseudothread_tid, &dump_type); ParseArgs(argc, argv, &pseudothread_tid, &dump_type);
@ -477,7 +490,7 @@ int main(int argc, char** argv) {
if (thread == g_target_thread) { if (thread == g_target_thread) {
// Read the thread's registers along with the rest of the crash info out of the pipe. // 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, 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.siginfo = &siginfo;
info.signo = info.siginfo->si_signo; info.signo = info.siginfo->si_signo;
} else { } else {
@ -592,7 +605,8 @@ int main(int argc, char** argv) {
{ {
ATRACE_NAME("engrave_tombstone"); ATRACE_NAME("engrave_tombstone");
engrave_tombstone(std::move(g_output_fd), &unwinder, thread_info, g_target_thread, 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 <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include <android-base/macros.h>
#include <android-base/unique_fd.h> #include <android-base/unique_fd.h>
#include <async_safe/log.h> #include <async_safe/log.h>
#include <bionic/reserved_signals.h> #include <bionic/reserved_signals.h>
@ -298,6 +299,8 @@ struct debugger_thread_info {
void* ucontext; void* ucontext;
uintptr_t abort_msg; uintptr_t abort_msg;
uintptr_t fdsan_table; 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. // 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. // ucontext_t is absurdly large on AArch64, so piece it together manually with writev.
uint32_t version = 2; uint32_t version = 3;
constexpr size_t expected = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV2); constexpr size_t expected = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV3);
errno = 0; errno = 0;
if (fcntl(output_write.get(), F_SETPIPE_SZ, expected) < static_cast<int>(expected)) { if (fcntl(output_write.get(), F_SETPIPE_SZ, expected) < static_cast<int>(expected)) {
fatal_errno("failed to set pipe buffer size"); fatal_errno("failed to set pipe buffer size");
} }
struct iovec iovs[5] = { struct iovec iovs[] = {
{.iov_base = &version, .iov_len = sizeof(version)}, {.iov_base = &version, .iov_len = sizeof(version)},
{.iov_base = thread_info->siginfo, .iov_len = sizeof(siginfo_t)}, {.iov_base = thread_info->siginfo, .iov_len = sizeof(siginfo_t)},
{.iov_base = thread_info->ucontext, .iov_len = sizeof(ucontext_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->abort_msg, .iov_len = sizeof(uintptr_t)},
{.iov_base = &thread_info->fdsan_table, .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) { if (rc == -1) {
fatal_errno("failed to write crash info"); fatal_errno("failed to write crash info");
} else if (rc != expected) { } 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; 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); uintptr_t si_val = reinterpret_cast<uintptr_t>(info->si_ptr);
if (signal_number == BIONIC_SIGNAL_DEBUGGER) { if (signal_number == BIONIC_SIGNAL_DEBUGGER) {
if (info->si_code == SI_QUEUE && info->si_pid == __getpid()) { 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) { if (g_callbacks.get_abort_message) {
abort_message = 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 // 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, .ucontext = context,
.abort_msg = reinterpret_cast<uintptr_t>(abort_message), .abort_msg = reinterpret_cast<uintptr_t>(abort_message),
.fdsan_table = reinterpret_cast<uintptr_t>(android_fdsan_get_fd_table()), .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. // Set PR_SET_DUMPABLE to 1, so that crash_dump can ptrace us.

View file

@ -24,11 +24,20 @@
__BEGIN_DECLS __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. // These callbacks are called in a signal handler, and thus must be async signal safe.
// If null, the callbacks will not be called. // If null, the callbacks will not be called.
typedef struct { typedef struct {
struct abort_msg_t* (*get_abort_message)(); struct abort_msg_t* (*get_abort_message)();
void (*post_dump)(); void (*post_dump)();
const struct gwp_asan::AllocatorState* (*get_gwp_asan_state)();
const struct gwp_asan::AllocationMetadata* (*get_gwp_asan_metadata)();
} debuggerd_callbacks_t; } debuggerd_callbacks_t;
void debuggerd_init(debuggerd_callbacks_t* callbacks); 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, void engrave_tombstone(android::base::unique_fd output_fd, unwindstack::Unwinder* unwinder,
const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread, const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread,
uint64_t abort_msg_address, OpenFilesList* open_files, 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 #endif // _DEBUGGERD_TOMBSTONE_H

View file

@ -23,6 +23,7 @@
#include <android-base/file.h> #include <android-base/file.h>
#include <android-base/properties.h> #include <android-base/properties.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "libdebuggerd/utility.h" #include "libdebuggerd/utility.h"
@ -31,8 +32,13 @@
#include "host_signal_fixup.h" #include "host_signal_fixup.h"
#include "log_fake.h" #include "log_fake.h"
// Include tombstone.cpp to define log_tag before GWP-ASan includes log.
#include "tombstone.cpp" #include "tombstone.cpp"
#include "gwp_asan.cpp"
using ::testing::MatchesRegex;
class TombstoneTest : public ::testing::Test { class TombstoneTest : public ::testing::Test {
protected: protected:
virtual void SetUp() { virtual void SetUp() {
@ -359,3 +365,131 @@ TEST_F(TombstoneTest, dump_timestamp) {
dump_timestamp(&log_, 0); dump_timestamp(&log_, 0);
ASSERT_STREQ("Timestamp: 1970-01-01 00:00:00+0000\n", amfd_data_.c_str()); 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 <unwindstack/Unwinder.h>
#include "libdebuggerd/backtrace.h" #include "libdebuggerd/backtrace.h"
#include "libdebuggerd/gwp_asan.h"
#include "libdebuggerd/open_files_list.h" #include "libdebuggerd/open_files_list.h"
#include "libdebuggerd/utility.h" #include "libdebuggerd/utility.h"
#include "gwp_asan/common.h"
#include "gwp_asan/crash_handler.h"
using android::base::GetBoolProperty; using android::base::GetBoolProperty;
using android::base::GetProperty; using android::base::GetProperty;
using android::base::StringPrintf; 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, 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; log->current_tid = thread_info.tid;
if (!primary_thread) { if (!primary_thread) {
_LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n"); _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) { if (thread_info.siginfo) {
dump_signal_info(log, thread_info, unwinder->GetProcessMemory().get()); 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) { if (primary_thread) {
@ -402,6 +413,14 @@ static bool dump_thread(log_t* log, unwindstack::Unwinder* unwinder, const Threa
} }
if (primary_thread) { 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(); unwindstack::Maps* maps = unwinder->GetMaps();
dump_memory_and_code(log, maps, unwinder->GetProcessMemory().get(), dump_memory_and_code(log, maps, unwinder->GetProcessMemory().get(),
thread_info.registers.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, 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, void engrave_tombstone(unique_fd output_fd, unwindstack::Unwinder* unwinder,
const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread, const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
uint64_t abort_msg_address, OpenFilesList* open_files, 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 // don't copy log messages to tombstone unless this is a dev device
bool want_logs = android::base::GetBoolProperty("ro.debuggable", false); 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()) { if (it == threads.end()) {
LOG(FATAL) << "failed to find target thread"; 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) { if (want_logs) {
dump_logs(&log, it->second.pid, 50); dump_logs(&log, it->second.pid, 50);
@ -618,7 +644,7 @@ void engrave_tombstone(unique_fd output_fd, unwindstack::Unwinder* unwinder,
continue; continue;
} }
dump_thread(&log, unwinder, thread_info, 0, false); dump_thread(&log, unwinder, thread_info, 0, false, gwp_asan_crash_data);
} }
if (open_files) { if (open_files) {

View file

@ -95,10 +95,16 @@ struct __attribute__((__packed__)) CrashInfoDataV2 : public CrashInfoDataV1 {
uintptr_t fdsan_table_address; 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 { struct __attribute__((__packed__)) CrashInfo {
CrashInfoHeader header; CrashInfoHeader header;
union { union {
CrashInfoDataV1 v1; CrashInfoDataV1 v1;
CrashInfoDataV2 v2; CrashInfoDataV2 v2;
CrashInfoDataV3 v3;
} data; } data;
}; };