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