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