Commit 5ad4c5de authored by Vlad Tsyrklevich's avatar Vlad Tsyrklevich Committed by Commit Bot

GWP-ASan: Refactor GuardedPageAllocator into two objects

Refactor a trivially copyable (e.g. plain old data) object out of the
current GuardedPageAllocator. This 'base state' object encapsulates
just the information required by the crash handler. Because its
construction/destruction is trivial, a base state object can be
overwritten with out-of-process memory and destructed without concerns
about undefined behavior or destructing complex objects with pointers
from another process.

The GuardedPageAllocator encapsulates all of the remaining data and
allocation logic that does not to be reached by the crash handler. As
such, it's been refactored into client/.

Bug: 896019

Change-Id: I418411b914a61c592f8b585d55058aa5a705acfb
Reviewed-on: https://chromium-review.googlesource.com/c/1339246
Commit-Queue: Vlad Tsyrklevich <vtsyrklevich@chromium.org>
Reviewed-by: default avatarVitaly Buka <vitalybuka@chromium.org>
Cr-Commit-Position: refs/heads/master@{#610902}
parent 391b36e5
...@@ -7,7 +7,6 @@ source_set("unit_tests") { ...@@ -7,7 +7,6 @@ source_set("unit_tests") {
if (is_win) { if (is_win) {
deps = [ deps = [
"//components/gwp_asan/client:unit_tests", "//components/gwp_asan/client:unit_tests",
"//components/gwp_asan/common:unit_tests",
] ]
} }
} }
...@@ -2,12 +2,17 @@ ...@@ -2,12 +2,17 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
assert(is_win, "GWP-ASan client currently only supports Windows.")
component("client") { component("client") {
output_name = "gwp_asan_client" output_name = "gwp_asan_client"
sources = [ sources = [
"crash_key.cc", "crash_key.cc",
"crash_key.h", "crash_key.h",
"export.h", "export.h",
"guarded_page_allocator.cc",
"guarded_page_allocator.h",
"guarded_page_allocator_win.cc",
"gwp_asan.cc", "gwp_asan.cc",
"gwp_asan.h", "gwp_asan.h",
"sampling_allocator_shims.cc", "sampling_allocator_shims.cc",
...@@ -28,6 +33,7 @@ component("client") { ...@@ -28,6 +33,7 @@ component("client") {
source_set("unit_tests") { source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"guarded_page_allocator_unittest.cc",
"sampling_allocator_shims_unittest.cc", "sampling_allocator_shims_unittest.cc",
] ]
deps = [ deps = [
......
...@@ -12,10 +12,9 @@ ...@@ -12,10 +12,9 @@
namespace gwp_asan { namespace gwp_asan {
namespace internal { namespace internal {
void RegisterAllocatorAddress(void* gpa_ptr) { void RegisterAllocatorAddress(uintptr_t ptr) {
static crash_reporter::CrashKeyString<24> gpa_crash_key(kGpaCrashKey); static crash_reporter::CrashKeyString<24> gpa_crash_key(kGpaCrashKey);
gpa_crash_key.Set( gpa_crash_key.Set(base::StringPrintf("%zx", ptr));
base::StringPrintf("%zx", reinterpret_cast<uintptr_t>(gpa_ptr)));
} }
} // namespace internal } // namespace internal
......
...@@ -5,13 +5,14 @@ ...@@ -5,13 +5,14 @@
#ifndef COMPONENTS_GWP_ASAN_CLIENT_CRASH_KEY_H_ #ifndef COMPONENTS_GWP_ASAN_CLIENT_CRASH_KEY_H_
#define COMPONENTS_GWP_ASAN_CLIENT_CRASH_KEY_H_ #define COMPONENTS_GWP_ASAN_CLIENT_CRASH_KEY_H_
#include "components/gwp_asan/common/allocator_state.h"
namespace gwp_asan { namespace gwp_asan {
namespace internal { namespace internal {
// Registers a crash key that both signals to the crash handler that GWP-ASan // Registers a crash key that both signals to the crash handler that GWP-ASan
// has been enabled and also where to find the GuardedPageAllocator for this // has been enabled and also where to find the AllocatorState for this process.
// process. void RegisterAllocatorAddress(uintptr_t ptr);
void RegisterAllocatorAddress(void* gpa_ptr);
} // namespace internal } // namespace internal
} // namespace gwp_asan } // namespace gwp_asan
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef COMPONENTS_GWP_ASAN_COMMON_GUARDED_PAGE_ALLOCATOR_H_ #ifndef COMPONENTS_GWP_ASAN_CLIENT_GUARDED_PAGE_ALLOCATOR_H_
#define COMPONENTS_GWP_ASAN_COMMON_GUARDED_PAGE_ALLOCATOR_H_ #define COMPONENTS_GWP_ASAN_CLIENT_GUARDED_PAGE_ALLOCATOR_H_
#include <atomic> #include <atomic>
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
#include "base/no_destructor.h" #include "base/no_destructor.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "base/thread_annotations.h" #include "base/thread_annotations.h"
#include "base/threading/platform_thread.h" #include "components/gwp_asan/client/export.h"
#include "components/gwp_asan/common/allocator_state.h"
namespace gwp_asan { namespace gwp_asan {
namespace internal { namespace internal {
...@@ -22,22 +23,13 @@ namespace internal { ...@@ -22,22 +23,13 @@ namespace internal {
// platforms.) // platforms.)
unsigned CountTrailingZeroBits64(uint64_t x); unsigned CountTrailingZeroBits64(uint64_t x);
class GuardedPageAllocator { // This class encompasses the allocation and deallocation logic on top of the
// AllocatorState. Its members are not inspected or used by the crash handler.
class GWP_ASAN_EXPORT GuardedPageAllocator {
public: public:
// Maximum number of pages this class can allocate.
static constexpr size_t kGpaMaxPages = 64;
// Default maximum alignment for all returned allocations. // Default maximum alignment for all returned allocations.
static constexpr size_t kGpaAllocAlignment = 16; static constexpr size_t kGpaAllocAlignment = 16;
enum class ErrorType {
kUseAfterFree = 0,
kBufferUnderflow = 1,
kBufferOverflow = 2,
kDoubleFree = 3,
kUnknown = 4,
};
// Configures this allocator to map memory for num_pages pages (excluding // Configures this allocator to map memory for num_pages pages (excluding
// guard pages). num_pages must be in the range [1, kGpaMaxPages]. Init should // guard pages). num_pages must be in the range [1, kGpaMaxPages]. Init should
// only be called once. // only be called once.
...@@ -46,14 +38,13 @@ class GuardedPageAllocator { ...@@ -46,14 +38,13 @@ class GuardedPageAllocator {
// On success, returns a pointer to size bytes of page-guarded memory. On // On success, returns a pointer to size bytes of page-guarded memory. On
// failure, returns nullptr. The allocation is not guaranteed to be // failure, returns nullptr. The allocation is not guaranteed to be
// zero-filled. Failure can occur if memory could not be mapped or protected, // zero-filled. Failure can occur if memory could not be mapped or protected,
// if the allocation is greather than a page in size, or if all guarded pages // or if all guarded pages are already allocated.
// are already allocated.
// //
// The align parameter specifies a power of two to align the allocation up to. // The align parameter specifies a power of two to align the allocation up to.
// It must be less than or equal to the allocation size. If it's left as zero // It must be less than or equal to the allocation size. If it's left as zero
// it will default to the default alignment the allocator chooses. // it will default to the default alignment the allocator chooses.
// //
// Precondition: Init() must have been called, align <= size // Precondition: Init() must have been called, align <= size <= page_size_
void* Allocate(size_t size, size_t align = 0); void* Allocate(size_t size, size_t align = 0);
// Deallocates memory pointed to by ptr. ptr must have been previously // Deallocates memory pointed to by ptr. ptr must have been previously
...@@ -64,39 +55,20 @@ class GuardedPageAllocator { ...@@ -64,39 +55,20 @@ class GuardedPageAllocator {
// previously returned by a call to Allocate. // previously returned by a call to Allocate.
size_t GetRequestedSize(const void* ptr) const; size_t GetRequestedSize(const void* ptr) const;
// Get the address of the GuardedPageAllocator crash key (the address of the
// the shared allocator state with the crash handler.)
uintptr_t GetCrashKeyAddress() const;
// Returns true if ptr points to memory managed by this class. // Returns true if ptr points to memory managed by this class.
inline bool PointerIsMine(const void* ptr) const { inline bool PointerIsMine(const void* ptr) const {
uintptr_t addr = reinterpret_cast<uintptr_t>(ptr); return state_.PointerIsMine(reinterpret_cast<uintptr_t>(ptr));
return pages_base_addr_ <= addr && addr < pages_end_addr_;
} }
private: private:
using BitMap = uint64_t; using BitMap = uint64_t;
static_assert(kGpaMaxPages == sizeof(BitMap) * 8, static_assert(AllocatorState::kGpaMaxPages == sizeof(BitMap) * 8,
"Maximum number of pages is the size of free_pages_ bitmap"); "Maximum number of pages is the size of free_pages_ bitmap");
// Structure for storing data about a slot.
struct SlotMetadata {
// Information saved for allocations and deallocations.
struct AllocationInfo {
// (De)allocation thread id or base::kInvalidThreadId if no (de)allocation
// occurred.
base::PlatformThreadId tid = base::kInvalidThreadId;
// Address of stack trace addresses or null if no (de)allocation occurred.
uintptr_t trace_addr = 0;
// Stack trace length or 0 if no (de)allocation occurred.
size_t trace_len = 0;
};
// Size of the allocation
size_t alloc_size = 0;
// The allocation address.
uintptr_t alloc_ptr = 0;
AllocationInfo alloc;
AllocationInfo dealloc;
};
// Does not allocate any memory for the allocator, to finish initializing call // Does not allocate any memory for the allocator, to finish initializing call
// Init(). // Init().
GuardedPageAllocator(); GuardedPageAllocator();
...@@ -122,24 +94,6 @@ class GuardedPageAllocator { ...@@ -122,24 +94,6 @@ class GuardedPageAllocator {
// Marks the specified slot as unreserved. // Marks the specified slot as unreserved.
void FreeSlot(size_t slot) LOCKS_EXCLUDED(lock_); void FreeSlot(size_t slot) LOCKS_EXCLUDED(lock_);
// Returns the address of the page that addr resides on.
uintptr_t GetPageAddr(uintptr_t addr) const;
// Returns an address somewhere on the valid page nearest to addr.
uintptr_t GetNearestValidPage(uintptr_t addr) const;
// Returns the slot number for the page nearest to addr.
size_t GetNearestSlot(uintptr_t addr) const;
// Returns the likely error type given an exception address and whether its
// previously been allocated and deallocated.
ErrorType GetErrorType(uintptr_t addr,
bool allocated,
bool deallocated) const;
uintptr_t SlotToAddr(size_t slot) const;
size_t AddrToSlot(uintptr_t addr) const;
// Allocate num_pages_ stack traces. // Allocate num_pages_ stack traces.
void AllocateStackTraces(); void AllocateStackTraces();
...@@ -157,6 +111,9 @@ class GuardedPageAllocator { ...@@ -157,6 +111,9 @@ class GuardedPageAllocator {
void RecordAllocationInSlot(size_t slot, size_t size, void* ptr); void RecordAllocationInSlot(size_t slot, size_t size, void* ptr);
void RecordDeallocationInSlot(size_t slot); void RecordDeallocationInSlot(size_t slot);
// Allocator state shared with with the crash analyzer.
AllocatorState state_;
// Allocator lock that protects free_pages_. // Allocator lock that protects free_pages_.
base::Lock lock_; base::Lock lock_;
...@@ -164,24 +121,11 @@ class GuardedPageAllocator { ...@@ -164,24 +121,11 @@ class GuardedPageAllocator {
// Bit=1: Free. Bit=0: Reserved. // Bit=1: Free. Bit=0: Reserved.
BitMap free_pages_ GUARDED_BY(lock_) = 0; BitMap free_pages_ GUARDED_BY(lock_) = 0;
// Information about every allocation, including its size, offset, and // StackTrace objects for every slot in AllocatorState::data_. We avoid
// pointers to the allocation/deallocation stack traces (if present.)
SlotMetadata data_[kGpaMaxPages] = {};
uintptr_t pages_base_addr_ = 0; // Points to start of mapped region.
uintptr_t pages_end_addr_ = 0; // Points to the end of mapped region.
uintptr_t first_page_addr_ = 0; // Points to first allocatable page.
size_t num_pages_ = 0; // Number of pages mapped (excluding guard pages).
size_t page_size_ = 0; // Page size.
// Set to true if a double free has occurred.
std::atomic<bool> double_free_detected_{false};
// StackTrace objects for every slot in AllocateStateBase::data_. We avoid
// statically allocating the StackTrace objects because they are large and // statically allocating the StackTrace objects because they are large and
// the allocator may be initialized with num_pages_ < kGpaMaxPages. // the allocator may be initialized with num_pages_ < kGpaMaxPages.
base::debug::StackTrace* alloc_traces[kGpaMaxPages]; base::debug::StackTrace* alloc_traces[AllocatorState::kGpaMaxPages];
base::debug::StackTrace* dealloc_traces[kGpaMaxPages]; base::debug::StackTrace* dealloc_traces[AllocatorState::kGpaMaxPages];
// Required for a singleton to access the constructor. // Required for a singleton to access the constructor.
friend base::NoDestructor<GuardedPageAllocator>; friend base::NoDestructor<GuardedPageAllocator>;
...@@ -197,4 +141,4 @@ class GuardedPageAllocator { ...@@ -197,4 +141,4 @@ class GuardedPageAllocator {
} // namespace internal } // namespace internal
} // namespace gwp_asan } // namespace gwp_asan
#endif // COMPONENTS_GWP_ASAN_COMMON_GUARDED_PAGE_ALLOCATOR_H_ #endif // COMPONENTS_GWP_ASAN_CLIENT_GUARDED_PAGE_ALLOCATOR_H_
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "components/gwp_asan/common/guarded_page_allocator.h" #include "components/gwp_asan/client/guarded_page_allocator.h"
#include <array> #include <array>
#include <set> #include <set>
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
namespace gwp_asan { namespace gwp_asan {
namespace internal { namespace internal {
static constexpr size_t kGpaMaxPages = GuardedPageAllocator::kGpaMaxPages; static constexpr size_t kGpaMaxPages = AllocatorState::kGpaMaxPages;
class GuardedPageAllocatorTest : public testing::Test { class GuardedPageAllocatorTest : public testing::Test {
protected: protected:
...@@ -120,18 +120,20 @@ TEST_F(GuardedPageAllocatorTest, AllocationAlignment) { ...@@ -120,18 +120,20 @@ TEST_F(GuardedPageAllocatorTest, AllocationAlignment) {
} }
TEST_F(GuardedPageAllocatorTest, GetNearestValidPageEdgeCases) { TEST_F(GuardedPageAllocatorTest, GetNearestValidPageEdgeCases) {
EXPECT_EQ(gpa_.GetPageAddr(gpa_.GetNearestValidPage(gpa_.pages_base_addr_)), EXPECT_EQ(gpa_.state_.GetPageAddr(
gpa_.first_page_addr_); gpa_.state_.GetNearestValidPage(gpa_.state_.pages_base_addr)),
EXPECT_EQ( gpa_.state_.first_page_addr);
gpa_.GetPageAddr(gpa_.GetNearestValidPage(gpa_.pages_end_addr_ - 1)), EXPECT_EQ(gpa_.state_.GetPageAddr(gpa_.state_.GetNearestValidPage(
gpa_.pages_end_addr_ - (2 * gpa_.page_size_)); gpa_.state_.pages_end_addr - 1)),
gpa_.state_.pages_end_addr - (2 * gpa_.state_.page_size));
} }
TEST_F(GuardedPageAllocatorTest, GetErrorTypeEdgeCases) { TEST_F(GuardedPageAllocatorTest, GetErrorTypeEdgeCases) {
EXPECT_EQ(gpa_.GetErrorType(gpa_.pages_base_addr_, true, false), EXPECT_EQ(gpa_.state_.GetErrorType(gpa_.state_.pages_base_addr, true, false),
GuardedPageAllocator::ErrorType::kBufferUnderflow); AllocatorState::ErrorType::kBufferUnderflow);
EXPECT_EQ(gpa_.GetErrorType(gpa_.pages_end_addr_ - 1, true, false), EXPECT_EQ(
GuardedPageAllocator::ErrorType::kBufferOverflow); gpa_.state_.GetErrorType(gpa_.state_.pages_end_addr - 1, true, false),
AllocatorState::ErrorType::kBufferOverflow);
} }
class GuardedPageAllocatorParamTest class GuardedPageAllocatorParamTest
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
#include <windows.h> #include <windows.h>
#include "components/gwp_asan/common/guarded_page_allocator.h" #include "components/gwp_asan/client/guarded_page_allocator.h"
#include "base/bits.h" #include "base/bits.h"
#include "base/logging.h" #include "base/logging.h"
...@@ -34,7 +34,7 @@ unsigned CountTrailingZeroBits64(uint64_t x) { ...@@ -34,7 +34,7 @@ unsigned CountTrailingZeroBits64(uint64_t x) {
// protection routines can be broken out in base/ and merged with those used for // protection routines can be broken out in base/ and merged with those used for
// PartionAlloc/ProtectedMemory. // PartionAlloc/ProtectedMemory.
bool GuardedPageAllocator::MapPages() { bool GuardedPageAllocator::MapPages() {
size_t len = (2 * num_pages_ + 1) * page_size_; size_t len = (2 * state_.num_pages + 1) * state_.page_size;
void* base_ptr = VirtualAlloc(nullptr, len, MEM_RESERVE, PAGE_NOACCESS); void* base_ptr = VirtualAlloc(nullptr, len, MEM_RESERVE, PAGE_NOACCESS);
if (!base_ptr) { if (!base_ptr) {
DPLOG(ERROR) << "Failed to reserve guarded allocator region"; DPLOG(ERROR) << "Failed to reserve guarded allocator region";
...@@ -44,10 +44,10 @@ bool GuardedPageAllocator::MapPages() { ...@@ -44,10 +44,10 @@ bool GuardedPageAllocator::MapPages() {
uintptr_t base_addr = reinterpret_cast<uintptr_t>(base_ptr); uintptr_t base_addr = reinterpret_cast<uintptr_t>(base_ptr);
// Commit the pages used for allocations. // Commit the pages used for allocations.
for (size_t i = 0; i < num_pages_; i++) { for (size_t i = 0; i < state_.num_pages; i++) {
uintptr_t address = base_addr + page_size_ * ((2 * i) + 1); uintptr_t address = base_addr + state_.page_size * ((2 * i) + 1);
LPVOID ret = VirtualAlloc(reinterpret_cast<LPVOID>(address), page_size_, LPVOID ret = VirtualAlloc(reinterpret_cast<LPVOID>(address),
MEM_COMMIT, PAGE_NOACCESS); state_.page_size, MEM_COMMIT, PAGE_NOACCESS);
if (!ret) { if (!ret) {
PLOG(ERROR) << "Failed to commit allocation page"; PLOG(ERROR) << "Failed to commit allocation page";
UnmapPages(); UnmapPages();
...@@ -55,30 +55,30 @@ bool GuardedPageAllocator::MapPages() { ...@@ -55,30 +55,30 @@ bool GuardedPageAllocator::MapPages() {
} }
} }
pages_base_addr_ = base_addr; state_.pages_base_addr = base_addr;
first_page_addr_ = pages_base_addr_ + page_size_; state_.first_page_addr = state_.pages_base_addr + state_.page_size;
pages_end_addr_ = pages_base_addr_ + len; state_.pages_end_addr = state_.pages_base_addr + len;
return true; return true;
} }
void GuardedPageAllocator::UnmapPages() { void GuardedPageAllocator::UnmapPages() {
DCHECK(pages_base_addr_); DCHECK(state_.pages_base_addr);
BOOL err = BOOL err = VirtualFree(reinterpret_cast<void*>(state_.pages_base_addr), 0,
VirtualFree(reinterpret_cast<void*>(pages_base_addr_), 0, MEM_RELEASE); MEM_RELEASE);
DCHECK(err); DCHECK(err);
(void)err; (void)err;
} }
void GuardedPageAllocator::MarkPageReadWrite(void* ptr) { void GuardedPageAllocator::MarkPageReadWrite(void* ptr) {
DWORD old_prot; DWORD old_prot;
BOOL err = VirtualProtect(ptr, page_size_, PAGE_READWRITE, &old_prot); BOOL err = VirtualProtect(ptr, state_.page_size, PAGE_READWRITE, &old_prot);
PCHECK(err != 0) << "VirtualProtect"; PCHECK(err != 0) << "VirtualProtect";
} }
void GuardedPageAllocator::MarkPageInaccessible(void* ptr) { void GuardedPageAllocator::MarkPageInaccessible(void* ptr) {
DWORD old_prot; DWORD old_prot;
BOOL err = VirtualProtect(ptr, page_size_, PAGE_NOACCESS, &old_prot); BOOL err = VirtualProtect(ptr, state_.page_size, PAGE_NOACCESS, &old_prot);
PCHECK(err != 0) << "VirtualProtect"; PCHECK(err != 0) << "VirtualProtect";
} }
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "base/rand_util.h" #include "base/rand_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "components/gwp_asan/client/guarded_page_allocator.h"
#include "components/gwp_asan/client/sampling_allocator_shims.h" #include "components/gwp_asan/client/sampling_allocator_shims.h"
#include "components/gwp_asan/common/guarded_page_allocator.h"
namespace gwp_asan { namespace gwp_asan {
...@@ -23,8 +23,8 @@ namespace { ...@@ -23,8 +23,8 @@ namespace {
const base::Feature kGwpAsan{"GwpAsanMalloc", const base::Feature kGwpAsan{"GwpAsanMalloc",
base::FEATURE_DISABLED_BY_DEFAULT}; base::FEATURE_DISABLED_BY_DEFAULT};
const base::FeatureParam<int> kAllocationsParam{ const base::FeatureParam<int> kAllocationsParam{&kGwpAsan, "TotalAllocations",
&kGwpAsan, "TotalAllocations", GuardedPageAllocator::kGpaMaxPages}; AllocatorState::kGpaMaxPages};
const base::FeatureParam<int> kAllocationSamplingParam{ const base::FeatureParam<int> kAllocationSamplingParam{
&kGwpAsan, "AllocationSamplingFrequency", 128}; &kGwpAsan, "AllocationSamplingFrequency", 128};
...@@ -39,7 +39,7 @@ bool EnableForMalloc() { ...@@ -39,7 +39,7 @@ bool EnableForMalloc() {
int total_allocations = kAllocationsParam.Get(); int total_allocations = kAllocationsParam.Get();
if (total_allocations < 1 || if (total_allocations < 1 ||
total_allocations > total_allocations >
base::checked_cast<int>(GuardedPageAllocator::kGpaMaxPages)) { base::checked_cast<int>(AllocatorState::kGpaMaxPages)) {
DLOG(ERROR) << "GWP-ASan TotalAllocations is out-of-range: " DLOG(ERROR) << "GWP-ASan TotalAllocations is out-of-range: "
<< total_allocations; << total_allocations;
return false; return false;
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
#include "build/build_config.h" #include "build/build_config.h"
#include "components/gwp_asan/client/crash_key.h" #include "components/gwp_asan/client/crash_key.h"
#include "components/gwp_asan/client/export.h" #include "components/gwp_asan/client/export.h"
#include "components/gwp_asan/common/guarded_page_allocator.h" #include "components/gwp_asan/client/guarded_page_allocator.h"
#if defined(OS_WIN) #if defined(OS_WIN)
#include "components/gwp_asan/client/sampling_allocator_shims_win.h" #include "components/gwp_asan/client/sampling_allocator_shims_win.h"
...@@ -232,7 +232,7 @@ GWP_ASAN_EXPORT GuardedPageAllocator& GetGpaForTesting() { ...@@ -232,7 +232,7 @@ GWP_ASAN_EXPORT GuardedPageAllocator& GetGpaForTesting() {
void InstallAllocatorHooks(size_t num_pages, size_t sampling_frequency) { void InstallAllocatorHooks(size_t num_pages, size_t sampling_frequency) {
#if BUILDFLAG(USE_ALLOCATOR_SHIM) #if BUILDFLAG(USE_ALLOCATOR_SHIM)
GetGpa().Init(num_pages); GetGpa().Init(num_pages);
RegisterAllocatorAddress(&GetGpa()); RegisterAllocatorAddress(GetGpa().GetCrashKeyAddress());
sampling_state.Init(sampling_frequency); sampling_state.Init(sampling_frequency);
base::allocator::InsertAllocatorDispatch(&g_allocator_dispatch); base::allocator::InsertAllocatorDispatch(&g_allocator_dispatch);
#else #else
......
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
#include "base/test/test_timeouts.h" #include "base/test/test_timeouts.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "components/crash/core/common/crash_key.h" #include "components/crash/core/common/crash_key.h"
#include "components/gwp_asan/client/guarded_page_allocator.h"
#include "components/gwp_asan/common/crash_key_name.h" #include "components/gwp_asan/common/crash_key_name.h"
#include "components/gwp_asan/common/guarded_page_allocator.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h" #include "testing/multiprocess_func_list.h"
...@@ -83,7 +83,7 @@ bool allocationCheck(std::function<void*(void)> allocate, ...@@ -83,7 +83,7 @@ bool allocationCheck(std::function<void*(void)> allocate,
} }
MULTIPROCESS_TEST_MAIN(BasicFunctionality) { MULTIPROCESS_TEST_MAIN(BasicFunctionality) {
InstallAllocatorHooks(GuardedPageAllocator::kGpaMaxPages, kSamplingFrequency); InstallAllocatorHooks(AllocatorState::kGpaMaxPages, kSamplingFrequency);
const size_t page_size = base::GetPageSize(); const size_t page_size = base::GetPageSize();
int failures = 0; int failures = 0;
...@@ -133,7 +133,7 @@ TEST_F(SamplingAllocatorShimsTest, BasicFunctionality) { ...@@ -133,7 +133,7 @@ TEST_F(SamplingAllocatorShimsTest, BasicFunctionality) {
} }
MULTIPROCESS_TEST_MAIN(Realloc) { MULTIPROCESS_TEST_MAIN(Realloc) {
InstallAllocatorHooks(GuardedPageAllocator::kGpaMaxPages, kSamplingFrequency); InstallAllocatorHooks(AllocatorState::kGpaMaxPages, kSamplingFrequency);
void* alloc = GetGpaForTesting().Allocate(base::GetPageSize()); void* alloc = GetGpaForTesting().Allocate(base::GetPageSize());
CHECK_NE(alloc, nullptr); CHECK_NE(alloc, nullptr);
...@@ -159,7 +159,7 @@ TEST_F(SamplingAllocatorShimsTest, Realloc) { ...@@ -159,7 +159,7 @@ TEST_F(SamplingAllocatorShimsTest, Realloc) {
} }
MULTIPROCESS_TEST_MAIN(Calloc) { MULTIPROCESS_TEST_MAIN(Calloc) {
InstallAllocatorHooks(GuardedPageAllocator::kGpaMaxPages, kSamplingFrequency); InstallAllocatorHooks(AllocatorState::kGpaMaxPages, kSamplingFrequency);
for (size_t i = 0; i < kLoopIterations; i++) { for (size_t i = 0; i < kLoopIterations; i++) {
unsigned char* alloc = unsigned char* alloc =
...@@ -184,13 +184,15 @@ TEST_F(SamplingAllocatorShimsTest, Calloc) { ...@@ -184,13 +184,15 @@ TEST_F(SamplingAllocatorShimsTest, Calloc) {
} }
MULTIPROCESS_TEST_MAIN(CrashKey) { MULTIPROCESS_TEST_MAIN(CrashKey) {
InstallAllocatorHooks(GuardedPageAllocator::kGpaMaxPages, kSamplingFrequency); InstallAllocatorHooks(AllocatorState::kGpaMaxPages, kSamplingFrequency);
std::string crash_key = crash_reporter::GetCrashKeyValue(kGpaCrashKey); std::string crash_key = crash_reporter::GetCrashKeyValue(kGpaCrashKey);
uint64_t value; uint64_t value;
if (!base::HexStringToUInt64(crash_key, &value) || if (!base::HexStringToUInt64(crash_key, &value))
value != reinterpret_cast<uintptr_t>(&GetGpaForTesting())) return kFailure;
if (value != GetGpaForTesting().GetCrashKeyAddress())
return kFailure; return kFailure;
return kSuccess; return kSuccess;
......
...@@ -2,29 +2,14 @@ ...@@ -2,29 +2,14 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
assert(is_win, "GWP-ASan currently only supports Windows.")
static_library("common") { static_library("common") {
sources = [ sources = [
"allocator_state.cc",
"allocator_state.h",
"crash_key_name.h", "crash_key_name.h",
"guarded_page_allocator.cc",
"guarded_page_allocator.h",
"guarded_page_allocator_win.cc",
] ]
deps = [ deps = [
"//base", "//base",
] ]
} }
source_set("unit_tests") {
testonly = true
sources = [
"guarded_page_allocator_unittest.cc",
]
deps = [
":common",
"//base/test:test_support",
"//testing/gtest",
]
}
// 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 "base/bits.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
using base::debug::StackTrace;
namespace gwp_asan {
namespace internal {
// TODO: Delete out-of-line constexpr defininitons once C++17 is in use.
constexpr size_t AllocatorState::kGpaMaxPages;
uintptr_t AllocatorState::GetPageAddr(uintptr_t addr) const {
const uintptr_t addr_mask = ~(page_size - 1ULL);
return addr & addr_mask;
}
uintptr_t AllocatorState::GetNearestValidPage(uintptr_t addr) const {
if (addr < first_page_addr)
return first_page_addr;
const uintptr_t last_page_addr = pages_end_addr - 2 * page_size;
if (addr > last_page_addr)
return last_page_addr;
uintptr_t offset = addr - first_page_addr;
// If addr is already on a valid page, just return addr.
if ((offset >> base::bits::Log2Floor(page_size)) % 2 == 0)
return addr;
// ptr points to a guard page, so get nearest valid page.
const size_t kHalfPageSize = page_size / 2;
if ((offset >> base::bits::Log2Floor(kHalfPageSize)) % 2 == 0) {
return addr - kHalfPageSize; // Round down.
}
return addr + kHalfPageSize; // Round up.
}
size_t AllocatorState::GetNearestSlot(uintptr_t addr) const {
return AddrToSlot(GetPageAddr(GetNearestValidPage(addr)));
}
AllocatorState::ErrorType AllocatorState::GetErrorType(uintptr_t addr,
bool allocated,
bool deallocated) const {
if (!allocated)
return ErrorType::kUnknown;
if (double_free_detected)
return ErrorType::kDoubleFree;
if (deallocated)
return ErrorType::kUseAfterFree;
if (addr < first_page_addr)
return ErrorType::kBufferUnderflow;
const uintptr_t last_page_addr = pages_end_addr - 2 * page_size;
if (addr > last_page_addr)
return ErrorType::kBufferOverflow;
const uintptr_t offset = addr - first_page_addr;
DCHECK_NE((offset >> base::bits::Log2Floor(page_size)) % 2, 0ULL);
const size_t kHalfPageSize = page_size / 2;
return (offset >> base::bits::Log2Floor(kHalfPageSize)) % 2 == 0
? ErrorType::kBufferOverflow
: ErrorType::kBufferUnderflow;
}
uintptr_t AllocatorState::SlotToAddr(size_t slot) const {
DCHECK_LT(slot, kGpaMaxPages);
return first_page_addr + 2 * slot * page_size;
}
size_t AllocatorState::AddrToSlot(uintptr_t addr) const {
DCHECK_EQ(addr % page_size, 0ULL);
uintptr_t offset = addr - first_page_addr;
DCHECK_EQ((offset >> base::bits::Log2Floor(page_size)) % 2, 0ULL);
size_t slot = (offset >> base::bits::Log2Floor(page_size)) / 2;
DCHECK_LT(slot, kGpaMaxPages);
return slot;
}
} // namespace internal
} // namespace gwp_asan
// Copyright (c) 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.
// The AllocateState 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
// corrupted or maliciously tampered with, this class is security sensitive and
// needs to be modified with care. It has been purposefully designed to be:
// - Minimal: This is the minimum set of methods and members required by the
// crash handler.
// - Trivially copyable: An instance of this object is copied from another
// processes memory. Ensuring it is trivially copyable means that the crash
// handler will not accidentally trigger a complex destructor on objects
// initialized from another processes memory.
// - 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.
#ifndef COMPONENTS_GWP_ASAN_COMMON_ALLOCATOR_STATE_H_
#define COMPONENTS_GWP_ASAN_COMMON_ALLOCATOR_STATE_H_
#include <atomic>
#include "base/debug/stack_trace.h"
#include "base/threading/platform_thread.h"
namespace gwp_asan {
namespace internal {
class GuardedPageAllocator;
class AllocatorState {
public:
// Maximum number of pages this class can allocate.
static constexpr size_t kGpaMaxPages = 64;
enum class ErrorType {
kUseAfterFree = 0,
kBufferUnderflow = 1,
kBufferOverflow = 2,
kDoubleFree = 3,
kUnknown = 4,
};
// Structure for storing data about a slot.
struct SlotMetadata {
// Information saved for allocations and deallocations.
struct AllocationInfo {
// (De)allocation thread id or base::kInvalidThreadId if no (de)allocation
// occurred.
base::PlatformThreadId tid = base::kInvalidThreadId;
// Pointer to stack trace addresses or null if no (de)allocation occurred.
uintptr_t trace_addr = 0;
// Stack trace length or 0 if no (de)allocation occurred.
size_t trace_len = 0;
};
// Size of the allocation
size_t alloc_size = 0;
// The allocation address.
uintptr_t alloc_ptr = 0;
AllocationInfo alloc;
AllocationInfo dealloc;
};
// TODO(vtsyrklevich): Get rid of inline (requires chromium-style plugin
// update.)
inline constexpr AllocatorState();
// Returns true if address is in memory managed by this class.
inline bool PointerIsMine(uintptr_t addr) const {
return pages_base_addr <= addr && addr < pages_end_addr;
}
// Returns the likely error type given an exception address and whether its
// previously been allocated and deallocated.
ErrorType GetErrorType(uintptr_t addr,
bool allocated,
bool deallocated) const;
// Returns the address of the page that addr resides on.
uintptr_t GetPageAddr(uintptr_t addr) const;
// Returns an address somewhere on the valid page nearest to addr.
uintptr_t GetNearestValidPage(uintptr_t addr) const;
// Returns the slot number for the page nearest to addr.
size_t GetNearestSlot(uintptr_t addr) const;
uintptr_t SlotToAddr(size_t slot) const;
size_t AddrToSlot(uintptr_t addr) const;
// Information about every allocation, including its size, offset, and
// pointers to the allocation/deallocation stack traces (if present.)
SlotMetadata data[kGpaMaxPages] = {};
uintptr_t pages_base_addr = 0; // Points to start of mapped region.
uintptr_t pages_end_addr = 0; // Points to the end of mapped region.
uintptr_t first_page_addr = 0; // Points to first allocatable page.
size_t num_pages = 0; // Number of pages mapped (excluding guard pages).
size_t page_size = 0; // Page size.
// Set to true if a double free has occurred.
std::atomic<bool> double_free_detected{false};
DISALLOW_COPY_AND_ASSIGN(AllocatorState);
};
constexpr AllocatorState::AllocatorState() {}
// Ensure that the allocator state is a plain-old-data. That way we can safely
// initialize it by copying memory from out-of-process without worrying about
// destructors operating on the fields in an unexpected way.
static_assert(std::is_trivially_copyable<AllocatorState>(),
"AllocatorState must be POD");
} // namespace internal
} // namespace gwp_asan
#endif // COMPONENTS_GWP_ASAN_COMMON_ALLOCATOR_STATE_H_
...@@ -9,8 +9,8 @@ namespace gwp_asan { ...@@ -9,8 +9,8 @@ namespace gwp_asan {
namespace internal { namespace internal {
// The name of the crash key used to convey the address of the // The name of the crash key used to convey the address of the
// GuardedPageAllocator to the crash handler. // AllocatorBaseState to the crash handler.
const char kGpaCrashKey[] = "guarded-page-allocator-address"; const char kGpaCrashKey[] = "allocator-base-state-address";
} // namespace internal } // namespace internal
} // namespace gwp_asan } // namespace gwp_asan
......
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