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 PARTITION_ALLOC_LIGHTWEIGHT_QUARANTINE_H_
34 #define PARTITION_ALLOC_LIGHTWEIGHT_QUARANTINE_H_
35
36 #include <array>
37 #include <atomic>
38 #include <cstdint>
39 #include <limits>
40 #include <memory>
41 #include <type_traits>
42 #include <vector>
43
44 #include "partition_alloc/internal_allocator_forward.h"
45 #include "partition_alloc/partition_alloc_base/component_export.h"
46 #include "partition_alloc/partition_alloc_base/rand_util.h"
47 #include "partition_alloc/partition_alloc_base/thread_annotations.h"
48 #include "partition_alloc/partition_alloc_forward.h"
49 #include "partition_alloc/partition_lock.h"
50 #include "partition_alloc/partition_stats.h"
51
52 namespace partition_alloc {
53
54 struct PartitionRoot;
55 struct LightweightQuarantineStats;
56
57 namespace internal {
58
59 class LightweightQuarantineBranch;
60
PA_COMPONENT_EXPORT(PARTITION_ALLOC)61 class PA_COMPONENT_EXPORT(PARTITION_ALLOC) LightweightQuarantineRoot {
62 public:
63 explicit LightweightQuarantineRoot(PartitionRoot& allocator_root,
64 size_t capacity_in_bytes = 0)
65 : allocator_root_(allocator_root),
66 capacity_in_bytes_(capacity_in_bytes) {}
67
68 LightweightQuarantineBranch CreateBranch(bool lock_required = true);
69
70 void AccumulateStats(LightweightQuarantineStats& stats) const {
71 stats.count += count_.load(std::memory_order_relaxed);
72 stats.size_in_bytes += size_in_bytes_.load(std::memory_order_relaxed);
73 stats.cumulative_count += cumulative_count_.load(std::memory_order_relaxed);
74 stats.cumulative_size_in_bytes +=
75 cumulative_size_in_bytes_.load(std::memory_order_relaxed);
76 stats.quarantine_miss_count +=
77 quarantine_miss_count_.load(std::memory_order_relaxed);
78 }
79
80 size_t GetCapacityInBytes() const {
81 return capacity_in_bytes_.load(std::memory_order_relaxed);
82 }
83 void SetCapacityInBytes(size_t capacity) {
84 capacity_in_bytes_.store(capacity, std::memory_order_relaxed);
85 // `size_in_bytes` may exceed `capacity_in_bytes` here.
86 // Each branch will try to shrink their quarantine later.
87 }
88
89 private:
90 PartitionRoot& allocator_root_;
91 std::atomic_size_t capacity_in_bytes_;
92 // Total size of quarantined entries, capped by `capacity_in_bytes`.
93 std::atomic_size_t size_in_bytes_ = 0;
94
95 // Stats.
96 std::atomic_size_t count_ = 0; // Number of quarantined entries
97 std::atomic_size_t cumulative_count_ = 0;
98 std::atomic_size_t cumulative_size_in_bytes_ = 0;
99 std::atomic_size_t quarantine_miss_count_ = 0;
100
101 friend class LightweightQuarantineBranch;
102 };
103
PA_COMPONENT_EXPORT(PARTITION_ALLOC)104 class PA_COMPONENT_EXPORT(PARTITION_ALLOC) LightweightQuarantineBranch {
105 public:
106 using Root = LightweightQuarantineRoot;
107
108 LightweightQuarantineBranch(const LightweightQuarantineBranch&) = delete;
109 LightweightQuarantineBranch(LightweightQuarantineBranch&& b);
110 ~LightweightQuarantineBranch();
111
112 // Quarantines an object. This list holds information you put into `entry`
113 // as much as possible. If the object is too large, this may return
114 // `false`, meaning that quarantine request has failed (and freed
115 // immediately). Otherwise, returns `true`.
116 bool Quarantine(void* object,
117 SlotSpanMetadata* slot_span,
118 uintptr_t slot_start);
119
120 // Dequarantine all entries **held by this branch**.
121 // It is possible that another branch with entries and it remains untouched.
122 void Purge() {
123 ConditionalScopedGuard guard(lock_required_, lock_);
124 PurgeInternal(0);
125 }
126
127 // Determines this list contains an object.
128 bool IsQuarantinedForTesting(void* object);
129
130 Root& GetRoot() { return root_; }
131
132 private:
133 LightweightQuarantineBranch(Root& root, bool lock_required);
134
135 // Try to dequarantine entries to satisfy below:
136 // root_.size_in_bytes_ <= target_size_in_bytes
137 // It is possible that this branch cannot satisfy the
138 // request as it has control over only what it has. If you need to ensure the
139 // constraint, call `Purge()` for each branch in sequence, synchronously.
140 void PurgeInternal(size_t target_size_in_bytes)
141 PA_EXCLUSIVE_LOCKS_REQUIRED(lock_);
142
143 Root& root_;
144
145 bool lock_required_;
146 Lock lock_;
147
148 // An utility to lock only if a condition is met.
149 class PA_SCOPED_LOCKABLE ConditionalScopedGuard {
150 public:
151 explicit ConditionalScopedGuard(bool condition, Lock& lock)
152 PA_EXCLUSIVE_LOCK_FUNCTION(lock)
153 : condition_(condition), lock_(lock) {
154 if (condition_) {
155 lock_.Acquire();
156 }
157 }
158 ~ConditionalScopedGuard() PA_UNLOCK_FUNCTION() {
159 if (condition_) {
160 lock_.Release();
161 }
162 }
163
164 private:
165 const bool condition_;
166 Lock& lock_;
167 };
168
169 // Non-cryptographic random number generator.
170 // Thread-unsafe so guarded by `lock_`.
171 base::InsecureRandomGenerator random_ PA_GUARDED_BY(lock_);
172
173 // `slots_` hold quarantined entries.
174 struct QuarantineSlot {
175 uintptr_t slot_start;
176 size_t usable_size;
177 };
178 std::vector<QuarantineSlot, InternalAllocator<QuarantineSlot>> slots_
179 PA_GUARDED_BY(lock_);
180 size_t branch_size_in_bytes_ PA_GUARDED_BY(lock_) = 0;
181
182 friend class LightweightQuarantineRoot;
183 };
184
185 } // namespace internal
186
187 } // namespace partition_alloc
188
189 #endif // PARTITION_ALLOC_LIGHTWEIGHT_QUARANTINE_H_
190