Commit 9caa221f authored by Vlad Tsyrklevich's avatar Vlad Tsyrklevich

Reland "GWP-ASan: Add crash handler hook."

This is a reland of 409ceb85

Original change's description:
> 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/1330283
> Reviewed-by: Mark Mentovai <mark@chromium.org>
> Reviewed-by: Brian White <bcwhite@chromium.org>
> Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#611033}

TBR=bcwhite@chromium.org

Bug: 896019
Change-Id: Ibc35c33ccc816cd2436412e2c6f399908161e7e1
Reviewed-on: https://chromium-review.googlesource.com/c/1351935
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
Reviewed-by: default avatarMark Mentovai <mark@chromium.org>
Reviewed-by: default avatarVitaly Buka <vitalybuka@chromium.org>
Cr-Commit-Position: refs/heads/master@{#611337}
parent 9c9c1d26
......@@ -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/compiler_specific.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) {
bool result = crash.SerializeToString(&data_);
DCHECK(result);
ALLOW_UNUSED_LOCAL(result);
}
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;
LOG(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.
......@@ -24813,6 +24813,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"/>
......@@ -38112,6 +38112,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