blob: 502b1b75fce7713d3f23b3a052a1f33bc9375ed3 [file] [log] [blame]
// 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.
#ifndef COMPONENTS_GWP_ASAN_CLIENT_GUARDED_PAGE_ALLOCATOR_H_
#define COMPONENTS_GWP_ASAN_CLIENT_GUARDED_PAGE_ALLOCATOR_H_
#include <atomic>
#include <memory>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/gtest_prod_util.h"
#include "base/no_destructor.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "components/gwp_asan/client/export.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();
// Configures this allocator to allocate up to max_alloced_pages pages at a
// time, holding metadata for up to num_metadata allocations, from a pool of
// 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.
void Init(size_t max_alloced_pages,
size_t num_metadata,
size_t total_pages,
OutOfMemoryCallback oom_callback);
// 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.
//
// Preconditions: Init() must have been called.
void* Allocate(size_t size, size_t align = 0);
// 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 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));
}
private:
// 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 {
public:
void Initialize(T max_entries);
T Allocate();
void Free(T entry);
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;
};
// Unmaps memory allocated by this class, if Init was called.
~GuardedPageAllocator();
// Allocates/deallocates the virtual memory used for allocations.
void* MapRegion();
void UnmapRegion();
// 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)
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_;
SimpleFreeList<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_failed_allocations_ GUARDED_BY(lock_) = 0;
bool oom_hit_ GUARDED_BY(lock_) = false;
OutOfMemoryCallback oom_callback_;
// Required for a singleton to access the constructor.
friend base::NoDestructor<GuardedPageAllocator>;
friend class CrashAnalyzerTest;
friend class GuardedPageAllocatorTest;
FRIEND_TEST_ALL_PREFIXES(CrashAnalyzerTest, InternalError);
FRIEND_TEST_ALL_PREFIXES(CrashAnalyzerTest, StackTraceCollection);
FRIEND_TEST_ALL_PREFIXES(GuardedPageAllocatorTest,
GetNearestValidPageEdgeCases);
FRIEND_TEST_ALL_PREFIXES(GuardedPageAllocatorTest, GetErrorTypeEdgeCases);
DISALLOW_COPY_AND_ASSIGN(GuardedPageAllocator);
};
} // namespace internal
} // namespace gwp_asan
#endif // COMPONENTS_GWP_ASAN_CLIENT_GUARDED_PAGE_ALLOCATOR_H_