blob: e1b986877943d34317bed0c4a7e8cdebf8a5ae22 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// 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
// 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.
// - 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_
#include <atomic>
#include <limits>
#include <string>
#include <type_traits>
#include "base/threading/platform_thread.h"
namespace gwp_asan {
namespace internal {
class GuardedPageAllocator;
class AllocatorState {
public:
using MetadataIdx = uint16_t;
using SlotIdx = uint16_t;
// Maximum number of virtual memory slots (guard-page buffered pages) this
// class can allocate.
static constexpr size_t kMaxRequestedSlots = 4096;
// When PartitionAlloc is used as the backing allocator, we might have to
// reserve extra slots to store PA metadata. Therefore, the number of reserved
// slots might be higher than the number of requested slots. Note that the
// current value is just a reasonable upper bound; the actual "slot overhead"
// from PA is significantly smaller.
static constexpr size_t kMaxReservedSlots = 2 * kMaxRequestedSlots;
// Maximum number of concurrent allocations/metadata this class can allocate.
static constexpr size_t kMaxMetadata = 2048;
// Invalid metadata index.
static constexpr MetadataIdx kInvalidMetadataIdx = kMaxMetadata;
// Maximum number of stack trace frames to collect for an allocation or
// deallocation.
static constexpr size_t kMaxStackFrames = 100;
// Number of bytes to allocate for both allocation and deallocation packed
// stack traces. (Stack trace entries take ~3.5 bytes on average.)
static constexpr size_t kMaxPackedTraceLength = 400;
static_assert(std::numeric_limits<SlotIdx>::max() >= kMaxReservedSlots,
"SlotIdx can hold all possible slot index values");
static_assert(std::numeric_limits<MetadataIdx>::max() >= kMaxMetadata - 1,
"MetadataIdx can hold all possible metadata index values");
static_assert(kInvalidMetadataIdx >= kMaxMetadata,
"kInvalidMetadataIdx can not reference a real index");
enum class ErrorType {
kUseAfterFree = 0,
kBufferUnderflow = 1,
kBufferOverflow = 2,
kDoubleFree = 3,
kUnknown = 4,
kFreeInvalidAddress = 5,
};
enum class GetMetadataReturnType {
kGwpAsanCrash = 0,
kGwpAsanCrashWithMissingMetadata = 1,
kErrorBadSlot = 2,
kErrorBadMetadataIndex = 3,
kErrorOutdatedMetadataIndex = 4,
};
// Structure for storing data about a slot.
struct SlotMetadata {
SlotMetadata();
// Information saved for allocations and deallocations.
struct AllocationInfo {
// (De)allocation thread id or base::kInvalidThreadId if no (de)allocation
// occurred.
uint64_t tid = base::kInvalidThreadId;
// Length used to encode the packed stack trace.
uint16_t trace_len = 0;
// Whether a stack trace has been collected for this (de)allocation.
bool trace_collected = false;
static_assert(std::numeric_limits<decltype(trace_len)>::max() >=
kMaxPackedTraceLength - 1,
"trace_len can hold all possible length values.");
};
// Size of the allocation
size_t alloc_size = 0;
// The allocation address.
uintptr_t alloc_ptr = 0;
// Used to synchronize whether a deallocation has occurred (e.g. whether a
// double free has occurred) between threads.
std::atomic<bool> deallocation_occurred{false};
// Holds the combined allocation/deallocation stack traces. The deallocation
// stack trace is stored immediately after the allocation stack trace to
// optimize on space.
uint8_t stack_trace_pool[kMaxPackedTraceLength];
AllocationInfo alloc;
AllocationInfo dealloc;
};
AllocatorState();
AllocatorState(const AllocatorState&) = delete;
AllocatorState& operator=(const AllocatorState&) = delete;
// 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;
}
// 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 and an exception
// address known to be in the GWP-ASan allocator region. Given the metadata
// and slot to metadata arrays for the allocator, this method returns an enum
// indicating an error or a GWP-ASan exception with or without metadata. If
// metadata is available, the |metadata_idx| parameter stores the index of the
// relevant metadata in the given array. If an error occurs, the |error|
// parameter is filled out with an error string.
GetMetadataReturnType GetMetadataForAddress(
uintptr_t exception_address,
const SlotMetadata* metadata_arr,
const MetadataIdx* slot_to_metadata,
MetadataIdx* metadata_idx,
std::string* error) 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;
// 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.
SlotIdx GetNearestSlot(uintptr_t addr) const;
uintptr_t SlotToAddr(SlotIdx slot) const;
SlotIdx AddrToSlot(uintptr_t addr) const;
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_metadata = 0; // Number of entries in |metadata_addr|.
size_t total_requested_pages = 0; // Virtual memory page pool size.
size_t total_reserved_pages = 0; // |total_requested_pages| plus zero or
// more pages to store allocator metadata.
size_t page_size = 0; // Page size.
// Pointer to an array of metadata about every allocation, including its size,
// offset, and pointers to the allocation/deallocation stack traces (if
// present.)
uintptr_t metadata_addr = 0;
// Pointer to an array that maps a slot index to a metadata index (or
// kInvalidMetadataIdx if no such mapping exists) in |metadata_addr|.
uintptr_t slot_to_metadata_addr;
// Set to the address of a double freed allocation if a double free occurred.
uintptr_t double_free_address = 0;
// If an invalid pointer has been free()d, this is the address of that invalid
// pointer.
uintptr_t free_invalid_address = 0;
};
// 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");
static_assert(std::is_trivially_copyable<AllocatorState::SlotMetadata>(),
"AllocatorState::SlotMetadata must be POD");
} // namespace internal
} // namespace gwp_asan
#endif // COMPONENTS_GWP_ASAN_COMMON_ALLOCATOR_STATE_H_