File: guarded_page_allocator.h

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (251 lines) | stat: -rw-r--r-- 10,206 bytes parent folder | download | duplicates (5)
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_