• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // Lightweight Quarantine (LQ) provides a low-cost quarantine mechanism with
6 // following characteristics.
7 //
8 // - Built on PartitionAlloc: only supports allocations in a known root
9 // - As fast as PA: LQ just defers `Free()` handling and may benefit from thread
10 //   cache etc.
11 // - Thread-safe
12 // - No allocation time information: triggered on `Free()`
13 // - Don't use quarantined objects' payload - available for zapping
14 // - Don't allocate heap memory.
15 // - Flexible to support several applications
16 //
17 // `LightweightQuarantineRoot` represents one quarantine system
18 // (e.g. scheduler loop quarantine).
19 // `LightweightQuarantineBranch` provides a quarantine request interface.
20 // It belongs to a `LightweightQuarantineRoot` and there can be multiple
21 // instances (e.g. one per thread). By having one branch per thread, it requires
22 // no lock for faster quarantine.
23 // ┌────────────────────────────┐
24 // │PartitionRoot               │
25 // └┬──────────────────────────┬┘
26 // ┌▽────────────────────────┐┌▽────────────────────┐
27 // │LQRoot 1                 ││LQRoot 2             │
28 // └┬───────────┬───────────┬┘└──────────────┬──┬──┬┘
29 // ┌▽─────────┐┌▽─────────┐┌▽─────────┐      ▽  ▽  ▽
30 // │LQBranch 1││LQBranch 2││LQBranch 3│
31 // └──────────┘└──────────┘└──────────┘
32 
33 #ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_LIGHTWEIGHT_QUARANTINE_H_
34 #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_LIGHTWEIGHT_QUARANTINE_H_
35 
36 #include <stdint.h>
37 #include <array>
38 #include <atomic>
39 #include <limits>
40 #include <type_traits>
41 
42 #include "partition_alloc/partition_alloc_base/export_template.h"
43 #include "partition_alloc/partition_alloc_base/rand_util.h"
44 #include "partition_alloc/partition_alloc_base/thread_annotations.h"
45 #include "partition_alloc/partition_lock.h"
46 #include "partition_alloc/partition_stats.h"
47 
48 namespace partition_alloc {
49 
50 struct PartitionRoot;
51 struct LightweightQuarantineStats;
52 
53 namespace internal {
54 
55 template <size_t QuarantineCapacityCount>
56 class LightweightQuarantineBranch;
57 
58 class LightweightQuarantineRoot {
59  public:
60   explicit LightweightQuarantineRoot(PartitionRoot& allocator_root,
61                                      size_t capacity_in_bytes = 0)
allocator_root_(allocator_root)62       : allocator_root_(allocator_root),
63         capacity_in_bytes_(capacity_in_bytes) {}
64 
65   template <size_t QuarantineCapacityCount>
66   LightweightQuarantineBranch<QuarantineCapacityCount> CreateBranch(
67       bool lock_required = true) {
68     return LightweightQuarantineBranch<QuarantineCapacityCount>(*this,
69                                                                 lock_required);
70   }
71 
AccumulateStats(LightweightQuarantineStats & stats)72   void AccumulateStats(LightweightQuarantineStats& stats) const {
73     stats.count += count_.load(std::memory_order_relaxed);
74     stats.size_in_bytes += size_in_bytes_.load(std::memory_order_relaxed);
75     stats.cumulative_count += cumulative_count_.load(std::memory_order_relaxed);
76     stats.cumulative_size_in_bytes +=
77         cumulative_size_in_bytes_.load(std::memory_order_relaxed);
78     stats.quarantine_miss_count +=
79         quarantine_miss_count_.load(std::memory_order_relaxed);
80   }
81 
GetCapacityInBytes()82   size_t GetCapacityInBytes() const {
83     return capacity_in_bytes_.load(std::memory_order_relaxed);
84   }
SetCapacityInBytesForTesting(size_t capacity)85   void SetCapacityInBytesForTesting(size_t capacity) {
86     capacity_in_bytes_.store(capacity, std::memory_order_relaxed);
87     // `size_in_bytes` may exceed `capacity_in_bytes` here.
88     // Each branch will try to shrink their quarantine later.
89   }
90 
91  private:
92   PartitionRoot& allocator_root_;
93   std::atomic_size_t capacity_in_bytes_;
94 
95   // Number of quarantined entries.
96   std::atomic_size_t count_ = 0;
97   // Total size of quarantined entries, capped by `capacity_in_bytes`.
98   std::atomic_size_t size_in_bytes_ = 0;
99 
100   // Stats.
101   std::atomic_size_t cumulative_count_ = 0;
102   std::atomic_size_t cumulative_size_in_bytes_ = 0;
103   std::atomic_size_t quarantine_miss_count_ = 0;
104 
105   template <size_t>
106   friend class LightweightQuarantineBranch;
107 };
108 
109 template <size_t QuarantineCapacityCount>
110 class LightweightQuarantineBranch {
111  public:
112   // `QuarantineCapacityCount` must be a positive number.
113   static constexpr uint32_t kQuarantineCapacityCount = QuarantineCapacityCount;
114   static_assert(0 < QuarantineCapacityCount);
115 
116   using Root = LightweightQuarantineRoot;
117 
118   LightweightQuarantineBranch(const LightweightQuarantineBranch&) = delete;
LightweightQuarantineBranch(LightweightQuarantineBranch && b)119   LightweightQuarantineBranch(LightweightQuarantineBranch&& b)
120       : root_(b.root_),
121         lock_required_(b.lock_required_),
122         slots_(std::move(b.slots_)),
123         branch_count_(b.branch_count_),
124         branch_size_in_bytes_(b.branch_size_in_bytes_) {}
~LightweightQuarantineBranch()125   ~LightweightQuarantineBranch() { Purge(); }
126 
127   // Quarantines an object. This list holds information you put into `entry`
128   // as much as possible.  If the object is too large, this may return
129   // `false`, meaning that quarantine request has failed (and freed
130   // immediately). Otherwise, returns `true`.
131   bool Quarantine(void* object);
132 
133   // Dequarantine all entries **held by this branch**.
134   // It is possible that another branch with entries and it remains untouched.
Purge()135   void Purge() {
136     ConditionalScopedGuard guard(lock_required_, lock_);
137     PurgeInternal(0, 0);
138   }
139 
140   // Determines this list contains an object.
IsQuarantinedForTesting(void * object)141   bool IsQuarantinedForTesting(void* object) {
142     ConditionalScopedGuard guard(lock_required_, lock_);
143     for (size_t i = 0; i < branch_count_; i++) {
144       if (slots_[i] == object) {
145         return true;
146       }
147     }
148     return false;
149   }
150 
GetRoot()151   Root& GetRoot() { return root_; }
152 
153  private:
LightweightQuarantineBranch(Root & root,bool lock_required)154   explicit LightweightQuarantineBranch(Root& root, bool lock_required)
155       : root_(root), lock_required_(lock_required) {}
156 
157   // Try to dequarantine entries to satisfy below:
158   //   branch_count_ <= target_branch_count
159   //   && root_.size_in_bytes_ <=  target_size_in_bytes
160   // It is possible that this branch cannot satisfy the
161   // request as it has control over only what it has. If you need to ensure the
162   // constraint, call `Purge()` for each branch in sequence, synchronously.
163   void PurgeInternal(size_t target_branch_count, size_t target_size_in_bytes)
164       PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
165 
166   Root& root_;
167 
168   bool lock_required_;
169   Lock lock_;
170 
171   // An utility to lock only if a condition is met.
172   class PA_SCOPED_LOCKABLE ConditionalScopedGuard {
173    public:
ConditionalScopedGuard(bool condition,Lock & lock)174     explicit ConditionalScopedGuard(bool condition, Lock& lock)
175         PA_EXCLUSIVE_LOCK_FUNCTION(lock)
176         : condition_(condition), lock_(lock) {
177       if (condition_) {
178         lock_.Acquire();
179       }
180     }
PA_UNLOCK_FUNCTION()181     ~ConditionalScopedGuard() PA_UNLOCK_FUNCTION() {
182       if (condition_) {
183         lock_.Release();
184       }
185     }
186 
187    private:
188     const bool condition_;
189     Lock& lock_;
190   };
191 
192   // Non-cryptographic random number generator.
193   // Thread-unsafe so guarded by `lock_`.
194   base::InsecureRandomGenerator random_ PA_GUARDED_BY(lock_);
195 
196   // `slots_` hold an array of quarantined entries.
197   // The contents of empty slots are undefined and reads should not occur.
198   // First `branch_count_` slots are used and entries should be shuffled.
199   std::array<void*, kQuarantineCapacityCount> slots_ PA_GUARDED_BY(lock_);
200 
201   // # of quarantined entries in this branch.
202   size_t branch_count_ PA_GUARDED_BY(lock_) = 0;
203   size_t branch_size_in_bytes_ PA_GUARDED_BY(lock_) = 0;
204 
205   friend class LightweightQuarantineRoot;
206 };
207 
208 #define EXPORT_TEMPLATE                             \
209   extern template class PA_EXPORT_TEMPLATE_DECLARE( \
210       PA_COMPONENT_EXPORT(PARTITION_ALLOC))
211 using LightweightQuarantineBranchForTesting = LightweightQuarantineBranch<1024>;
212 using SchedulerLoopQuarantineRoot = LightweightQuarantineRoot;
213 using SchedulerLoopQuarantineBranch = LightweightQuarantineBranch<1024>;
214 EXPORT_TEMPLATE LightweightQuarantineBranch<1024>;
215 #undef EXPORT_TEMPLATE
216 
217 }  // namespace internal
218 
219 }  // namespace partition_alloc
220 
221 #endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_LIGHTWEIGHT_QUARANTINE_H_
222