1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
|
// 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.
#ifndef COMPONENTS_GWP_ASAN_CLIENT_GUARDED_PAGE_ALLOCATOR_H_
#define COMPONENTS_GWP_ASAN_CLIENT_GUARDED_PAGE_ALLOCATOR_H_
#include <atomic>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/compiler_specific.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "build/build_config.h"
#include "components/gwp_asan/client/export.h"
#include "components/gwp_asan/client/gwp_asan.h"
#include "components/gwp_asan/common/allocator_state.h"
namespace gwp_asan {
namespace internal {
// This class encompasses the allocation and deallocation logic on top of the
// AllocatorState. Its members are not inspected or used by the crash handler.
//
// This class makes use of dynamically-sized arrays like std::vector<> to only
// allocate as much memory as we need; however, they only reserve memory at
// initialization-time so there is no risk of malloc reentrancy.
class GWP_ASAN_EXPORT GuardedPageAllocator {
public:
// Number of consecutive allocations that fail due to lack of available pages
// before we call the OOM callback.
static constexpr size_t kOutOfMemoryCount = 100;
// Default maximum alignment for all returned allocations.
static constexpr size_t kGpaAllocAlignment = 16;
// Callback used to report the allocator running out of memory, reports the
// number of successful allocations before running out of memory.
using OutOfMemoryCallback = base::OnceCallback<void(size_t)>;
// Does not allocate any memory for the allocator, to finish initializing call
// Init().
GuardedPageAllocator();
GuardedPageAllocator(const GuardedPageAllocator&) = delete;
GuardedPageAllocator& operator=(const GuardedPageAllocator&) = delete;
// Configures this allocator to allocate up to `settings.max_alloced_pages`
// pages at a time, holding metadata for up to `settings.num_metadata`
// allocations, from a pool of `settings.total_pages` pages, where:
// 1 <= max_alloced_pages <= num_metadata <= kMaxMetadata
// num_metadata <= total_pages <= kMaxSlots
//
// The OOM callback is called the first time the allocator fails to allocate
// kOutOfMemoryCount allocations consecutively due to lack of memory.
[[nodiscard]] bool Init(const AllocatorSettings& settings,
OutOfMemoryCallback oom_callback,
bool is_partition_alloc);
// On success, returns a pointer to size bytes of page-guarded memory. On
// failure, returns nullptr. The allocation is not guaranteed to be
// zero-filled. Failure can occur if memory could not be mapped or protected,
// or if all guarded pages are already allocated.
//
// 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 will default to the default alignment the allocator chooses.
//
// The type parameter should only be set for PartitionAlloc allocations.
//
// Preconditions: Init() must have been called.
void* Allocate(size_t size, size_t align = 0, const char* type = nullptr);
// Deallocates memory pointed to by ptr. ptr must have been previously
// returned by a call to Allocate.
void Deallocate(void* ptr);
// Returns the size requested when ptr was allocated. ptr must have been
// previously returned by a call to Allocate, and not have been deallocated.
size_t GetRequestedSize(const void* ptr) const;
// Retrieves the textual address of the shared allocator state required by the
// crash handler.
std::string GetCrashKey() const;
// Returns internal memory used by the allocator (required for sanitization
// on supported platforms.)
std::vector<std::pair<void*, size_t>> GetInternalMemoryRegions();
// Returns true if ptr points to memory managed by this class.
inline bool PointerIsMine(const void* ptr) const {
return state_.PointerIsMine(reinterpret_cast<uintptr_t>(ptr));
}
void DestructForTesting();
private:
// Virtual base class representing a free list of entries T.
template <typename T>
class FreeList {
public:
FreeList() = default;
virtual ~FreeList() = default;
virtual void Initialize(T max_entries) = 0;
virtual void Initialize(T max_entries, std::vector<T>&& free_list) = 0;
virtual bool Allocate(T* out, const char* type) = 0;
virtual void Free(T entry) = 0;
};
// Manages a free list of slot or metadata indices in the range
// [0, max_entries). Access to SimpleFreeList objects must be synchronized.
//
// SimpleFreeList is specifically designed to pre-allocate data in Initialize
// so that it never recurses into malloc/free during Allocate/Free.
template <typename T>
class SimpleFreeList final : public FreeList<T> {
public:
~SimpleFreeList() final = default;
void Initialize(T max_entries) final;
void Initialize(T max_entries, std::vector<T>&& free_list) final;
bool Allocate(T* out, const char* type) final;
void Free(T entry) final;
private:
std::vector<T> free_list_;
// Number of used entries. This counter ensures all free entries are used
// before starting to use random eviction.
T num_used_entries_ = 0;
T max_entries_ = 0;
};
// Manages a free list of slot indices especially for PartitionAlloc.
// Allocate() is type-aware so that once a page has been used to allocate
// a given partition, it never reallocates an object of a different type on
// that page. Access to this object must be synchronized.
//
// PartitionAllocSlotFreeList can perform malloc/free during Allocate/Free,
// so it is not safe to use with malloc hooks!
//
// TODO(vtsyrklevich): Right now we allocate slots to partitions on a
// first-come first-serve basis, this makes it likely that all slots will be
// used up by common types first. Set aside a fixed amount of slots (~5%) for
// one-off partitions so that we make sure to sample rare types as well.
class PartitionAllocSlotFreeList final
: public FreeList<AllocatorState::SlotIdx> {
public:
PartitionAllocSlotFreeList();
~PartitionAllocSlotFreeList() final;
void Initialize(AllocatorState::SlotIdx max_entries) final;
void Initialize(AllocatorState::SlotIdx max_entries,
std::vector<AllocatorState::SlotIdx>&& free_list) final;
bool Allocate(AllocatorState::SlotIdx* out, const char* type) final;
void Free(AllocatorState::SlotIdx entry) final;
private:
std::vector<const char*> type_mapping_;
std::map<const char*, std::vector<AllocatorState::SlotIdx>> free_list_;
std::vector<AllocatorState::SlotIdx> initial_free_list_;
// Number of used entries. This counter ensures all free entries are used
// before starting to use random eviction.
AllocatorState::SlotIdx num_used_entries_ = 0;
AllocatorState::SlotIdx max_entries_ = 0;
};
// Unmaps memory allocated by this class, if Init was called.
~GuardedPageAllocator();
// Allocates/deallocates the virtual memory used for allocations.
void* MapRegion();
void UnmapRegion();
// Provide a hint for MapRegion() on where to place the GWP-ASan region.
void* MapRegionHint() const;
// Returns the size of the virtual memory region used to store allocations.
size_t RegionSize() const;
// Mark page read-write.
void MarkPageReadWrite(void*);
// Mark page inaccessible and decommit the memory from use to save memory
// used by the quarantine.
void MarkPageInaccessible(void*);
// On success, returns true and writes the reserved indices to |slot| and
// |metadata_idx|. Otherwise returns false if no allocations are available.
bool ReserveSlotAndMetadata(AllocatorState::SlotIdx* slot,
AllocatorState::MetadataIdx* metadata_idx,
const char* type) LOCKS_EXCLUDED(lock_);
// Marks the specified slot and metadata as unreserved.
void FreeSlotAndMetadata(AllocatorState::SlotIdx slot,
AllocatorState::MetadataIdx metadata_idx)
LOCKS_EXCLUDED(lock_);
// Record the metadata for an allocation or deallocation for a given metadata
// index.
ALWAYS_INLINE
void RecordAllocationMetadata(AllocatorState::MetadataIdx metadata_idx,
size_t size,
void* ptr);
ALWAYS_INLINE void RecordDeallocationMetadata(
AllocatorState::MetadataIdx metadata_idx);
// Allocator state shared with with the crash analyzer.
AllocatorState state_;
// Lock that synchronizes allocating/freeing slots between threads.
base::Lock lock_;
std::unique_ptr<FreeList<AllocatorState::SlotIdx>> free_slots_
GUARDED_BY(lock_);
SimpleFreeList<AllocatorState::MetadataIdx> free_metadata_ GUARDED_BY(lock_);
// Number of currently-allocated pages.
size_t num_alloced_pages_ GUARDED_BY(lock_) = 0;
// Max number of concurrent allocations.
size_t max_alloced_pages_ = 0;
// Array of metadata (e.g. stack traces) for allocations.
// TODO(vtsyrklevich): Use an std::vector<> here as well.
std::unique_ptr<AllocatorState::SlotMetadata[]> metadata_;
// Maps a slot index to a metadata index (or kInvalidMetadataIdx if no such
// mapping exists.)
std::vector<AllocatorState::MetadataIdx> slot_to_metadata_idx_;
// Maintain a count of total allocations and consecutive failed allocations
// to report allocator OOM.
size_t total_allocations_ GUARDED_BY(lock_) = 0;
size_t consecutive_oom_hits_ GUARDED_BY(lock_) = 0;
bool oom_hit_ GUARDED_BY(lock_) = false;
OutOfMemoryCallback oom_callback_;
bool is_partition_alloc_ = false;
friend class BaseGpaTest;
friend class BaseCrashAnalyzerTest;
FRIEND_TEST_ALL_PREFIXES(CrashAnalyzerTest, InternalError);
FRIEND_TEST_ALL_PREFIXES(CrashAnalyzerTest, StackTraceCollection);
};
} // namespace internal
} // namespace gwp_asan
#endif // COMPONENTS_GWP_ASAN_CLIENT_GUARDED_PAGE_ALLOCATOR_H_
|