Commit 409ceb85 authored by Vlad Tsyrklevich's avatar Vlad Tsyrklevich

GWP-ASan: Add crash handler hook.

After a GWP-ASan exception occurs, a crash handler hook (e.g. a crashpad
UserStreamDataSource) is responsible for looking at the crash and
1) determining if the exception was related to GWP-ASan, and 2) adding
additional debug information to the minidump if so.

The crash handler hook determines if the exception was related to
GWP-ASan by finding the AllocatorState address (using a crash key),
reading the allocator state and seeing if the exception occurred in the
bounds of the allocator region. If it did, we extract debug information
about that allocation and report it in the minidump.

CQ-DEPEND=CL:1339246

Bug: 896019
Change-Id: I63d12b5137098b20ec946e3bddbdcabaf20e430a
Reviewed-on: https://chromium-review.googlesource.com/c/1330283Reviewed-by: default avatarMark Mentovai <mark@chromium.org>
Reviewed-by: default avatarBrian White <bcwhite@chromium.org>
Reviewed-by: default avatarVitaly Buka <vitalybuka@chromium.org>
Cr-Commit-Position: refs/heads/master@{#611033}
parent c0b760ab
......@@ -7,6 +7,8 @@ source_set("unit_tests") {
if (is_win) {
deps = [
"//components/gwp_asan/client:unit_tests",
"//components/gwp_asan/common:unit_tests",
"//components/gwp_asan/crash_handler:unit_tests",
]
}
}
......@@ -119,23 +119,6 @@ TEST_F(GuardedPageAllocatorTest, AllocationAlignment) {
EXPECT_DEATH(GetRightAlignedAllocationOffset(5, 3), "");
}
TEST_F(GuardedPageAllocatorTest, GetNearestValidPageEdgeCases) {
EXPECT_EQ(gpa_.state_.GetPageAddr(
gpa_.state_.GetNearestValidPage(gpa_.state_.pages_base_addr)),
gpa_.state_.first_page_addr);
EXPECT_EQ(gpa_.state_.GetPageAddr(gpa_.state_.GetNearestValidPage(
gpa_.state_.pages_end_addr - 1)),
gpa_.state_.pages_end_addr - (2 * gpa_.state_.page_size));
}
TEST_F(GuardedPageAllocatorTest, GetErrorTypeEdgeCases) {
EXPECT_EQ(gpa_.state_.GetErrorType(gpa_.state_.pages_base_addr, true, false),
AllocatorState::ErrorType::kBufferUnderflow);
EXPECT_EQ(
gpa_.state_.GetErrorType(gpa_.state_.pages_end_addr - 1, true, false),
AllocatorState::ErrorType::kBufferOverflow);
}
class GuardedPageAllocatorParamTest
: public GuardedPageAllocatorTest,
public testing::WithParamInterface<size_t> {
......
......@@ -13,3 +13,15 @@ static_library("common") {
"//base",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"allocator_state_unittest.cc",
]
deps = [
":common",
"//base/test:test_support",
"//testing/gtest",
]
}
......@@ -5,6 +5,7 @@
#include "components/gwp_asan/common/allocator_state.h"
#include "base/bits.h"
#include "base/process/process_metrics.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
......@@ -16,6 +17,47 @@ namespace internal {
// TODO: Delete out-of-line constexpr defininitons once C++17 is in use.
constexpr size_t AllocatorState::kGpaMaxPages;
AllocatorState::GetMetadataReturnType AllocatorState::GetMetadataForAddress(
uintptr_t exception_address,
SlotMetadata* slot,
ErrorType* error_type) const {
CHECK(IsValid());
if (!PointerIsMine(exception_address))
return GetMetadataReturnType::kUnrelatedCrash;
size_t slot_idx = GetNearestSlot(exception_address);
if (slot_idx >= kGpaMaxPages)
return GetMetadataReturnType::kErrorBadSlot;
*slot = data[slot_idx];
*error_type = GetErrorType(exception_address, slot->alloc.trace_addr != 0,
slot->dealloc.trace_addr != 0);
return GetMetadataReturnType::kGwpAsanCrash;
}
bool AllocatorState::IsValid() const {
if (!page_size || page_size != base::GetPageSize())
return false;
if (num_pages == 0 || num_pages > kGpaMaxPages)
return false;
if (pages_base_addr % page_size != 0 || pages_end_addr % page_size != 0 ||
first_page_addr % page_size != 0)
return false;
if (pages_base_addr >= pages_end_addr)
return false;
if (first_page_addr != pages_base_addr + page_size ||
pages_end_addr - pages_base_addr != page_size * (num_pages * 2 + 1))
return false;
return true;
}
uintptr_t AllocatorState::GetPageAddr(uintptr_t addr) const {
const uintptr_t addr_mask = ~(page_size - 1ULL);
return addr & addr_mask;
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The AllocateState class is the subset of the GuardedPageAllocator that is
// The AllocatorState class is the subset of the GuardedPageAllocator that is
// required by the crash handler to analyzer crashes and provide debug
// information. The crash handler initializes an instance of this class from
// the crashed processes memory. Because the out-of-process allocator could be
......@@ -17,6 +17,10 @@
// - Free of pointers: Pointers are all uintptr_t since none of these pointers
// need to be directly dereferenced. Encourage users like the crash handler
// to consider them addresses instead of pointers.
// - Validatable: The IsValid() method is intended to sanity check the internal
// fields such that it's safe to call any method on a valid object. All
// additional methods and fields need to be audited to ensure they maintain
// this invariant!
#ifndef COMPONENTS_GWP_ASAN_COMMON_ALLOCATOR_STATE_H_
#define COMPONENTS_GWP_ASAN_COMMON_ALLOCATOR_STATE_H_
......@@ -44,6 +48,12 @@ class AllocatorState {
kUnknown = 4,
};
enum class GetMetadataReturnType {
kUnrelatedCrash = 0,
kGwpAsanCrash = 1,
kErrorBadSlot = 2,
};
// Structure for storing data about a slot.
struct SlotMetadata {
// Information saved for allocations and deallocations.
......@@ -75,6 +85,25 @@ class AllocatorState {
return pages_base_addr <= addr && addr < pages_end_addr;
}
// Sanity check allocator internals. This method is used to verify that
// the allocator base state is well formed when the crash handler analyzes the
// allocator from a crashing process. This method is security-sensitive, it
// must validate parameters to ensure that an attacker with the ability to
// modify the allocator internals can not cause the crash handler to misbehave
// and cause memory errors.
bool IsValid() const;
// This method is meant to be called from the crash handler with a validated
// AllocatorState object read from the crashed process. This method checks if
// exception_address is an address in the GWP-ASan region, and writes the
// error type and slot metadata to the provided arguments if so.
//
// Returns an enum indicating an error, unrelated exception, or a GWP-ASan
// exception (with slot and error_type filled out.)
GetMetadataReturnType GetMetadataForAddress(uintptr_t exception_address,
SlotMetadata* slot,
ErrorType* error_type) const;
// Returns the likely error type given an exception address and whether its
// previously been allocated and deallocated.
ErrorType GetErrorType(uintptr_t addr,
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/gwp_asan/common/allocator_state.h"
#include <limits>
#include "base/process/process_metrics.h"
#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gwp_asan {
namespace internal {
static constexpr size_t kGpaMaxPages = AllocatorState::kGpaMaxPages;
class AllocatorStateTest : public testing::Test {
protected:
void InitializeState(size_t page_size,
size_t num_pages,
int base_addr_offset = 0,
int first_page_offset = 0,
int end_addr_offset = 0) {
state_.page_size = page_size;
state_.num_pages = num_pages;
// Some arbitrary page-aligned address
const uintptr_t base = page_size * 10;
state_.pages_base_addr = base_addr_offset + base;
state_.first_page_addr = first_page_offset + base + page_size;
state_.pages_end_addr =
end_addr_offset + base + page_size * (num_pages * 2 + 1);
}
AllocatorState state_;
};
TEST_F(AllocatorStateTest, Valid) {
InitializeState(base::GetPageSize(), 1);
EXPECT_TRUE(state_.IsValid());
InitializeState(base::GetPageSize(), kGpaMaxPages);
EXPECT_TRUE(state_.IsValid());
}
TEST_F(AllocatorStateTest, InvalidNumPages) {
InitializeState(base::GetPageSize(), 0);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize(), kGpaMaxPages + 1);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize(), std::numeric_limits<size_t>::max());
EXPECT_FALSE(state_.IsValid());
}
TEST_F(AllocatorStateTest, InvalidPageSize) {
InitializeState(0, 1);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize() + 1, 1);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize() * 2, 1);
EXPECT_FALSE(state_.IsValid());
}
TEST_F(AllocatorStateTest, InvalidAddresses) {
// Invalid [pages_base_addr, first_page_addr, pages_end_addr]
InitializeState(base::GetPageSize(), 1, 1, 1, 1);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize(), 1, 1, 0, 0);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize(), 1, 0, 1, 0);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize(), 1, 0, 0, 1);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize(), 1, base::GetPageSize(), 0, 0);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize(), 1, 0, base::GetPageSize(), 0);
EXPECT_FALSE(state_.IsValid());
InitializeState(base::GetPageSize(), 1, 0, 0, base::GetPageSize());
EXPECT_FALSE(state_.IsValid());
}
TEST_F(AllocatorStateTest, GetNearestValidPageEdgeCases) {
InitializeState(base::GetPageSize(), kGpaMaxPages);
EXPECT_TRUE(state_.IsValid());
EXPECT_EQ(
state_.GetPageAddr(state_.GetNearestValidPage(state_.pages_base_addr)),
state_.first_page_addr);
EXPECT_EQ(
state_.GetPageAddr(state_.GetNearestValidPage(state_.pages_end_addr - 1)),
state_.pages_end_addr - (2 * state_.page_size));
}
TEST_F(AllocatorStateTest, GetErrorTypeEdgeCases) {
InitializeState(base::GetPageSize(), kGpaMaxPages);
EXPECT_TRUE(state_.IsValid());
EXPECT_EQ(state_.GetErrorType(state_.pages_base_addr, true, false),
AllocatorState::ErrorType::kBufferUnderflow);
EXPECT_EQ(state_.GetErrorType(state_.pages_end_addr - 1, true, false),
AllocatorState::ErrorType::kBufferOverflow);
}
} // namespace internal
} // namespace gwp_asan
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//third_party/protobuf/proto_library.gni")
assert(is_win, "GWP-ASan crash handler currently only supports Windows.")
static_library("crash_handler") {
sources = [
"crash_analyzer.cc",
"crash_analyzer.h",
"crash_analyzer_win.cc",
"crash_handler.cc",
"crash_handler.h",
]
deps = [
":crash_proto",
"//base",
"//components/gwp_asan/common",
"//third_party/crashpad/crashpad/client",
"//third_party/crashpad/crashpad/handler",
"//third_party/crashpad/crashpad/minidump",
"//third_party/crashpad/crashpad/snapshot",
"//third_party/crashpad/crashpad/util",
"//third_party/protobuf:protobuf_lite",
]
}
proto_library("crash_proto") {
sources = [
"crash.proto",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"crash_handler_unittest.cc",
]
deps = [
":crash_handler",
":crash_proto",
"//base/test:test_support",
"//components/gwp_asan/client",
"//components/gwp_asan/common",
"//testing/gtest",
"//third_party/crashpad/crashpad/client",
"//third_party/crashpad/crashpad/handler",
"//third_party/crashpad/crashpad/minidump",
"//third_party/crashpad/crashpad/snapshot",
"//third_party/crashpad/crashpad/tools:tool_support",
"//third_party/crashpad/crashpad/util",
"//third_party/protobuf:protobuf_lite",
]
}
include_rules = [
"+third_party/crashpad",
]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Serialization format for GWP ASan crashdata, written to the minidump using
// stream ID 0x4B6B0004.
syntax = "proto2";
package gwp_asan;
option optimize_for = LITE_RUNTIME;
message Crash {
enum ErrorType {
// These should not be renumbered and should be kept in sync with
// AllocatorState::ErrorType in allocator_state.h
USE_AFTER_FREE = 0;
BUFFER_UNDERFLOW = 1;
BUFFER_OVERFLOW = 2;
DOUBLE_FREE = 3;
UNKNOWN = 4;
}
message AllocationInfo {
repeated uint64 stack_trace = 1 [packed = true];
optional uint64 thread_id = 2;
}
optional ErrorType error_type = 1;
optional uint64 allocation_address = 2;
optional uint64 allocation_size = 3;
optional AllocationInfo allocation = 4;
optional AllocationInfo deallocation = 5;
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/gwp_asan/crash_handler/crash_analyzer.h"
#include <algorithm>
#include <stddef.h>
#include "base/logging.h"
#include "base/process/process_metrics.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "components/gwp_asan/common/allocator_state.h"
#include "components/gwp_asan/common/crash_key_name.h"
#include "components/gwp_asan/crash_handler/crash.pb.h"
#include "third_party/crashpad/crashpad/client/annotation.h"
#include "third_party/crashpad/crashpad/snapshot/cpu_context.h"
#include "third_party/crashpad/crashpad/snapshot/exception_snapshot.h"
#include "third_party/crashpad/crashpad/snapshot/module_snapshot.h"
#include "third_party/crashpad/crashpad/snapshot/process_snapshot.h"
#include "third_party/crashpad/crashpad/util/process/process_memory.h"
namespace gwp_asan {
namespace internal {
using GwpAsanCrashAnalysisResult = CrashAnalyzer::GwpAsanCrashAnalysisResult;
// TODO: Delete out-of-line constexpr defininitons once C++17 is in use.
constexpr size_t CrashAnalyzer::kMaxStackTraceLen;
GwpAsanCrashAnalysisResult CrashAnalyzer::GetExceptionInfo(
const crashpad::ProcessSnapshot& process_snapshot,
gwp_asan::Crash* proto) {
crashpad::VMAddress gpa_ptr = GetAllocatorAddress(process_snapshot);
// If the annotation wasn't present, GWP-ASan wasn't enabled.
if (!gpa_ptr)
return GwpAsanCrashAnalysisResult::kUnrelatedCrash;
const crashpad::ExceptionSnapshot* exception = process_snapshot.Exception();
if (!exception)
return GwpAsanCrashAnalysisResult::kUnrelatedCrash;
if (!exception->Context())
return GwpAsanCrashAnalysisResult::kErrorNullCpuContext;
#if defined(ARCH_CPU_64_BITS)
constexpr bool is_64_bit = true;
#else
constexpr bool is_64_bit = false;
#endif
// TODO(vtsyrklevich): Look at using crashpad's process_types to read the GPA
// state bitness-independently.
if (exception->Context()->Is64Bit() != is_64_bit)
return GwpAsanCrashAnalysisResult::kErrorMismatchedBitness;
crashpad::VMAddress crash_addr = GetAccessAddress(*exception);
if (!crash_addr)
return GwpAsanCrashAnalysisResult::kUnrelatedCrash;
if (!process_snapshot.Memory())
return GwpAsanCrashAnalysisResult::kErrorNullProcessMemory;
return AnalyzeCrashedAllocator(*process_snapshot.Memory(), gpa_ptr,
crash_addr, proto);
}
crashpad::VMAddress CrashAnalyzer::GetAllocatorAddress(
const crashpad::ProcessSnapshot& process_snapshot) {
for (auto* module : process_snapshot.Modules()) {
for (auto annotation : module->AnnotationObjects()) {
if (annotation.name != kGpaCrashKey)
continue;
if (annotation.type !=
static_cast<uint16_t>(crashpad::Annotation::Type::kString)) {
DLOG(ERROR) << "Bad annotation type " << annotation.type;
return 0;
}
std::string annotation_str(reinterpret_cast<char*>(&annotation.value[0]),
annotation.value.size());
uint64_t value;
if (!base::HexStringToUInt64(annotation_str, &value))
return 0;
return value;
}
}
return 0;
}
GwpAsanCrashAnalysisResult CrashAnalyzer::AnalyzeCrashedAllocator(
const crashpad::ProcessMemory& memory,
crashpad::VMAddress gpa_addr,
crashpad::VMAddress exception_addr,
gwp_asan::Crash* proto) {
AllocatorState unsafe_state;
if (!memory.Read(gpa_addr, sizeof(unsafe_state), &unsafe_state)) {
DLOG(ERROR) << "Failed to read AllocatorState from process.";
return GwpAsanCrashAnalysisResult::kErrorFailedToReadAllocator;
}
if (!unsafe_state.IsValid()) {
DLOG(ERROR) << "Allocator sanity check failed!";
return GwpAsanCrashAnalysisResult::kErrorAllocatorFailedSanityCheck;
}
const AllocatorState& valid_state = unsafe_state;
SlotMetadata slot;
AllocatorState::ErrorType error_type;
auto ret =
valid_state.GetMetadataForAddress(exception_addr, &slot, &error_type);
if (ret == AllocatorState::GetMetadataReturnType::kErrorBadSlot) {
DLOG(ERROR) << "Allocator computed a bad slot index!";
return GwpAsanCrashAnalysisResult::kErrorBadSlot;
}
if (ret == AllocatorState::GetMetadataReturnType::kUnrelatedCrash)
return GwpAsanCrashAnalysisResult::kUnrelatedCrash;
proto->set_error_type(static_cast<Crash_ErrorType>(error_type));
proto->set_allocation_address(slot.alloc_ptr);
proto->set_allocation_size(slot.alloc_size);
if (slot.alloc.tid != base::kInvalidThreadId || slot.alloc.trace_len)
ReadAllocationInfo(memory, slot.alloc, proto->mutable_allocation());
if (slot.dealloc.tid != base::kInvalidThreadId || slot.dealloc.trace_len)
ReadAllocationInfo(memory, slot.dealloc, proto->mutable_deallocation());
return GwpAsanCrashAnalysisResult::kGwpAsanCrash;
}
bool CrashAnalyzer::ReadAllocationInfo(
const crashpad::ProcessMemory& memory,
const SlotMetadata::AllocationInfo& slot_info,
gwp_asan::Crash_AllocationInfo* proto_info) {
if (slot_info.tid != base::kInvalidThreadId)
proto_info->set_thread_id(slot_info.tid);
if (!slot_info.trace_len)
return true;
size_t trace_len = std::min(slot_info.trace_len, kMaxStackTraceLen);
// On 32-bit platforms we can't read directly to
// proto_info->mutable_stack_trace()->mutable_data(), so we read to an
// intermediate uintptr_t array.
uintptr_t trace[kMaxStackTraceLen];
if (!ReadStackTrace(memory,
static_cast<crashpad::VMAddress>(slot_info.trace_addr),
trace, trace_len))
return false;
proto_info->mutable_stack_trace()->Resize(trace_len, 0);
uint64_t* output = proto_info->mutable_stack_trace()->mutable_data();
for (size_t i = 0; i < trace_len; i++)
output[i] = trace[i];
return true;
}
bool CrashAnalyzer::ReadStackTrace(const crashpad::ProcessMemory& memory,
crashpad::VMAddress crashing_trace_addr,
uintptr_t* trace_output,
size_t trace_len) {
if (!memory.Read(crashing_trace_addr, sizeof(uintptr_t) * trace_len,
trace_output)) {
DLOG(ERROR) << "Memory read should always succeed.";
return false;
}
return true;
}
} // namespace internal
} // namespace gwp_asan
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_GWP_ASAN_CRASH_HANDLER_CRASH_ANALYZER_H_
#define COMPONENTS_GWP_ASAN_CRASH_HANDLER_CRASH_ANALYZER_H_
#include <stddef.h>
#include "components/gwp_asan/common/allocator_state.h"
#include "components/gwp_asan/crash_handler/crash.pb.h"
#include "third_party/crashpad/crashpad/util/misc/address_types.h"
namespace crashpad {
class ExceptionSnapshot;
class ProcessMemory;
class ProcessSnapshot;
} // namespace crashpad
namespace gwp_asan {
namespace internal {
class CrashAnalyzer {
public:
// Captures the result of the GWP-ASan crash analyzer, whether the crash is
// determined to be related or unrelated to GWP-ASan or if an error was
// encountered analyzing the exception.
//
// These values are persisted via UMA--entries should not be renumbered and
// numeric values should never be reused.
enum class GwpAsanCrashAnalysisResult {
// The crash is not caused by GWP-ASan.
kUnrelatedCrash = 0,
// The crash is caused by GWP-ASan.
kGwpAsanCrash = 1,
// The ProcessMemory from the snapshot was null.
kErrorNullProcessMemory = 2,
// Failed to read the crashing process' memory of the global allocator.
kErrorFailedToReadAllocator = 3,
// The crashing process' global allocator members failed sanity checks.
kErrorAllocatorFailedSanityCheck = 4,
// Failed to read crash stack traces.
kErrorFailedToReadStackTrace = 5,
// The ExceptionSnapshot CPU context was null.
kErrorNullCpuContext = 6,
// The crashing process' bitness does not match the crash handler.
kErrorMismatchedBitness = 7,
// The allocator computed an invalid slot index.
kErrorBadSlot = 8,
// Number of values in this enumeration, required by UMA.
kMaxValue = kErrorBadSlot
};
// Given a ProcessSnapshot, determine if the exception is related to GWP-ASan.
// If it is, return kGwpAsanCrash and fill out the info parameter with
// details about the exception. Otherwise, return a value indicating that the
// crash is unrelated or that an error occured.
static GwpAsanCrashAnalysisResult GetExceptionInfo(
const crashpad::ProcessSnapshot& process_snapshot,
gwp_asan::Crash* proto);
private:
using SlotMetadata = AllocatorState::SlotMetadata;
// The maximum stack trace size the analyzer will extract from a crashing
// process.
static constexpr size_t kMaxStackTraceLen = 64;
// Given an ExceptionSnapshot, return the address of where the exception
// occurred (or null if it was not a data access exception.)
static crashpad::VMAddress GetAccessAddress(
const crashpad::ExceptionSnapshot& exception);
// If the allocator annotation is present in the given snapshot, then return
// the address for the GuardedPageAllocator in the crashing process.
static crashpad::VMAddress GetAllocatorAddress(
const crashpad::ProcessSnapshot& process_snapshot);
// This method implements the underlying logic for GetExceptionInfo(). It
// analyzes the GuardedPageAllocator of the crashing process, and if the
// exception address is in the GWP-ASan region it fills out the protobuf
// parameter and returns kGwpAsanCrash.
static GwpAsanCrashAnalysisResult AnalyzeCrashedAllocator(
const crashpad::ProcessMemory& memory,
crashpad::VMAddress gpa_addr,
crashpad::VMAddress exception_addr,
gwp_asan::Crash* proto);
// This method fills out an AllocationInfo protobuf from a
// SlotMetadata::AllocationInfo struct.
static bool ReadAllocationInfo(const crashpad::ProcessMemory& memory,
const SlotMetadata::AllocationInfo& slot_info,
gwp_asan::Crash_AllocationInfo* proto_info);
// Read a stack trace from a crashing process with address crashing_trace_addr
// and length trace_len into trace_output. On failure returns false.
static bool ReadStackTrace(const crashpad::ProcessMemory& memory,
crashpad::VMAddress crashing_trace_addr,
uintptr_t* trace_output,
size_t trace_len);
};
} // namespace internal
} // namespace gwp_asan
#endif // COMPONENTS_GWP_ASAN_CRASH_HANDLER_CRASH_ANALYZER_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/gwp_asan/crash_handler/crash_analyzer.h"
#include <windows.h>
#include "base/logging.h"
#include "third_party/crashpad/crashpad/snapshot/exception_snapshot.h"
namespace gwp_asan {
namespace internal {
crashpad::VMAddress CrashAnalyzer::GetAccessAddress(
const crashpad::ExceptionSnapshot& exception) {
if (exception.Exception() != EXCEPTION_ACCESS_VIOLATION)
return 0;
const std::vector<uint64_t>& codes = exception.Codes();
if (codes.size() < 2) {
DLOG(FATAL) << "Exception array is too small! " << codes.size();
return 0;
}
return codes[1];
}
} // namespace internal
} // namespace gwp_asan
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/gwp_asan/crash_handler/crash_handler.h"
#include <stddef.h>
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "components/gwp_asan/crash_handler/crash.pb.h"
#include "components/gwp_asan/crash_handler/crash_analyzer.h"
#include "third_party/crashpad/crashpad/minidump/minidump_user_extension_stream_data_source.h"
#include "third_party/crashpad/crashpad/snapshot/process_snapshot.h"
namespace gwp_asan {
namespace internal {
namespace {
using GwpAsanCrashAnalysisResult = CrashAnalyzer::GwpAsanCrashAnalysisResult;
// Return a serialized protobuf using a wrapper interface that
// crashpad::UserStreamDataSource expects us to return.
class BufferExtensionStreamDataSource final
: public crashpad::MinidumpUserExtensionStreamDataSource {
public:
BufferExtensionStreamDataSource(uint32_t stream_type, Crash& crash);
size_t StreamDataSize() override;
bool ReadStreamData(Delegate* delegate) override;
private:
std::string data_;
DISALLOW_COPY_AND_ASSIGN(BufferExtensionStreamDataSource);
};
BufferExtensionStreamDataSource::BufferExtensionStreamDataSource(
uint32_t stream_type,
Crash& crash)
: crashpad::MinidumpUserExtensionStreamDataSource(stream_type) {
DCHECK(crash.SerializeToString(&data_));
}
size_t BufferExtensionStreamDataSource::StreamDataSize() {
DCHECK(!data_.empty());
return data_.size();
}
bool BufferExtensionStreamDataSource::ReadStreamData(Delegate* delegate) {
DCHECK(!data_.empty());
return delegate->ExtensionStreamDataSourceRead(data_.data(), data_.size());
}
const char* ErrorToString(Crash_ErrorType type) {
switch (type) {
case Crash::USE_AFTER_FREE:
return "heap-use-after-free";
case Crash::BUFFER_UNDERFLOW:
return "heap-buffer-underflow";
case Crash::BUFFER_OVERFLOW:
return "heap-buffer-overflow";
case Crash::DOUBLE_FREE:
return "double-free";
case Crash::UNKNOWN:
return "unknown";
default:
return "unexpected error type";
}
}
std::unique_ptr<crashpad::MinidumpUserExtensionStreamDataSource>
HandleException(const crashpad::ProcessSnapshot& snapshot) {
gwp_asan::Crash proto;
auto result = CrashAnalyzer::GetExceptionInfo(snapshot, &proto);
UMA_HISTOGRAM_ENUMERATION("GwpAsan.CrashAnalysisResult", result);
if (result != GwpAsanCrashAnalysisResult::kGwpAsanCrash)
return nullptr;
DLOG(ERROR) << "Detected GWP-ASan crash for allocation at 0x" << std::hex
<< proto.allocation_address() << std::dec << " of type "
<< ErrorToString(proto.error_type());
return std::make_unique<BufferExtensionStreamDataSource>(
kGwpAsanMinidumpStreamType, proto);
}
} // namespace
} // namespace internal
std::unique_ptr<crashpad::MinidumpUserExtensionStreamDataSource>
UserStreamDataSource::ProduceStreamData(crashpad::ProcessSnapshot* snapshot) {
if (!snapshot) {
DLOG(ERROR) << "Null process snapshot is unexpected.";
return nullptr;
}
return internal::HandleException(*snapshot);
}
} // namespace gwp_asan
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_GWP_ASAN_CRASH_HANDLER_CRASH_HANDLER_H_
#define COMPONENTS_GWP_ASAN_CRASH_HANDLER_CRASH_HANDLER_H_
#include "base/macros.h"
#include "third_party/crashpad/crashpad/handler/user_stream_data_source.h"
namespace crashpad {
class ProcessSnapshot;
} // namespace crashpad
namespace gwp_asan {
namespace internal {
// The stream type assigned to the minidump stream that holds the serialized
// GWP ASan crash state.
const uint32_t kGwpAsanMinidumpStreamType = 0x4B6B0004;
} // namespace internal
// A crashpad extension installed at crashpad handler start-up to inspect the
// crashing process, see if the crash was caused by a GWP-ASan exception, and
// add a GWP-ASan stream to the minidump if so.
class UserStreamDataSource : public crashpad::UserStreamDataSource {
public:
UserStreamDataSource() = default;
std::unique_ptr<crashpad::MinidumpUserExtensionStreamDataSource>
ProduceStreamData(crashpad::ProcessSnapshot* process_snapshot) override;
private:
DISALLOW_COPY_AND_ASSIGN(UserStreamDataSource);
};
} // namespace gwp_asan
#endif // COMPONENTS_GWP_ASAN_CRASH_HANDLER_CRASH_HANDLER_H_
This diff is collapsed.
......@@ -24806,6 +24806,23 @@ Called by update_gpu_driver_bug_workaround_entries.py.-->
<int value="1" label="Broadcast"/>
</enum>
<enum name="GwpAsanCrashAnalysisResult">
<int value="0" label="The crash is not caused by GWP-ASan"/>
<int value="1" label="The crash is caused by GWP-ASan"/>
<int value="2" label="The ProcessMemory from the snapshot was null"/>
<int value="3"
label="Failed to read the crashing process' memory of the global
allocator"/>
<int value="4"
label="The crashing process' global allocator members failed sanity
checks"/>
<int value="5" label="Failed to read crash stack traces"/>
<int value="6" label="The ExceptionSnapshot CPU context was null"/>
<int value="7"
label="The crashing process' bitness does not match the crash handler"/>
<int value="8" label="The allocator computed an invalid slot index"/>
</enum>
<enum name="GzipEncodingFixupResult">
<int value="0" label="Gzip encoding left as-is"/>
<int value="1" label="MIME type indicated GZIP content"/>
......@@ -38023,6 +38023,17 @@ uploading your change for review.
</summary>
</histogram>
<histogram name="GwpAsan.CrashAnalysisResult" enum="GwpAsanCrashAnalysisResult"
expires_after="M76">
<owner>vtsyrklevich@chromium.org</owner>
<owner>dynamic-tools@google.com</owner>
<summary>
Reports the GWP-ASan crash analysis result. That can either be a
determination that the crash was related to GWP-ASan, not related to
GWP-ASan, or an error making that determination. Recorded with every crash.
</summary>
</histogram>
<histogram name="Hardware.Display.Count.OnChange">
<owner>erikchen@chromium.org</owner>
<summary>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment