Merge "[GWP-ASan] Add GWP-ASan information to tombstones."
This commit is contained in:
commit
2dd81da3b4
11 changed files with 598 additions and 17 deletions
|
|
@ -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: [
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
273
debuggerd/libdebuggerd/gwp_asan.cpp
Normal file
273
debuggerd/libdebuggerd/gwp_asan.cpp
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libdebuggerd/gwp_asan.h"
|
||||||
|
#include "libdebuggerd/utility.h"
|
||||||
|
|
||||||
|
#include "gwp_asan/common.h"
|
||||||
|
#include "gwp_asan/crash_handler.h"
|
||||||
|
|
||||||
|
#include <unwindstack/Maps.h>
|
||||||
|
#include <unwindstack/Memory.h>
|
||||||
|
#include <unwindstack/Regs.h>
|
||||||
|
#include <unwindstack/Unwinder.h>
|
||||||
|
|
||||||
|
// Retrieve GWP-ASan state from `state_addr` inside the process at
|
||||||
|
// `process_memory`. Place the state into `*state`.
|
||||||
|
static bool retrieve_gwp_asan_state(unwindstack::Memory* process_memory, uintptr_t state_addr,
|
||||||
|
gwp_asan::AllocatorState* state) {
|
||||||
|
return process_memory->ReadFully(state_addr, state, sizeof(*state));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the GWP-ASan metadata pool from `metadata_addr` inside the process
|
||||||
|
// at `process_memory`. The number of metadata slots is retrieved from the
|
||||||
|
// allocator state provided. This function returns a heap-allocated copy of the
|
||||||
|
// metadata pool whose ownership should be managed by the caller. Returns
|
||||||
|
// nullptr on failure.
|
||||||
|
static const gwp_asan::AllocationMetadata* retrieve_gwp_asan_metadata(
|
||||||
|
unwindstack::Memory* process_memory, const gwp_asan::AllocatorState& state,
|
||||||
|
uintptr_t metadata_addr) {
|
||||||
|
if (state.MaxSimultaneousAllocations > 1024) {
|
||||||
|
ALOGE(
|
||||||
|
"Error when retrieving GWP-ASan metadata, MSA from state (%zu) "
|
||||||
|
"exceeds maximum allowed (1024).",
|
||||||
|
state.MaxSimultaneousAllocations);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
gwp_asan::AllocationMetadata* meta =
|
||||||
|
new gwp_asan::AllocationMetadata[state.MaxSimultaneousAllocations];
|
||||||
|
if (!process_memory->ReadFully(metadata_addr, meta,
|
||||||
|
sizeof(*meta) * state.MaxSimultaneousAllocations)) {
|
||||||
|
ALOGE(
|
||||||
|
"Error when retrieving GWP-ASan metadata, could not retrieve %zu "
|
||||||
|
"pieces of metadata.",
|
||||||
|
state.MaxSimultaneousAllocations);
|
||||||
|
delete[] meta;
|
||||||
|
meta = nullptr;
|
||||||
|
}
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
GwpAsanCrashData::GwpAsanCrashData(unwindstack::Memory* process_memory,
|
||||||
|
uintptr_t gwp_asan_state_ptr, uintptr_t gwp_asan_metadata_ptr,
|
||||||
|
const ThreadInfo& thread_info) {
|
||||||
|
if (!process_memory || !gwp_asan_metadata_ptr || !gwp_asan_state_ptr) return;
|
||||||
|
// Extract the GWP-ASan regions from the dead process.
|
||||||
|
if (!retrieve_gwp_asan_state(process_memory, gwp_asan_state_ptr, &state_)) return;
|
||||||
|
metadata_.reset(retrieve_gwp_asan_metadata(process_memory, state_, gwp_asan_metadata_ptr));
|
||||||
|
if (!metadata_.get()) return;
|
||||||
|
|
||||||
|
// Get the external crash address from the thread info.
|
||||||
|
crash_address_ = 0u;
|
||||||
|
if (signal_has_si_addr(thread_info.siginfo)) {
|
||||||
|
crash_address_ = reinterpret_cast<uintptr_t>(thread_info.siginfo->si_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the error belongs to GWP-ASan.
|
||||||
|
if (!__gwp_asan_error_is_mine(&state_, crash_address_)) return;
|
||||||
|
|
||||||
|
is_gwp_asan_responsible_ = true;
|
||||||
|
thread_id_ = thread_info.tid;
|
||||||
|
|
||||||
|
// Grab the internal error address, if it exists.
|
||||||
|
uintptr_t internal_crash_address = __gwp_asan_get_internal_crash_address(&state_);
|
||||||
|
if (internal_crash_address) {
|
||||||
|
crash_address_ = internal_crash_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get other information from the internal state.
|
||||||
|
error_ = __gwp_asan_diagnose_error(&state_, metadata_.get(), crash_address_);
|
||||||
|
error_string_ = gwp_asan::ErrorToString(error_);
|
||||||
|
responsible_allocation_ = __gwp_asan_get_metadata(&state_, metadata_.get(), crash_address_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GwpAsanCrashData::CrashIsMine() const {
|
||||||
|
return is_gwp_asan_responsible_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GwpAsanCrashData::DumpCause(log_t* log) const {
|
||||||
|
if (!CrashIsMine()) {
|
||||||
|
ALOGE("Internal Error: DumpCause() on a non-GWP-ASan crash.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error_ == gwp_asan::Error::UNKNOWN) {
|
||||||
|
_LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: Unknown error occurred at 0x%" PRIxPTR ".\n",
|
||||||
|
crash_address_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!responsible_allocation_) {
|
||||||
|
_LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: %s at 0x%" PRIxPTR ".\n", error_string_,
|
||||||
|
crash_address_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t alloc_address = __gwp_asan_get_allocation_address(responsible_allocation_);
|
||||||
|
size_t alloc_size = __gwp_asan_get_allocation_size(responsible_allocation_);
|
||||||
|
|
||||||
|
if (crash_address_ == alloc_address) {
|
||||||
|
// Use After Free on a 41-byte allocation at 0xdeadbeef.
|
||||||
|
_LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: %s on a %zu-byte allocation at 0x%" PRIxPTR "\n",
|
||||||
|
error_string_, alloc_size, alloc_address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t diff;
|
||||||
|
const char* location_str;
|
||||||
|
|
||||||
|
if (crash_address_ < alloc_address) {
|
||||||
|
// Buffer Underflow, 6 bytes left of a 41-byte allocation at 0xdeadbeef.
|
||||||
|
location_str = "left of";
|
||||||
|
diff = alloc_address - crash_address_;
|
||||||
|
} else if (crash_address_ - alloc_address < alloc_size) {
|
||||||
|
// Use After Free, 40 bytes into a 41-byte allocation at 0xdeadbeef.
|
||||||
|
location_str = "into";
|
||||||
|
diff = crash_address_ - alloc_address;
|
||||||
|
} else {
|
||||||
|
// Buffer Overflow, 6 bytes right of a 41-byte allocation at 0xdeadbeef, or
|
||||||
|
// Invalid Free, 47 bytes right of a 41-byte allocation at 0xdeadbeef.
|
||||||
|
location_str = "right of";
|
||||||
|
diff = crash_address_ - alloc_address;
|
||||||
|
if (error_ == gwp_asan::Error::BUFFER_OVERFLOW) {
|
||||||
|
diff -= alloc_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suffix of 'bytes', i.e. 4 bytes' vs. '1 byte'.
|
||||||
|
const char* byte_suffix = "s";
|
||||||
|
if (diff == 1) {
|
||||||
|
byte_suffix = "";
|
||||||
|
}
|
||||||
|
_LOG(log, logtype::HEADER,
|
||||||
|
"Cause: [GWP-ASan]: %s, %" PRIuPTR " byte%s %s a %zu-byte allocation at 0x%" PRIxPTR "\n",
|
||||||
|
error_string_, diff, byte_suffix, location_str, alloc_size, alloc_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a frame for symbolization using the maps from the provided unwinder.
|
||||||
|
// The constructed frame contains just enough information to be used to
|
||||||
|
// symbolize a GWP-ASan stack trace.
|
||||||
|
static unwindstack::FrameData BuildFrame(unwindstack::Unwinder* unwinder, uintptr_t pc,
|
||||||
|
size_t frame_num) {
|
||||||
|
unwindstack::FrameData frame;
|
||||||
|
frame.num = frame_num;
|
||||||
|
|
||||||
|
unwindstack::Maps* maps = unwinder->GetMaps();
|
||||||
|
unwindstack::MapInfo* map_info = maps->Find(pc);
|
||||||
|
if (!map_info) {
|
||||||
|
frame.rel_pc = pc;
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
unwindstack::Elf* elf =
|
||||||
|
map_info->GetElf(unwinder->GetProcessMemory(), unwindstack::Regs::CurrentArch());
|
||||||
|
|
||||||
|
uint64_t relative_pc = elf->GetRelPc(pc, map_info);
|
||||||
|
|
||||||
|
// Create registers just to get PC adjustment. Doesn't matter what they point
|
||||||
|
// to.
|
||||||
|
unwindstack::Regs* regs = unwindstack::Regs::CreateFromLocal();
|
||||||
|
uint64_t pc_adjustment = regs->GetPcAdjustment(relative_pc, elf);
|
||||||
|
relative_pc -= pc_adjustment;
|
||||||
|
// The debug PC may be different if the PC comes from the JIT.
|
||||||
|
uint64_t debug_pc = relative_pc;
|
||||||
|
|
||||||
|
// If we don't have a valid ELF file, check the JIT.
|
||||||
|
if (!elf->valid()) {
|
||||||
|
unwindstack::JitDebug jit_debug(unwinder->GetProcessMemory());
|
||||||
|
uint64_t jit_pc = pc - pc_adjustment;
|
||||||
|
unwindstack::Elf* jit_elf = jit_debug.GetElf(maps, jit_pc);
|
||||||
|
if (jit_elf != nullptr) {
|
||||||
|
debug_pc = jit_pc;
|
||||||
|
elf = jit_elf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy all the things we need into the frame for symbolization.
|
||||||
|
frame.rel_pc = relative_pc;
|
||||||
|
frame.pc = pc - pc_adjustment;
|
||||||
|
frame.map_name = map_info->name;
|
||||||
|
frame.map_elf_start_offset = map_info->elf_start_offset;
|
||||||
|
frame.map_exact_offset = map_info->offset;
|
||||||
|
frame.map_start = map_info->start;
|
||||||
|
frame.map_end = map_info->end;
|
||||||
|
frame.map_flags = map_info->flags;
|
||||||
|
frame.map_load_bias = elf->GetLoadBias();
|
||||||
|
|
||||||
|
if (!elf->GetFunctionName(relative_pc, &frame.function_name, &frame.function_offset)) {
|
||||||
|
frame.function_name = "";
|
||||||
|
frame.function_offset = 0;
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
|
||||||
|
|
||||||
|
bool GwpAsanCrashData::HasDeallocationTrace() const {
|
||||||
|
assert(CrashIsMine() && "HasDeallocationTrace(): Crash is not mine!");
|
||||||
|
if (!responsible_allocation_ || !__gwp_asan_is_deallocated(responsible_allocation_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GwpAsanCrashData::DumpDeallocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const {
|
||||||
|
assert(HasDeallocationTrace() && "DumpDeallocationTrace(): No dealloc trace!");
|
||||||
|
uint64_t thread_id = __gwp_asan_get_deallocation_thread_id(responsible_allocation_);
|
||||||
|
|
||||||
|
std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
|
||||||
|
size_t num_frames =
|
||||||
|
__gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
|
||||||
|
|
||||||
|
if (thread_id == gwp_asan::kInvalidThreadID) {
|
||||||
|
_LOG(log, logtype::BACKTRACE, "\ndeallocated by thread <unknown>:\n");
|
||||||
|
} else {
|
||||||
|
_LOG(log, logtype::BACKTRACE, "\ndeallocated by thread %" PRIu64 ":\n", thread_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
unwinder->SetDisplayBuildID(true);
|
||||||
|
for (size_t i = 0; i < num_frames; ++i) {
|
||||||
|
unwindstack::FrameData frame_data = BuildFrame(unwinder, frames.get()[i], i);
|
||||||
|
_LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GwpAsanCrashData::HasAllocationTrace() const {
|
||||||
|
assert(CrashIsMine() && "HasAllocationTrace(): Crash is not mine!");
|
||||||
|
return responsible_allocation_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GwpAsanCrashData::DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const {
|
||||||
|
assert(HasAllocationTrace() && "DumpAllocationTrace(): No dealloc trace!");
|
||||||
|
uint64_t thread_id = __gwp_asan_get_allocation_thread_id(responsible_allocation_);
|
||||||
|
|
||||||
|
std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
|
||||||
|
size_t num_frames =
|
||||||
|
__gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
|
||||||
|
|
||||||
|
if (thread_id == gwp_asan::kInvalidThreadID) {
|
||||||
|
_LOG(log, logtype::BACKTRACE, "\nallocated by thread <unknown>:\n");
|
||||||
|
} else {
|
||||||
|
_LOG(log, logtype::BACKTRACE, "\nallocated by thread %" PRIu64 ":\n", thread_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
unwinder->SetDisplayBuildID(true);
|
||||||
|
for (size_t i = 0; i < num_frames; ++i) {
|
||||||
|
unwindstack::FrameData frame_data = BuildFrame(unwinder, frames.get()[i], i);
|
||||||
|
_LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
95
debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
Normal file
95
debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <log/log.h>
|
||||||
|
#include <unwindstack/Memory.h>
|
||||||
|
|
||||||
|
#include "gwp_asan/common.h"
|
||||||
|
#include "types.h"
|
||||||
|
#include "utility.h"
|
||||||
|
|
||||||
|
class GwpAsanCrashData {
|
||||||
|
public:
|
||||||
|
GwpAsanCrashData() = delete;
|
||||||
|
~GwpAsanCrashData() = default;
|
||||||
|
|
||||||
|
// Construct the crash data object. Takes a handle to the object that can
|
||||||
|
// supply the memory of the dead process, and pointers to the GWP-ASan state
|
||||||
|
// and metadata regions within that process. Also takes the thread information
|
||||||
|
// of the crashed process. If the process didn't crash via SEGV, GWP-ASan may
|
||||||
|
// still be responsible, as it terminates when it detects an internal error
|
||||||
|
// (double free, invalid free). In these cases, we will retrieve the fault
|
||||||
|
// address from the GWP-ASan allocator's state.
|
||||||
|
GwpAsanCrashData(unwindstack::Memory* process_memory, uintptr_t gwp_asan_state_ptr,
|
||||||
|
uintptr_t gwp_asan_metadata_ptr, const ThreadInfo& thread_info);
|
||||||
|
|
||||||
|
// Is GWP-ASan responsible for this crash.
|
||||||
|
bool CrashIsMine() const;
|
||||||
|
|
||||||
|
// Returns the fault address. The fault address may be the same as provided
|
||||||
|
// during construction, or it may have been retrieved from GWP-ASan's internal
|
||||||
|
// allocator crash state.
|
||||||
|
uintptr_t GetFaultAddress() const;
|
||||||
|
|
||||||
|
// Dump the GWP-ASan stringified cause of this crash. May only be called if
|
||||||
|
// CrashIsMine() returns true.
|
||||||
|
void DumpCause(log_t* log) const;
|
||||||
|
|
||||||
|
// Returns whether this crash has a deallocation trace. May only be called if
|
||||||
|
// CrashIsMine() returns true.
|
||||||
|
bool HasDeallocationTrace() const;
|
||||||
|
|
||||||
|
// Dump the GWP-ASan deallocation trace for this crash. May only be called if
|
||||||
|
// HasDeallocationTrace() returns true.
|
||||||
|
void DumpDeallocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const;
|
||||||
|
|
||||||
|
// Returns whether this crash has a allocation trace. May only be called if
|
||||||
|
// CrashIsMine() returns true.
|
||||||
|
bool HasAllocationTrace() const;
|
||||||
|
|
||||||
|
// Dump the GWP-ASan allocation trace for this crash. May only be called if
|
||||||
|
// HasAllocationTrace() returns true.
|
||||||
|
void DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Is GWP-ASan responsible for this crash.
|
||||||
|
bool is_gwp_asan_responsible_ = false;
|
||||||
|
|
||||||
|
// Thread ID of the crash.
|
||||||
|
size_t thread_id_;
|
||||||
|
|
||||||
|
// The type of error that GWP-ASan caused (and the stringified version),
|
||||||
|
// Undefined if GWP-ASan isn't responsible for the crash.
|
||||||
|
gwp_asan::Error error_;
|
||||||
|
const char* error_string_;
|
||||||
|
|
||||||
|
// Pointer to the crash address. Holds the internal crash address if it
|
||||||
|
// exists, otherwise the address provided at construction.
|
||||||
|
uintptr_t crash_address_ = 0u;
|
||||||
|
|
||||||
|
// Pointer to the metadata for the responsible allocation, nullptr if it
|
||||||
|
// doesn't exist.
|
||||||
|
const gwp_asan::AllocationMetadata* responsible_allocation_ = nullptr;
|
||||||
|
|
||||||
|
// Internal state.
|
||||||
|
gwp_asan::AllocatorState state_;
|
||||||
|
std::unique_ptr<const gwp_asan::AllocationMetadata> metadata_;
|
||||||
|
};
|
||||||
|
|
@ -55,6 +55,7 @@ void engrave_tombstone_ucontext(int tombstone_fd, uint64_t abort_msg_address, si
|
||||||
void engrave_tombstone(android::base::unique_fd output_fd, unwindstack::Unwinder* unwinder,
|
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
|
||||||
|
|
|
||||||
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue