xref: /aosp_15_r20/external/cronet/base/allocator/partition_allocator/src/partition_alloc/partition_root.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2020 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 #ifndef PARTITION_ALLOC_PARTITION_ROOT_H_
6 #define PARTITION_ALLOC_PARTITION_ROOT_H_
7 
8 // DESCRIPTION
9 // PartitionRoot::Alloc() and PartitionRoot::Free() are approximately analogous
10 // to malloc() and free().
11 //
12 // The main difference is that a PartitionRoot object must be supplied to these
13 // functions, representing a specific "heap partition" that will be used to
14 // satisfy the allocation. Different partitions are guaranteed to exist in
15 // separate address spaces, including being separate from the main system
16 // heap. If the contained objects are all freed, physical memory is returned to
17 // the system but the address space remains reserved.  See PartitionAlloc.md for
18 // other security properties PartitionAlloc provides.
19 //
20 // THE ONLY LEGITIMATE WAY TO OBTAIN A PartitionRoot IS THROUGH THE
21 // PartitionAllocator classes. To minimize the instruction count to the fullest
22 // extent possible, the PartitionRoot is really just a header adjacent to other
23 // data areas provided by the allocator class.
24 //
25 // The constraints for PartitionRoot::Alloc() are:
26 // - Multi-threaded use against a single partition is ok; locking is handled.
27 // - Allocations of any arbitrary size can be handled (subject to a limit of
28 //   INT_MAX bytes for security reasons).
29 // - Bucketing is by approximate size, for example an allocation of 4000 bytes
30 //   might be placed into a 4096-byte bucket. Bucket sizes are chosen to try and
31 //   keep worst-case waste to ~10%.
32 
33 #include <algorithm>
34 #include <atomic>
35 #include <bit>
36 #include <cstddef>
37 #include <cstdint>
38 #include <limits>
39 #include <optional>
40 #include <utility>
41 
42 #include "build/build_config.h"
43 #include "partition_alloc/address_pool_manager_types.h"
44 #include "partition_alloc/allocation_guard.h"
45 #include "partition_alloc/chromecast_buildflags.h"
46 #include "partition_alloc/freeslot_bitmap.h"
47 #include "partition_alloc/in_slot_metadata.h"
48 #include "partition_alloc/lightweight_quarantine.h"
49 #include "partition_alloc/page_allocator.h"
50 #include "partition_alloc/partition_address_space.h"
51 #include "partition_alloc/partition_alloc-inl.h"
52 #include "partition_alloc/partition_alloc_allocation_data.h"
53 #include "partition_alloc/partition_alloc_base/bits.h"
54 #include "partition_alloc/partition_alloc_base/compiler_specific.h"
55 #include "partition_alloc/partition_alloc_base/component_export.h"
56 #include "partition_alloc/partition_alloc_base/debug/debugging_buildflags.h"
57 #include "partition_alloc/partition_alloc_base/export_template.h"
58 #include "partition_alloc/partition_alloc_base/no_destructor.h"
59 #include "partition_alloc/partition_alloc_base/notreached.h"
60 #include "partition_alloc/partition_alloc_base/thread_annotations.h"
61 #include "partition_alloc/partition_alloc_base/time/time.h"
62 #include "partition_alloc/partition_alloc_buildflags.h"
63 #include "partition_alloc/partition_alloc_check.h"
64 #include "partition_alloc/partition_alloc_config.h"
65 #include "partition_alloc/partition_alloc_constants.h"
66 #include "partition_alloc/partition_alloc_forward.h"
67 #include "partition_alloc/partition_alloc_hooks.h"
68 #include "partition_alloc/partition_bucket.h"
69 #include "partition_alloc/partition_bucket_lookup.h"
70 #include "partition_alloc/partition_cookie.h"
71 #include "partition_alloc/partition_direct_map_extent.h"
72 #include "partition_alloc/partition_freelist_entry.h"
73 #include "partition_alloc/partition_lock.h"
74 #include "partition_alloc/partition_oom.h"
75 #include "partition_alloc/partition_page.h"
76 #include "partition_alloc/reservation_offset_table.h"
77 #include "partition_alloc/tagging.h"
78 #include "partition_alloc/thread_cache.h"
79 #include "partition_alloc/thread_isolation/thread_isolation.h"
80 
81 #if BUILDFLAG(USE_STARSCAN)
82 #include "partition_alloc/starscan/pcscan.h"
83 #endif
84 
85 namespace partition_alloc::internal {
86 
87 // We want this size to be big enough that we have time to start up other
88 // scripts _before_ we wrap around.
89 static constexpr size_t kAllocInfoSize = 1 << 24;
90 
91 struct AllocInfo {
92   std::atomic<size_t> index{0};
93   struct {
94     uintptr_t addr;
95     size_t size;
96   } allocs[kAllocInfoSize] = {};
97 };
98 
99 #if BUILDFLAG(RECORD_ALLOC_INFO)
100 extern AllocInfo g_allocs;
101 
102 void RecordAllocOrFree(uintptr_t addr, size_t size);
103 #endif  // BUILDFLAG(RECORD_ALLOC_INFO)
104 }  // namespace partition_alloc::internal
105 
106 namespace partition_alloc {
107 
108 namespace internal {
109 // Avoid including partition_address_space.h from this .h file, by moving the
110 // call to IsManagedByPartitionAllocBRPPool into the .cc file.
111 #if BUILDFLAG(PA_DCHECK_IS_ON)
112 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
113 void DCheckIfManagedByPartitionAllocBRPPool(uintptr_t address);
114 #else
115 PA_ALWAYS_INLINE void DCheckIfManagedByPartitionAllocBRPPool(
116     uintptr_t address) {}
117 #endif
118 
119 #if PA_CONFIG(USE_PARTITION_ROOT_ENUMERATOR)
120 class PartitionRootEnumerator;
121 #endif
122 
123 }  // namespace internal
124 
125 // Bit flag constants used to purge memory.  See PartitionRoot::PurgeMemory.
126 //
127 // In order to support bit operations like `flag_a | flag_b`, the old-fashioned
128 // enum (+ surrounding named struct) is used instead of enum class.
129 struct PurgeFlags {
130   enum : int {
131     // Decommitting the ring list of empty slot spans is reasonably fast.
132     kDecommitEmptySlotSpans = 1 << 0,
133     // Discarding unused system pages is slower, because it involves walking all
134     // freelists in all active slot spans of all buckets >= system page
135     // size. It often frees a similar amount of memory to decommitting the empty
136     // slot spans, though.
137     kDiscardUnusedSystemPages = 1 << 1,
138     // Aggressively reclaim memory. This is meant to be used in low-memory
139     // situations, not for periodic memory reclaiming.
140     kAggressiveReclaim = 1 << 2,
141   };
142 };
143 
144 // Options struct used to configure PartitionRoot and PartitionAllocator.
145 struct PartitionOptions {
146   // Marked inline so that the chromium style plugin doesn't complain that a
147   // "complex constructor" has an inline body. This warning is disabled when
148   // the constructor is explicitly marked "inline". Note that this is a false
149   // positive of the plugin, since constexpr implies inline.
150   inline constexpr PartitionOptions();
151   inline constexpr PartitionOptions(const PartitionOptions& other);
152   inline constexpr ~PartitionOptions();
153 
154   enum class AllowToggle : uint8_t {
155     kDisallowed,
156     kAllowed,
157   };
158   enum class EnableToggle : uint8_t {
159     kDisabled,
160     kEnabled,
161   };
162 
163   // Expose the enum arms directly at the level of `PartitionOptions`,
164   // since the variant names are already sufficiently descriptive.
165   static constexpr auto kAllowed = AllowToggle::kAllowed;
166   static constexpr auto kDisallowed = AllowToggle::kDisallowed;
167   static constexpr auto kDisabled = EnableToggle::kDisabled;
168   static constexpr auto kEnabled = EnableToggle::kEnabled;
169 
170   EnableToggle thread_cache = kDisabled;
171   AllowToggle star_scan_quarantine = kDisallowed;
172   EnableToggle backup_ref_ptr = kDisabled;
173   AllowToggle use_configurable_pool = kDisallowed;
174 
175   EnableToggle scheduler_loop_quarantine = kDisabled;
176   size_t scheduler_loop_quarantine_capacity_in_bytes = 0;
177 
178   EnableToggle zapping_by_free_flags = kDisabled;
179 
180   struct {
181     EnableToggle enabled = kDisabled;
182     TagViolationReportingMode reporting_mode =
183         TagViolationReportingMode::kUndefined;
184   } memory_tagging;
185 #if BUILDFLAG(ENABLE_THREAD_ISOLATION)
186   ThreadIsolationOption thread_isolation;
187 #endif
188 
189   EnableToggle use_pool_offset_freelists = kDisabled;
190 };
191 
192 constexpr PartitionOptions::PartitionOptions() = default;
193 constexpr PartitionOptions::PartitionOptions(const PartitionOptions& other) =
194     default;
195 constexpr PartitionOptions::~PartitionOptions() = default;
196 
197 // When/if free lists should be "straightened" when calling
198 // PartitionRoot::PurgeMemory(..., accounting_only=false).
199 enum class StraightenLargerSlotSpanFreeListsMode {
200   kNever,
201   kOnlyWhenUnprovisioning,
202   kAlways,
203 };
204 
205 // Never instantiate a PartitionRoot directly, instead use
206 // PartitionAllocator.
PA_COMPONENT_EXPORT(PARTITION_ALLOC)207 struct PA_ALIGNAS(64) PA_COMPONENT_EXPORT(PARTITION_ALLOC) PartitionRoot {
208   using SlotSpanMetadata = internal::SlotSpanMetadata;
209   using Bucket = internal::PartitionBucket;
210   using FreeListEntry = internal::PartitionFreelistEntry;
211   using SuperPageExtentEntry = internal::PartitionSuperPageExtentEntry;
212   using DirectMapExtent = internal::PartitionDirectMapExtent;
213 #if BUILDFLAG(USE_STARSCAN)
214   using PCScan = internal::PCScan;
215 #endif
216 
217   enum class QuarantineMode : uint8_t {
218     kAlwaysDisabled,
219     kDisabledByDefault,
220     kEnabled,
221   };
222 
223   enum class ScanMode : uint8_t {
224     kDisabled,
225     kEnabled,
226   };
227 
228   enum class BucketDistribution : uint8_t { kNeutral, kDenser };
229 
230   // Root settings accessed on fast paths.
231   //
232   // Careful! PartitionAlloc's performance is sensitive to its layout.  Please
233   // put the fast-path objects in the struct below.
234   struct alignas(internal::kPartitionCachelineSize) Settings {
235     // Chromium-style: Complex constructor needs an explicit out-of-line
236     // constructor.
237     Settings();
238 
239     // Defines whether objects should be quarantined for this root.
240     QuarantineMode quarantine_mode = QuarantineMode::kAlwaysDisabled;
241 
242     // Defines whether the root should be scanned.
243     ScanMode scan_mode = ScanMode::kDisabled;
244 
245     // It's important to default to the 'neutral' distribution, otherwise a
246     // switch from 'dense' -> 'neutral' would leave some buckets with dirty
247     // memory forever, since no memory would be allocated from these, their
248     // freelist would typically not be empty, making these unreclaimable.
249     BucketDistribution bucket_distribution = BucketDistribution::kNeutral;
250 
251     bool with_thread_cache = false;
252 
253 #if BUILDFLAG(PA_DCHECK_IS_ON)
254     bool use_cookie = false;
255 #else
256     static constexpr bool use_cookie = false;
257 #endif  // BUILDFLAG(PA_DCHECK_IS_ON)
258 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
259     bool brp_enabled_ = false;
260 #if PA_CONFIG(MAYBE_ENABLE_MAC11_MALLOC_SIZE_HACK)
261     bool mac11_malloc_size_hack_enabled_ = false;
262     size_t mac11_malloc_size_hack_usable_size_ = 0;
263 #endif  // PA_CONFIG(MAYBE_ENABLE_MAC11_MALLOC_SIZE_HACK)
264     size_t in_slot_metadata_size = 0;
265 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
266     bool use_configurable_pool = false;
267     bool zapping_by_free_flags = false;
268     bool scheduler_loop_quarantine = false;
269 #if BUILDFLAG(HAS_MEMORY_TAGGING)
270     bool memory_tagging_enabled_ = false;
271     TagViolationReportingMode memory_tagging_reporting_mode_ =
272         TagViolationReportingMode::kUndefined;
273 #endif  // BUILDFLAG(HAS_MEMORY_TAGGING)
274 #if BUILDFLAG(ENABLE_THREAD_ISOLATION)
275     ThreadIsolationOption thread_isolation;
276 #endif
277 
278     bool use_pool_offset_freelists = false;
279 
280 #if PA_CONFIG(EXTRAS_REQUIRED)
281     uint32_t extras_size = 0;
282 #else
283     // Teach the compiler that code can be optimized in builds that use no
284     // extras.
285     static inline constexpr uint32_t extras_size = 0;
286 #endif  // PA_CONFIG(EXTRAS_REQUIRED)
287   };
288 
289   Settings settings;
290 
291   // Not used on the fastest path (thread cache allocations), but on the fast
292   // path of the central allocator.
293   alignas(internal::kPartitionCachelineSize) internal::Lock lock_;
294 
295   Bucket buckets[internal::kNumBuckets] = {};
296   Bucket sentinel_bucket{};
297 
298   // All fields below this comment are not accessed on the fast path.
299   bool initialized = false;
300 
301   // Bookkeeping.
302   // - total_size_of_super_pages - total virtual address space for normal bucket
303   //     super pages
304   // - total_size_of_direct_mapped_pages - total virtual address space for
305   //     direct-map regions
306   // - total_size_of_committed_pages - total committed pages for slots (doesn't
307   //     include metadata, bitmaps (if any), or any data outside or regions
308   //     described in #1 and #2)
309   // Invariant: total_size_of_allocated_bytes <=
310   //            total_size_of_committed_pages <
311   //                total_size_of_super_pages +
312   //                total_size_of_direct_mapped_pages.
313   // Invariant: total_size_of_committed_pages <= max_size_of_committed_pages.
314   // Invariant: total_size_of_allocated_bytes <= max_size_of_allocated_bytes.
315   // Invariant: max_size_of_allocated_bytes <= max_size_of_committed_pages.
316   // Since all operations on the atomic variables have relaxed semantics, we
317   // don't check these invariants with DCHECKs.
318   std::atomic<size_t> total_size_of_committed_pages{0};
319   std::atomic<size_t> max_size_of_committed_pages{0};
320   std::atomic<size_t> total_size_of_super_pages{0};
321   std::atomic<size_t> total_size_of_direct_mapped_pages{0};
322   size_t total_size_of_allocated_bytes
323       PA_GUARDED_BY(internal::PartitionRootLock(this)) = 0;
324   size_t max_size_of_allocated_bytes
325       PA_GUARDED_BY(internal::PartitionRootLock(this)) = 0;
326   // Atomic, because system calls can be made without the lock held.
327   std::atomic<uint64_t> syscall_count{};
328   std::atomic<uint64_t> syscall_total_time_ns{};
329 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
330   std::atomic<size_t> total_size_of_brp_quarantined_bytes{0};
331   std::atomic<size_t> total_count_of_brp_quarantined_slots{0};
332   std::atomic<size_t> cumulative_size_of_brp_quarantined_bytes{0};
333   std::atomic<size_t> cumulative_count_of_brp_quarantined_slots{0};
334 #endif
335   // Slot span memory which has been provisioned, and is currently unused as
336   // it's part of an empty SlotSpan. This is not clean memory, since it has
337   // either been used for a memory allocation, and/or contains freelist
338   // entries. But it might have been moved to swap. Note that all this memory
339   // can be decommitted at any time.
340   size_t empty_slot_spans_dirty_bytes
341       PA_GUARDED_BY(internal::PartitionRootLock(this)) = 0;
342 
343   // Only tolerate up to |total_size_of_committed_pages >>
344   // max_empty_slot_spans_dirty_bytes_shift| dirty bytes in empty slot
345   // spans. That is, the default value of 3 tolerates up to 1/8. Since
346   // |empty_slot_spans_dirty_bytes| is never strictly larger than
347   // total_size_of_committed_pages, setting this to 0 removes the cap. This is
348   // useful to make tests deterministic and easier to reason about.
349   int max_empty_slot_spans_dirty_bytes_shift = 3;
350 
351   uintptr_t next_super_page = 0;
352   uintptr_t next_partition_page = 0;
353   uintptr_t next_partition_page_end = 0;
354   SuperPageExtentEntry* current_extent = nullptr;
355   SuperPageExtentEntry* first_extent = nullptr;
356   DirectMapExtent* direct_map_list
357       PA_GUARDED_BY(internal::PartitionRootLock(this)) = nullptr;
358   SlotSpanMetadata*
359       global_empty_slot_span_ring[internal::kMaxFreeableSpans] PA_GUARDED_BY(
360           internal::PartitionRootLock(this)) = {};
361   int16_t global_empty_slot_span_ring_index
362       PA_GUARDED_BY(internal::PartitionRootLock(this)) = 0;
363   int16_t global_empty_slot_span_ring_size
364       PA_GUARDED_BY(internal::PartitionRootLock(this)) =
365           internal::kDefaultEmptySlotSpanRingSize;
366 
367   // Integrity check = ~reinterpret_cast<uintptr_t>(this).
368   uintptr_t inverted_self = 0;
369   std::atomic<int> thread_caches_being_constructed_{0};
370 
371   bool quarantine_always_for_testing = false;
372 
373   size_t scheduler_loop_quarantine_capacity_in_bytes = 0;
374   internal::LightweightQuarantineRoot scheduler_loop_quarantine_root;
375   // NoDestructor because we don't need to dequarantine objects as the root
376   // associated with it is dying anyway.
377   std::optional<
378       internal::base::NoDestructor<internal::LightweightQuarantineBranch>>
379       scheduler_loop_quarantine;
380 
381   PartitionRoot();
382   explicit PartitionRoot(PartitionOptions opts);
383 
384   // TODO(tasak): remove ~PartitionRoot() after confirming all tests
385   // don't need ~PartitionRoot().
386   ~PartitionRoot();
387 
388   // This will unreserve any space in the pool that the PartitionRoot is
389   // using. This is needed because many tests create and destroy many
390   // PartitionRoots over the lifetime of a process, which can exhaust the
391   // pool and cause tests to fail.
392   void DestructForTesting();
393 
394   void DecommitEmptySlotSpansForTesting();
395 
396 #if PA_CONFIG(MAYBE_ENABLE_MAC11_MALLOC_SIZE_HACK)
397   void EnableMac11MallocSizeHackIfNeeded();
398   void EnableMac11MallocSizeHackForTesting();
399   void InitMac11MallocSizeHackUsableSize();
400 #endif  // PA_CONFIG(MAYBE_ENABLE_MAC11_MALLOC_SIZE_HACK)
401 
402   // Public API
403   //
404   // Allocates out of the given bucket. Properly, this function should probably
405   // be in PartitionBucket, but because the implementation needs to be inlined
406   // for performance, and because it needs to inspect SlotSpanMetadata,
407   // it becomes impossible to have it in PartitionBucket as this causes a
408   // cyclical dependency on SlotSpanMetadata function implementations.
409   //
410   // Moving it a layer lower couples PartitionRoot and PartitionBucket, but
411   // preserves the layering of the includes.
412   void Init(PartitionOptions);
413 
414   void EnableThreadCacheIfSupported();
415 
416   PA_ALWAYS_INLINE static PartitionRoot* FromSlotSpanMetadata(
417       SlotSpanMetadata* slot_span);
418   // These two functions work unconditionally for normal buckets.
419   // For direct map, they only work for the first super page of a reservation,
420   // (see partition_alloc_constants.h for the direct map allocation layout).
421   // In particular, the functions always work for a pointer to the start of a
422   // reservation.
423   PA_ALWAYS_INLINE static PartitionRoot* FromFirstSuperPage(
424       uintptr_t super_page);
425   PA_ALWAYS_INLINE static PartitionRoot* FromAddrInFirstSuperpage(
426       uintptr_t address);
427 
428   PA_ALWAYS_INLINE void DecreaseTotalSizeOfAllocatedBytes(uintptr_t addr,
429                                                           size_t len)
430       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
431   PA_ALWAYS_INLINE void IncreaseTotalSizeOfAllocatedBytes(uintptr_t addr,
432                                                           size_t len,
433                                                           size_t raw_size)
434       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
435   PA_ALWAYS_INLINE void IncreaseCommittedPages(size_t len);
436   PA_ALWAYS_INLINE void DecreaseCommittedPages(size_t len);
437   PA_ALWAYS_INLINE void DecommitSystemPagesForData(
438       uintptr_t address,
439       size_t length,
440       PageAccessibilityDisposition accessibility_disposition)
441       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
442   PA_ALWAYS_INLINE void RecommitSystemPagesForData(
443       uintptr_t address,
444       size_t length,
445       PageAccessibilityDisposition accessibility_disposition,
446       bool request_tagging)
447       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
448 
449   template <bool already_locked>
450   PA_ALWAYS_INLINE bool TryRecommitSystemPagesForDataInternal(
451       uintptr_t address,
452       size_t length,
453       PageAccessibilityDisposition accessibility_disposition,
454       bool request_tagging);
455 
456   // TryRecommitSystemPagesForDataWithAcquiringLock() locks this root internally
457   // before invoking DecommitEmptySlotSpans(), which needs the lock. So the root
458   // must not be locked when invoking this method.
459   PA_ALWAYS_INLINE bool TryRecommitSystemPagesForDataWithAcquiringLock(
460       uintptr_t address,
461       size_t length,
462       PageAccessibilityDisposition accessibility_disposition,
463       bool request_tagging)
464       PA_LOCKS_EXCLUDED(internal::PartitionRootLock(this));
465 
466   // TryRecommitSystemPagesForDataLocked() doesn't lock this root internally
467   // before invoking DecommitEmptySlotSpans(), which needs the lock. So the root
468   // must have been already locked when invoking this method.
469   PA_ALWAYS_INLINE bool TryRecommitSystemPagesForDataLocked(
470       uintptr_t address,
471       size_t length,
472       PageAccessibilityDisposition accessibility_disposition,
473       bool request_tagging)
474       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
475 
476   [[noreturn]] PA_NOINLINE void OutOfMemory(size_t size);
477 
478   // Returns a pointer aligned on |alignment|, or nullptr.
479   //
480   // |alignment| has to be a power of two and a multiple of sizeof(void*) (as in
481   // posix_memalign() for POSIX systems). The returned pointer may include
482   // padding, and can be passed to |Free()| later.
483   //
484   // NOTE: This is incompatible with anything that adds extras before the
485   // returned pointer, such as in-slot metadata.
486   template <AllocFlags flags = AllocFlags::kNone>
487   PA_NOINLINE void* AlignedAlloc(size_t alignment, size_t requested_size) {
488     return AlignedAllocInline<flags>(alignment, requested_size);
489   }
490   template <AllocFlags flags = AllocFlags::kNone>
491   PA_ALWAYS_INLINE void* AlignedAllocInline(size_t alignment,
492                                             size_t requested_size);
493 
494   // PartitionAlloc supports multiple partitions, and hence multiple callers to
495   // these functions. Setting PA_ALWAYS_INLINE bloats code, and can be
496   // detrimental to performance, for instance if multiple callers are hot (by
497   // increasing cache footprint). Set PA_NOINLINE on the "basic" top-level
498   // functions to mitigate that for "vanilla" callers.
499   //
500   // |type_name == nullptr|: ONLY FOR TESTS except internal uses.
501   // You should provide |type_name| to make debugging easier.
502   template <AllocFlags flags = AllocFlags::kNone>
503   PA_NOINLINE PA_MALLOC_FN void* Alloc(size_t requested_size,
504                                        const char* type_name = nullptr) {
505     return AllocInline<flags>(requested_size, type_name);
506   }
507   template <AllocFlags flags = AllocFlags::kNone>
508   PA_ALWAYS_INLINE PA_MALLOC_FN void* AllocInline(
509       size_t requested_size,
510       const char* type_name = nullptr) {
511     return AllocInternal<flags>(requested_size, internal::PartitionPageSize(),
512                                 type_name);
513   }
514 
515   // AllocInternal exposed for testing.
516   template <AllocFlags flags = AllocFlags::kNone>
517   PA_NOINLINE PA_MALLOC_FN void* AllocInternalForTesting(
518       size_t requested_size,
519       size_t slot_span_alignment,
520       const char* type_name) {
521     return AllocInternal<flags>(requested_size, slot_span_alignment, type_name);
522   }
523 
524   template <AllocFlags alloc_flags = AllocFlags::kNone,
525             FreeFlags free_flags = FreeFlags::kNone>
526   PA_NOINLINE void* Realloc(void* ptr, size_t new_size, const char* type_name) {
527     return ReallocInline<alloc_flags, free_flags>(ptr, new_size, type_name);
528   }
529   template <AllocFlags alloc_flags = AllocFlags::kNone,
530             FreeFlags free_flags = FreeFlags::kNone>
531   PA_ALWAYS_INLINE void* ReallocInline(void* ptr,
532                                        size_t new_size,
533                                        const char* type_name);
534 
535   template <FreeFlags flags = FreeFlags::kNone>
536   PA_NOINLINE void Free(void* object) {
537     FreeInline<flags>(object);
538   }
539   template <FreeFlags flags = FreeFlags::kNone>
540   PA_ALWAYS_INLINE void FreeInline(void* object);
541 
542   template <FreeFlags flags = FreeFlags::kNone>
543   PA_NOINLINE static void FreeInUnknownRoot(void* object) {
544     FreeInlineInUnknownRoot<flags>(object);
545   }
546   template <FreeFlags flags = FreeFlags::kNone>
547   PA_ALWAYS_INLINE static void FreeInlineInUnknownRoot(void* object);
548 
549   // Immediately frees the pointer bypassing the quarantine. |slot_start| is the
550   // beginning of the slot that contains |object|.
551   PA_ALWAYS_INLINE void FreeNoHooksImmediate(void* object,
552                                              SlotSpanMetadata* slot_span,
553                                              uintptr_t slot_start);
554 
555   PA_ALWAYS_INLINE size_t GetSlotUsableSize(const SlotSpanMetadata* slot_span) {
556     return AdjustSizeForExtrasSubtract(slot_span->GetUtilizedSlotSize());
557   }
558 
559   PA_ALWAYS_INLINE static size_t GetUsableSize(void* ptr);
560 
561   // Same as GetUsableSize() except it adjusts the return value for macOS 11
562   // malloc_size() hack.
563   PA_ALWAYS_INLINE static size_t GetUsableSizeWithMac11MallocSizeHack(
564       void* ptr);
565 
566   PA_ALWAYS_INLINE PageAccessibilityConfiguration
567   GetPageAccessibility(bool request_tagging) const;
568   PA_ALWAYS_INLINE PageAccessibilityConfiguration
569       PageAccessibilityWithThreadIsolationIfEnabled(
570           PageAccessibilityConfiguration::Permissions) const;
571 
572   PA_ALWAYS_INLINE size_t
573   AllocationCapacityFromSlotStart(uintptr_t slot_start) const;
574   PA_ALWAYS_INLINE size_t
575   AllocationCapacityFromRequestedSize(size_t size) const;
576 
577 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
578   PA_ALWAYS_INLINE static internal::InSlotMetadata*
579   InSlotMetadataPointerFromSlotStartAndSize(uintptr_t slot_start,
580                                             size_t slot_size);
581   PA_ALWAYS_INLINE internal::InSlotMetadata*
582   InSlotMetadataPointerFromObjectForTesting(void* object) const;
583 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
584 
585   PA_ALWAYS_INLINE bool IsMemoryTaggingEnabled() const;
586   PA_ALWAYS_INLINE TagViolationReportingMode
587   memory_tagging_reporting_mode() const;
588 
589   // Frees memory from this partition, if possible, by decommitting pages or
590   // even entire slot spans. |flags| is an OR of base::PartitionPurgeFlags.
591   void PurgeMemory(int flags);
592 
593   // Reduces the size of the empty slot spans ring, until the dirty size is <=
594   // |limit|.
595   void ShrinkEmptySlotSpansRing(size_t limit)
596       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
597   // The empty slot span ring starts "small", can be enlarged later. This
598   // improves performance by performing fewer system calls, at the cost of more
599   // memory usage.
600   void EnableLargeEmptySlotSpanRing() {
601     ::partition_alloc::internal::ScopedGuard locker{
602         internal::PartitionRootLock(this)};
603     global_empty_slot_span_ring_size = internal::kMinFreeableSpans;
604   }
605 
606   void DumpStats(const char* partition_name,
607                  bool is_light_dump,
608                  PartitionStatsDumper* partition_stats_dumper);
609 
610   static void DeleteForTesting(PartitionRoot* partition_root);
611   void ResetForTesting(bool allow_leaks);
612   void ResetBookkeepingForTesting();
613   void SetGlobalEmptySlotSpanRingIndexForTesting(int16_t index);
614 
615   PA_ALWAYS_INLINE BucketDistribution GetBucketDistribution() const {
616     return settings.bucket_distribution;
617   }
618 
619   static uint16_t SizeToBucketIndex(size_t size,
620                                     BucketDistribution bucket_distribution);
621 
622   PA_ALWAYS_INLINE void FreeInSlotSpan(uintptr_t slot_start,
623                                        SlotSpanMetadata* slot_span)
624       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
625 
626   // Frees memory, with |slot_start| as returned by |RawAlloc()|.
627   PA_ALWAYS_INLINE void RawFree(uintptr_t slot_start);
628   PA_ALWAYS_INLINE void RawFree(uintptr_t slot_start,
629                                 SlotSpanMetadata* slot_span)
630       PA_LOCKS_EXCLUDED(internal::PartitionRootLock(this));
631 
632   PA_ALWAYS_INLINE void RawFreeBatch(FreeListEntry* head,
633                                      FreeListEntry* tail,
634                                      size_t size,
635                                      SlotSpanMetadata* slot_span)
636       PA_LOCKS_EXCLUDED(internal::PartitionRootLock(this));
637 
638   PA_ALWAYS_INLINE void RawFreeWithThreadCache(uintptr_t slot_start,
639                                                SlotSpanMetadata* slot_span);
640 
641   // This is safe to do because we are switching to a bucket distribution with
642   // more buckets, meaning any allocations we have done before the switch are
643   // guaranteed to have a bucket under the new distribution when they are
644   // eventually deallocated. We do not need synchronization here.
645   void SwitchToDenserBucketDistribution() {
646     settings.bucket_distribution = BucketDistribution::kDenser;
647   }
648   // Switching back to the less dense bucket distribution is ok during tests.
649   // At worst, we end up with deallocations that are sent to a bucket that we
650   // cannot allocate from, which will not cause problems besides wasting
651   // memory.
652   void ResetBucketDistributionForTesting() {
653     settings.bucket_distribution = BucketDistribution::kNeutral;
654   }
655 
656   ThreadCache* thread_cache_for_testing() const {
657     return settings.with_thread_cache ? ThreadCache::Get() : nullptr;
658   }
659   size_t get_total_size_of_committed_pages() const {
660     return total_size_of_committed_pages.load(std::memory_order_relaxed);
661   }
662   size_t get_max_size_of_committed_pages() const {
663     return max_size_of_committed_pages.load(std::memory_order_relaxed);
664   }
665 
666   size_t get_total_size_of_allocated_bytes() const {
667     // Since this is only used for bookkeeping, we don't care if the value is
668     // stale, so no need to get a lock here.
669     return PA_TS_UNCHECKED_READ(total_size_of_allocated_bytes);
670   }
671 
672   size_t get_max_size_of_allocated_bytes() const {
673     // Since this is only used for bookkeeping, we don't care if the value is
674     // stale, so no need to get a lock here.
675     return PA_TS_UNCHECKED_READ(max_size_of_allocated_bytes);
676   }
677 
678   internal::pool_handle ChoosePool() const {
679 #if BUILDFLAG(HAS_64_BIT_POINTERS)
680     if (settings.use_configurable_pool) {
681       PA_DCHECK(IsConfigurablePoolAvailable());
682       return internal::kConfigurablePoolHandle;
683     }
684 #endif
685 #if BUILDFLAG(ENABLE_THREAD_ISOLATION)
686     if (settings.thread_isolation.enabled) {
687       return internal::kThreadIsolatedPoolHandle;
688     }
689 #endif
690 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
691     if (PA_LIKELY(brp_enabled())) {
692       return internal::kBRPPoolHandle;
693     } else {
694       return internal::kRegularPoolHandle;
695     }
696 #else
697     return internal::kRegularPoolHandle;
698 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
699   }
700 
701   PA_ALWAYS_INLINE bool IsQuarantineAllowed() const {
702     return settings.quarantine_mode != QuarantineMode::kAlwaysDisabled;
703   }
704 
705   PA_ALWAYS_INLINE bool IsQuarantineEnabled() const {
706     return settings.quarantine_mode == QuarantineMode::kEnabled;
707   }
708 
709   PA_ALWAYS_INLINE bool ShouldQuarantine(void* object) const {
710     if (PA_UNLIKELY(settings.quarantine_mode != QuarantineMode::kEnabled)) {
711       return false;
712     }
713 #if BUILDFLAG(HAS_MEMORY_TAGGING)
714     if (PA_UNLIKELY(quarantine_always_for_testing)) {
715       return true;
716     }
717     // If quarantine is enabled and the tag overflows, move the containing slot
718     // to quarantine, to prevent the attacker from exploiting a pointer that has
719     // an old tag.
720     if (PA_LIKELY(IsMemoryTaggingEnabled())) {
721       return internal::HasOverflowTag(object);
722     }
723     // Default behaviour if MTE is not enabled for this PartitionRoot.
724     return true;
725 #else
726     return true;
727 #endif  // BUILDFLAG(HAS_MEMORY_TAGGING)
728   }
729 
730   PA_ALWAYS_INLINE void SetQuarantineAlwaysForTesting(bool value) {
731     quarantine_always_for_testing = value;
732   }
733 
734   PA_ALWAYS_INLINE bool IsScanEnabled() const {
735     // Enabled scan implies enabled quarantine.
736     PA_DCHECK(settings.scan_mode != ScanMode::kEnabled ||
737               IsQuarantineEnabled());
738     return settings.scan_mode == ScanMode::kEnabled;
739   }
740 
741   PA_ALWAYS_INLINE static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR size_t
742   GetDirectMapMetadataAndGuardPagesSize() {
743     // Because we need to fake a direct-map region to look like a super page, we
744     // need to allocate more pages around the payload:
745     // - The first partition page is a combination of metadata and guard region.
746     // - We also add a trailing guard page. In most cases, a system page would
747     //   suffice. But on 32-bit systems when BRP is on, we need a partition page
748     //   to match granularity of the BRP pool bitmap. For cosistency, we'll use
749     //   a partition page everywhere, which is cheap as it's uncommitted address
750     //   space anyway.
751     return 2 * internal::PartitionPageSize();
752   }
753 
754   PA_ALWAYS_INLINE static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR size_t
755   GetDirectMapSlotSize(size_t raw_size) {
756     // Caller must check that the size is not above the MaxDirectMapped()
757     // limit before calling. This also guards against integer overflow in the
758     // calculation here.
759     PA_DCHECK(raw_size <= internal::MaxDirectMapped());
760     return partition_alloc::internal::base::bits::AlignUp(
761         raw_size, internal::SystemPageSize());
762   }
763 
764   PA_ALWAYS_INLINE static size_t GetDirectMapReservationSize(
765       size_t padded_raw_size) {
766     // Caller must check that the size is not above the MaxDirectMapped()
767     // limit before calling. This also guards against integer overflow in the
768     // calculation here.
769     PA_DCHECK(padded_raw_size <= internal::MaxDirectMapped());
770     return partition_alloc::internal::base::bits::AlignUp(
771         padded_raw_size + GetDirectMapMetadataAndGuardPagesSize(),
772         internal::DirectMapAllocationGranularity());
773   }
774 
775   PA_ALWAYS_INLINE size_t AdjustSize0IfNeeded(size_t size) const {
776     // There are known cases where allowing size 0 would lead to problems:
777     // 1. If extras are present only before allocation (e.g. in-slot metadata),
778     //    the extras will fill the entire kAlignment-sized slot, leading to
779     //    returning a pointer to the next slot. Realloc() calls
780     //    SlotSpanMetadata::FromObject() prior to subtracting extras, thus
781     //    potentially getting a wrong slot span.
782     // 2. On macOS and iOS, PartitionGetSizeEstimate() is used for two purposes:
783     //    as a zone dispatcher and as an underlying implementation of
784     //    malloc_size(3). As a zone dispatcher, zero has a special meaning of
785     //    "doesn't belong to this zone". When extras fill out the entire slot,
786     //    the usable size is 0, thus confusing the zone dispatcher.
787     //
788     // To save ourselves a branch on this hot path, we could eliminate this
789     // check at compile time for cases not listed above. The #if statement would
790     // be rather complex. Then there is also the fear of the unknown. The
791     // existing cases were discovered through obscure, painful-to-debug crashes.
792     // Better save ourselves trouble with not-yet-discovered cases.
793     if (PA_UNLIKELY(size == 0)) {
794       return 1;
795     }
796     return size;
797   }
798 
799   // Adjusts the size by adding extras. Also include the 0->1 adjustment if
800   // needed.
801   PA_ALWAYS_INLINE size_t AdjustSizeForExtrasAdd(size_t size) const {
802     size = AdjustSize0IfNeeded(size);
803     PA_DCHECK(size + settings.extras_size >= size);
804     return size + settings.extras_size;
805   }
806 
807   // Adjusts the size by subtracing extras. Doesn't include the 0->1 adjustment,
808   // which leads to an asymmetry with AdjustSizeForExtrasAdd, but callers of
809   // AdjustSizeForExtrasSubtract either expect the adjustment to be included, or
810   // are indifferent.
811   PA_ALWAYS_INLINE size_t AdjustSizeForExtrasSubtract(size_t size) const {
812     return size - settings.extras_size;
813   }
814 
815   PA_ALWAYS_INLINE uintptr_t SlotStartToObjectAddr(uintptr_t slot_start) const {
816     // TODO(bartekn): Check that |slot_start| is indeed a slot start.
817     return slot_start;
818   }
819 
820   PA_ALWAYS_INLINE void* SlotStartToObject(uintptr_t slot_start) const {
821     // TODO(bartekn): Check that |slot_start| is indeed a slot start.
822     return internal::TagAddr(SlotStartToObjectAddr(slot_start));
823   }
824 
825   PA_ALWAYS_INLINE void* TaggedSlotStartToObject(
826       void* tagged_slot_start) const {
827     // TODO(bartekn): Check that |tagged_slot_start| is indeed a slot start.
828     return reinterpret_cast<void*>(
829         SlotStartToObjectAddr(reinterpret_cast<uintptr_t>(tagged_slot_start)));
830   }
831 
832   PA_ALWAYS_INLINE uintptr_t ObjectToSlotStart(void* object) const {
833     return UntagPtr(object);
834     // TODO(bartekn): Check that the result is indeed a slot start.
835   }
836 
837   PA_ALWAYS_INLINE uintptr_t ObjectToTaggedSlotStart(void* object) const {
838     return reinterpret_cast<uintptr_t>(object);
839     // TODO(bartekn): Check that the result is indeed a slot start.
840   }
841 
842 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
843   bool brp_enabled() const { return settings.brp_enabled_; }
844 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
845 
846   PA_ALWAYS_INLINE bool uses_configurable_pool() const {
847     return settings.use_configurable_pool;
848   }
849 
850   void AdjustForForeground() {
851     max_empty_slot_spans_dirty_bytes_shift = 2;
852     ::partition_alloc::internal::ScopedGuard guard{
853         internal::PartitionRootLock(this)};
854     global_empty_slot_span_ring_size = internal::kMaxFreeableSpans;
855   }
856 
857   void AdjustForBackground() {
858     max_empty_slot_spans_dirty_bytes_shift = 3;
859     // ShrinkEmptySlotSpansRing() will iterate through kMaxFreeableSpans, so
860     // no need to for this to free any empty pages now.
861     ::partition_alloc::internal::ScopedGuard guard{
862         internal::PartitionRootLock(this)};
863     global_empty_slot_span_ring_size = internal::kMinFreeableSpans;
864     if (global_empty_slot_span_ring_index >=
865         static_cast<int16_t>(internal::kMinFreeableSpans)) {
866       global_empty_slot_span_ring_index = 0;
867     }
868   }
869 
870   // To make tests deterministic, it is necessary to uncap the amount of memory
871   // waste incurred by empty slot spans. Otherwise, the size of various
872   // freelists, and committed memory becomes harder to reason about (and
873   // brittle) with a single thread, and non-deterministic with several.
874   void UncapEmptySlotSpanMemoryForTesting() {
875     max_empty_slot_spans_dirty_bytes_shift = 0;
876   }
877 
878   // Enables/disables the free list straightening for larger slot spans in
879   // PurgeMemory().
880   static void SetStraightenLargerSlotSpanFreeListsMode(
881       StraightenLargerSlotSpanFreeListsMode new_value);
882   // Enables/disables the free list sorting for smaller slot spans in
883   // PurgeMemory().
884   static void SetSortSmallerSlotSpanFreeListsEnabled(bool new_value);
885   // Enables/disables the sorting of active slot spans in PurgeMemory().
886   static void SetSortActiveSlotSpansEnabled(bool new_value);
887 
888   static StraightenLargerSlotSpanFreeListsMode
889   GetStraightenLargerSlotSpanFreeListsMode() {
890     return straighten_larger_slot_span_free_lists_;
891   }
892 
893   internal::LightweightQuarantineBranch&
894   GetSchedulerLoopQuarantineBranchForTesting() {
895     return GetSchedulerLoopQuarantineBranch();
896   }
897 
898   const internal::PartitionFreelistDispatcher* get_freelist_dispatcher() {
899 #if BUILDFLAG(USE_FREELIST_POOL_OFFSETS)
900     if (settings.use_pool_offset_freelists) {
901       return internal::PartitionFreelistDispatcher::Create(
902           internal::PartitionFreelistEncoding::kPoolOffsetFreeList);
903     }
904 #endif  // USE_FREELIST_POOL_OFFSETS
905     return internal::PartitionFreelistDispatcher::Create(
906         internal::PartitionFreelistEncoding::kEncodedFreeList);
907   }
908 
909  private:
910   static inline StraightenLargerSlotSpanFreeListsMode
911       straighten_larger_slot_span_free_lists_ =
912           StraightenLargerSlotSpanFreeListsMode::kOnlyWhenUnprovisioning;
913   static inline bool sort_smaller_slot_span_free_lists_ = true;
914   static inline bool sort_active_slot_spans_ = false;
915 
916   // Common path of Free() and FreeInUnknownRoot(). Returns
917   // true if the caller should return immediately.
918   template <FreeFlags flags>
919   PA_ALWAYS_INLINE static bool FreeProlog(void* object,
920                                           const PartitionRoot* root);
921 
922   // |buckets| has `kNumBuckets` elements, but we sometimes access it at index
923   // `kNumBuckets`, which is occupied by the sentinel bucket. The correct layout
924   // is enforced by a static_assert() in partition_root.cc, so this is
925   // fine. However, UBSAN is correctly pointing out that there is an
926   // out-of-bounds access, so disable it for these accesses.
927   //
928   // See crbug.com/1150772 for an instance of Clusterfuzz / UBSAN detecting
929   // this.
930   PA_ALWAYS_INLINE const Bucket& PA_NO_SANITIZE("undefined")
931       bucket_at(size_t i) const {
932     PA_DCHECK(i <= internal::kNumBuckets);
933     return buckets[i];
934   }
935 
936   // Returns whether a |bucket| from |this| root is direct-mapped. This function
937   // does not touch |bucket|, contrary to  PartitionBucket::is_direct_mapped().
938   //
939   // This is meant to be used in hot paths, and particularly *before* going into
940   // the thread cache fast path. Indeed, real-world profiles show that accessing
941   // an allocation's bucket is responsible for a sizable fraction of *total*
942   // deallocation time. This can be understood because
943   // - All deallocations have to access the bucket to know whether it is
944   //   direct-mapped. If not (vast majority of allocations), it can go through
945   //   the fast path, i.e. thread cache.
946   // - The bucket is relatively frequently written to, by *all* threads
947   //   (e.g. every time a slot span becomes full or empty), so accessing it will
948   //   result in some amount of cacheline ping-pong.
949   PA_ALWAYS_INLINE bool IsDirectMappedBucket(Bucket* bucket) const {
950     // All regular allocations are associated with a bucket in the |buckets_|
951     // array. A range check is then sufficient to identify direct-mapped
952     // allocations.
953     bool ret = !(bucket >= this->buckets && bucket <= &this->sentinel_bucket);
954     PA_DCHECK(ret == bucket->is_direct_mapped());
955     return ret;
956   }
957 
958   // Same as |Alloc()|, but allows specifying |slot_span_alignment|. It
959   // has to be a multiple of partition page size, greater than 0 and no greater
960   // than kMaxSupportedAlignment. If it equals exactly 1 partition page, no
961   // special action is taken as PartitionAlloc naturally guarantees this
962   // alignment, otherwise a sub-optimal allocation strategy is used to
963   // guarantee the higher-order alignment.
964   template <AllocFlags flags>
965   PA_ALWAYS_INLINE PA_MALLOC_FN void* AllocInternal(size_t requested_size,
966                                                     size_t slot_span_alignment,
967                                                     const char* type_name);
968 
969   // Same as |AllocInternal()|, but don't handle allocation hooks.
970   template <AllocFlags flags = AllocFlags::kNone>
971   PA_ALWAYS_INLINE PA_MALLOC_FN void* AllocInternalNoHooks(
972       size_t requested_size,
973       size_t slot_span_alignment);
974   // Allocates a memory slot, without initializing extras.
975   //
976   // - |flags| are as in Alloc().
977   // - |raw_size| accommodates for extras on top of Alloc()'s
978   //   |requested_size|.
979   // - |usable_size|, |slot_size| and |is_already_zeroed| are output only.
980   //   Note, |usable_size| is guaranteed to be no smaller than Alloc()'s
981   //   |requested_size|, and no larger than |slot_size|.
982   template <AllocFlags flags>
983   PA_ALWAYS_INLINE uintptr_t RawAlloc(Bucket* bucket,
984                                       size_t raw_size,
985                                       size_t slot_span_alignment,
986                                       size_t* usable_size,
987                                       size_t* slot_size,
988                                       bool* is_already_zeroed);
989   template <AllocFlags flags>
990   PA_ALWAYS_INLINE uintptr_t AllocFromBucket(Bucket* bucket,
991                                              size_t raw_size,
992                                              size_t slot_span_alignment,
993                                              size_t* usable_size,
994                                              size_t* slot_size,
995                                              bool* is_already_zeroed)
996       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
997 
998   // We use this to make MEMORY_TOOL_REPLACES_ALLOCATOR behave the same for max
999   // size as other alloc code.
1000   template <AllocFlags flags>
1001   PA_ALWAYS_INLINE static bool AllocWithMemoryToolProlog(size_t size) {
1002     if (size > partition_alloc::internal::MaxDirectMapped()) {
1003       if constexpr (ContainsFlags(flags, AllocFlags::kReturnNull)) {
1004         // Early return indicating not to proceed with allocation
1005         return false;
1006       }
1007       PA_CHECK(false);
1008     }
1009     return true;  // Allocation should proceed
1010   }
1011 
1012   bool TryReallocInPlaceForNormalBuckets(void* object,
1013                                          SlotSpanMetadata* slot_span,
1014                                          size_t new_size);
1015   bool TryReallocInPlaceForDirectMap(internal::SlotSpanMetadata* slot_span,
1016                                      size_t requested_size)
1017       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
1018   void DecommitEmptySlotSpans()
1019       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
1020   PA_ALWAYS_INLINE void RawFreeLocked(uintptr_t slot_start)
1021       PA_EXCLUSIVE_LOCKS_REQUIRED(internal::PartitionRootLock(this));
1022   ThreadCache* MaybeInitThreadCache();
1023 
1024   // May return an invalid thread cache.
1025   PA_ALWAYS_INLINE ThreadCache* GetOrCreateThreadCache();
1026   PA_ALWAYS_INLINE ThreadCache* GetThreadCache();
1027 
1028   PA_ALWAYS_INLINE internal::LightweightQuarantineBranch&
1029   GetSchedulerLoopQuarantineBranch();
1030 
1031   internal::LightweightQuarantineBranch CreateSchedulerLoopQuarantineBranch(
1032       bool lock_required);
1033 
1034   PA_ALWAYS_INLINE AllocationNotificationData
1035   CreateAllocationNotificationData(void* object,
1036                                    size_t size,
1037                                    const char* type_name) const;
1038   PA_ALWAYS_INLINE static FreeNotificationData
1039   CreateDefaultFreeNotificationData(void* address);
1040   PA_ALWAYS_INLINE FreeNotificationData
1041   CreateFreeNotificationData(void* address) const;
1042 
1043 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
1044   PA_NOINLINE void QuarantineForBrp(const SlotSpanMetadata* slot_span,
1045                                     void* object);
1046 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
1047 
1048 #if PA_CONFIG(USE_PARTITION_ROOT_ENUMERATOR)
1049   static internal::Lock& GetEnumeratorLock();
1050 
1051   PartitionRoot* PA_GUARDED_BY(GetEnumeratorLock()) next_root = nullptr;
1052   PartitionRoot* PA_GUARDED_BY(GetEnumeratorLock()) prev_root = nullptr;
1053 
1054   friend class internal::PartitionRootEnumerator;
1055 #endif  // PA_CONFIG(USE_PARTITION_ROOT_ENUMERATOR)
1056 
1057   friend class ThreadCache;
1058 };
1059 
1060 namespace internal {
1061 
PartitionRootLock(PartitionRoot * root)1062 PA_ALWAYS_INLINE ::partition_alloc::internal::Lock& PartitionRootLock(
1063     PartitionRoot* root) {
1064   return root->lock_;
1065 }
1066 
1067 class ScopedSyscallTimer {
1068  public:
1069 #if PA_CONFIG(COUNT_SYSCALL_TIME)
ScopedSyscallTimer(PartitionRoot * root)1070   explicit ScopedSyscallTimer(PartitionRoot* root)
1071       : root_(root), tick_(base::TimeTicks::Now()) {}
1072 
~ScopedSyscallTimer()1073   ~ScopedSyscallTimer() {
1074     root_->syscall_count.fetch_add(1, std::memory_order_relaxed);
1075 
1076     int64_t elapsed_nanos = (base::TimeTicks::Now() - tick_).InNanoseconds();
1077     if (elapsed_nanos > 0) {
1078       root_->syscall_total_time_ns.fetch_add(
1079           static_cast<uint64_t>(elapsed_nanos), std::memory_order_relaxed);
1080     }
1081   }
1082 
1083  private:
1084   PartitionRoot* root_;
1085   const base::TimeTicks tick_;
1086 #else
1087   explicit ScopedSyscallTimer(PartitionRoot* root) {
1088     root->syscall_count.fetch_add(1, std::memory_order_relaxed);
1089   }
1090 #endif
1091 };
1092 
1093 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
1094 
1095 struct SlotAddressAndSize {
1096   uintptr_t slot_start;
1097   size_t size;
1098 };
1099 
1100 PA_ALWAYS_INLINE SlotAddressAndSize
PartitionAllocGetDirectMapSlotStartAndSizeInBRPPool(uintptr_t address)1101 PartitionAllocGetDirectMapSlotStartAndSizeInBRPPool(uintptr_t address) {
1102   PA_DCHECK(IsManagedByPartitionAllocBRPPool(address));
1103 #if BUILDFLAG(HAS_64_BIT_POINTERS)
1104   // Use this variant of GetDirectMapReservationStart as it has better
1105   // performance.
1106   uintptr_t offset = OffsetInBRPPool(address);
1107   uintptr_t reservation_start =
1108       GetDirectMapReservationStart(address, kBRPPoolHandle, offset);
1109 #else  // BUILDFLAG(HAS_64_BIT_POINTERS)
1110   uintptr_t reservation_start = GetDirectMapReservationStart(address);
1111 #endif
1112   if (!reservation_start) {
1113     return SlotAddressAndSize{.slot_start = uintptr_t(0), .size = size_t(0)};
1114   }
1115 
1116   // The direct map allocation may not start exactly from the first page, as
1117   // there may be padding for alignment. The first page metadata holds an offset
1118   // to where direct map metadata, and thus direct map start, are located.
1119   auto* first_page_metadata =
1120       PartitionPageMetadata::FromAddr(reservation_start + PartitionPageSize());
1121   auto* page_metadata =
1122       first_page_metadata + first_page_metadata->slot_span_metadata_offset;
1123   PA_DCHECK(page_metadata->is_valid);
1124   PA_DCHECK(!page_metadata->slot_span_metadata_offset);
1125   auto* slot_span = &page_metadata->slot_span_metadata;
1126   uintptr_t slot_start = SlotSpanMetadata::ToSlotSpanStart(slot_span);
1127 #if BUILDFLAG(PA_DCHECK_IS_ON)
1128   auto* direct_map_metadata =
1129       PartitionDirectMapMetadata::FromSlotSpanMetadata(slot_span);
1130   size_t padding_for_alignment =
1131       direct_map_metadata->direct_map_extent.padding_for_alignment;
1132   PA_DCHECK(padding_for_alignment ==
1133             static_cast<size_t>(page_metadata - first_page_metadata) *
1134                 PartitionPageSize());
1135   PA_DCHECK(slot_start ==
1136             reservation_start + PartitionPageSize() + padding_for_alignment);
1137 #endif  // BUILDFLAG(PA_DCHECK_IS_ON)
1138   return SlotAddressAndSize{.slot_start = slot_start,
1139                             .size = slot_span->bucket->slot_size};
1140 }
1141 
1142 // Gets the start address and size of the allocated slot. The input |address|
1143 // can point anywhere in the slot, including the slot start as well as
1144 // immediately past the slot.
1145 //
1146 // This isn't a general purpose function, it is used specifically for obtaining
1147 // BackupRefPtr's in-slot metadata. The caller is responsible for ensuring that
1148 // the in-slot metadata is in place for this allocation.
1149 PA_ALWAYS_INLINE SlotAddressAndSize
PartitionAllocGetSlotStartAndSizeInBRPPool(uintptr_t address)1150 PartitionAllocGetSlotStartAndSizeInBRPPool(uintptr_t address) {
1151   PA_DCHECK(IsManagedByNormalBucketsOrDirectMap(address));
1152   DCheckIfManagedByPartitionAllocBRPPool(address);
1153 
1154   auto directmap_slot_info =
1155       PartitionAllocGetDirectMapSlotStartAndSizeInBRPPool(address);
1156   if (PA_UNLIKELY(directmap_slot_info.slot_start)) {
1157     return directmap_slot_info;
1158   }
1159 
1160   auto* slot_span = SlotSpanMetadata::FromAddr(address);
1161 #if BUILDFLAG(PA_DCHECK_IS_ON)
1162   auto* root = PartitionRoot::FromSlotSpanMetadata(slot_span);
1163   // Double check that in-slot metadata is indeed present. Currently that's the
1164   // case only when BRP is used.
1165   PA_DCHECK(root->brp_enabled());
1166 #endif  // BUILDFLAG(PA_DCHECK_IS_ON)
1167 
1168   // Get the offset from the beginning of the slot span.
1169   uintptr_t slot_span_start = SlotSpanMetadata::ToSlotSpanStart(slot_span);
1170   size_t offset_in_slot_span = address - slot_span_start;
1171 
1172   auto* bucket = slot_span->bucket;
1173   return SlotAddressAndSize{
1174       .slot_start =
1175           slot_span_start +
1176           bucket->slot_size * bucket->GetSlotNumber(offset_in_slot_span),
1177       .size = bucket->slot_size};
1178 }
1179 
1180 // Return values to indicate where a pointer is pointing relative to the bounds
1181 // of an allocation.
1182 enum class PtrPosWithinAlloc {
1183   // When BACKUP_REF_PTR_POISON_OOB_PTR is disabled, end-of-allocation pointers
1184   // are also considered in-bounds.
1185   kInBounds,
1186 #if BUILDFLAG(BACKUP_REF_PTR_POISON_OOB_PTR)
1187   kAllocEnd,
1188 #endif
1189   kFarOOB
1190 };
1191 
1192 // Checks whether `test_address` is in the same allocation slot as
1193 // `orig_address`.
1194 //
1195 // This can be called after adding or subtracting from the `orig_address`
1196 // to produce a different pointer which must still stay in the same allocation.
1197 //
1198 // The `type_size` is the size of the type that the raw_ptr is pointing to,
1199 // which may be the type the allocation is holding or a compatible pointer type
1200 // such as a base class or char*. It is used to detect pointers near the end of
1201 // the allocation but not strictly beyond it.
1202 //
1203 // This isn't a general purpose function. The caller is responsible for ensuring
1204 // that the in-slot metadata is in place for this allocation.
1205 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
1206 PtrPosWithinAlloc IsPtrWithinSameAlloc(uintptr_t orig_address,
1207                                        uintptr_t test_address,
1208                                        size_t type_size);
1209 
PartitionAllocFreeForRefCounting(uintptr_t slot_start)1210 PA_ALWAYS_INLINE void PartitionAllocFreeForRefCounting(uintptr_t slot_start) {
1211   auto* slot_span = SlotSpanMetadata::FromSlotStart(slot_start);
1212   auto* root = PartitionRoot::FromSlotSpanMetadata(slot_span);
1213   // Currently, InSlotMetadata is allocated when BRP is used.
1214   PA_DCHECK(root->brp_enabled());
1215   PA_DCHECK(!PartitionRoot::InSlotMetadataPointerFromSlotStartAndSize(
1216                  slot_start, slot_span->bucket->slot_size)
1217                  ->IsAlive());
1218 
1219   // Iterating over the entire slot can be really expensive.
1220 #if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
1221   auto hook = PartitionAllocHooks::GetQuarantineOverrideHook();
1222   // If we have a hook the object segment is not necessarily filled
1223   // with |kQuarantinedByte|.
1224   if (PA_LIKELY(!hook)) {
1225     unsigned char* object =
1226         static_cast<unsigned char*>(root->SlotStartToObject(slot_start));
1227     for (size_t i = 0; i < root->GetSlotUsableSize(slot_span); ++i) {
1228       PA_DCHECK(object[i] == kQuarantinedByte);
1229     }
1230   }
1231   DebugMemset(SlotStartAddr2Ptr(slot_start), kFreedByte,
1232               slot_span->GetUtilizedSlotSize());
1233 #endif  // BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
1234 
1235   root->total_size_of_brp_quarantined_bytes.fetch_sub(
1236       slot_span->GetSlotSizeForBookkeeping(), std::memory_order_relaxed);
1237   root->total_count_of_brp_quarantined_slots.fetch_sub(
1238       1, std::memory_order_relaxed);
1239 
1240   root->RawFreeWithThreadCache(slot_start, slot_span);
1241 }
1242 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
1243 
1244 }  // namespace internal
1245 
1246 template <AllocFlags flags>
1247 PA_ALWAYS_INLINE uintptr_t
AllocFromBucket(Bucket * bucket,size_t raw_size,size_t slot_span_alignment,size_t * usable_size,size_t * slot_size,bool * is_already_zeroed)1248 PartitionRoot::AllocFromBucket(Bucket* bucket,
1249                                size_t raw_size,
1250                                size_t slot_span_alignment,
1251                                size_t* usable_size,
1252                                size_t* slot_size,
1253                                bool* is_already_zeroed) {
1254   PA_DCHECK((slot_span_alignment >= internal::PartitionPageSize()) &&
1255             std::has_single_bit(slot_span_alignment));
1256   SlotSpanMetadata* slot_span = bucket->active_slot_spans_head;
1257   // There always must be a slot span on the active list (could be a sentinel).
1258   PA_DCHECK(slot_span);
1259   // Check that it isn't marked full, which could only be true if the span was
1260   // removed from the active list.
1261   PA_DCHECK(!slot_span->marked_full);
1262 
1263   uintptr_t slot_start =
1264       internal::SlotStartPtr2Addr(slot_span->get_freelist_head());
1265   // Use the fast path when a slot is readily available on the free list of the
1266   // first active slot span. However, fall back to the slow path if a
1267   // higher-order alignment is requested, because an inner slot of an existing
1268   // slot span is unlikely to satisfy it.
1269   if (PA_LIKELY(slot_span_alignment <= internal::PartitionPageSize() &&
1270                 slot_start)) {
1271     *is_already_zeroed = false;
1272     // This is a fast path, avoid calling GetSlotUsableSize() in Release builds
1273     // as it is costlier. Copy its small bucket path instead.
1274     *usable_size = AdjustSizeForExtrasSubtract(bucket->slot_size);
1275     PA_DCHECK(*usable_size == GetSlotUsableSize(slot_span));
1276 
1277     // If these DCHECKs fire, you probably corrupted memory.
1278     // TODO(crbug.com/1257655): See if we can afford to make these CHECKs.
1279     DCheckIsValidSlotSpan(slot_span);
1280 
1281     // All large allocations must go through the slow path to correctly update
1282     // the size metadata.
1283     PA_DCHECK(!slot_span->CanStoreRawSize());
1284     PA_DCHECK(!slot_span->bucket->is_direct_mapped());
1285 
1286     void* entry = slot_span->PopForAlloc(
1287         bucket->slot_size, PartitionRoot::FromSlotSpanMetadata(slot_span)
1288                                ->get_freelist_dispatcher());
1289 
1290     PA_DCHECK(internal::SlotStartPtr2Addr(entry) == slot_start);
1291 
1292     PA_DCHECK(slot_span->bucket == bucket);
1293   } else {
1294     slot_start =
1295         bucket->SlowPathAlloc(this, flags, raw_size, slot_span_alignment,
1296                               &slot_span, is_already_zeroed);
1297     if (PA_UNLIKELY(!slot_start)) {
1298       return 0;
1299     }
1300     PA_DCHECK(slot_span == SlotSpanMetadata::FromSlotStart(slot_start));
1301     // TODO(crbug.com/1257655): See if we can afford to make this a CHECK.
1302     DCheckIsValidSlotSpan(slot_span);
1303     // For direct mapped allocations, |bucket| is the sentinel.
1304     PA_DCHECK((slot_span->bucket == bucket) ||
1305               (slot_span->bucket->is_direct_mapped() &&
1306                (bucket == &sentinel_bucket)));
1307 
1308     *usable_size = GetSlotUsableSize(slot_span);
1309   }
1310   PA_DCHECK(slot_span->GetUtilizedSlotSize() <= slot_span->bucket->slot_size);
1311   IncreaseTotalSizeOfAllocatedBytes(
1312       slot_start, slot_span->GetSlotSizeForBookkeeping(), raw_size);
1313 
1314 #if BUILDFLAG(USE_FREESLOT_BITMAP)
1315   if (!slot_span->bucket->is_direct_mapped()) {
1316     internal::FreeSlotBitmapMarkSlotAsUsed(slot_start);
1317   }
1318 #endif
1319 
1320   *slot_size = slot_span->bucket->slot_size;
1321   return slot_start;
1322 }
1323 
CreateAllocationNotificationData(void * object,size_t size,const char * type_name)1324 AllocationNotificationData PartitionRoot::CreateAllocationNotificationData(
1325     void* object,
1326     size_t size,
1327     const char* type_name) const {
1328   AllocationNotificationData notification_data(object, size, type_name);
1329 
1330   if (IsMemoryTaggingEnabled()) {
1331 #if BUILDFLAG(HAS_MEMORY_TAGGING)
1332     notification_data.SetMteReportingMode(memory_tagging_reporting_mode());
1333 #endif
1334   }
1335 
1336   return notification_data;
1337 }
1338 
CreateDefaultFreeNotificationData(void * address)1339 FreeNotificationData PartitionRoot::CreateDefaultFreeNotificationData(
1340     void* address) {
1341   return FreeNotificationData(address);
1342 }
1343 
CreateFreeNotificationData(void * address)1344 FreeNotificationData PartitionRoot::CreateFreeNotificationData(
1345     void* address) const {
1346   FreeNotificationData notification_data =
1347       CreateDefaultFreeNotificationData(address);
1348 
1349   if (IsMemoryTaggingEnabled()) {
1350 #if BUILDFLAG(HAS_MEMORY_TAGGING)
1351     notification_data.SetMteReportingMode(memory_tagging_reporting_mode());
1352 #endif
1353   }
1354 
1355   return notification_data;
1356 }
1357 
1358 // static
1359 template <FreeFlags flags>
FreeProlog(void * object,const PartitionRoot * root)1360 PA_ALWAYS_INLINE bool PartitionRoot::FreeProlog(void* object,
1361                                                 const PartitionRoot* root) {
1362   static_assert(AreValidFlags(flags));
1363   if constexpr (ContainsFlags(flags, FreeFlags::kNoHooks)) {
1364     return false;
1365   }
1366 
1367 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
1368   if constexpr (!ContainsFlags(flags, FreeFlags::kNoMemoryToolOverride)) {
1369     free(object);
1370     return true;
1371   }
1372 #endif  // defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
1373   if (PA_UNLIKELY(!object)) {
1374     return true;
1375   }
1376 
1377   if (PartitionAllocHooks::AreHooksEnabled()) {
1378     // A valid |root| might not be available if this function is called from
1379     // |FreeInUnknownRoot| and not deducible if object originates from
1380     // an override hook.
1381     // TODO(crbug.com/1137393): See if we can make the root available more
1382     // reliably or even make this function non-static.
1383     auto notification_data = root ? root->CreateFreeNotificationData(object)
1384                                   : CreateDefaultFreeNotificationData(object);
1385     PartitionAllocHooks::FreeObserverHookIfEnabled(notification_data);
1386     if (PartitionAllocHooks::FreeOverrideHookIfEnabled(object)) {
1387       return true;
1388     }
1389   }
1390 
1391   return false;
1392 }
1393 
IsMemoryTaggingEnabled()1394 PA_ALWAYS_INLINE bool PartitionRoot::IsMemoryTaggingEnabled() const {
1395 #if BUILDFLAG(HAS_MEMORY_TAGGING)
1396   return settings.memory_tagging_enabled_;
1397 #else
1398   return false;
1399 #endif  // BUILDFLAG(HAS_MEMORY_TAGGING)
1400 }
1401 
1402 PA_ALWAYS_INLINE TagViolationReportingMode
memory_tagging_reporting_mode()1403 PartitionRoot::memory_tagging_reporting_mode() const {
1404 #if BUILDFLAG(HAS_MEMORY_TAGGING)
1405   return settings.memory_tagging_reporting_mode_;
1406 #else
1407   return TagViolationReportingMode::kUndefined;
1408 #endif  // BUILDFLAG(HAS_MEMORY_TAGGING)
1409 }
1410 
1411 // static
1412 template <FreeFlags flags>
FreeInlineInUnknownRoot(void * object)1413 PA_ALWAYS_INLINE void PartitionRoot::FreeInlineInUnknownRoot(void* object) {
1414   bool early_return = FreeProlog<flags>(object, nullptr);
1415   if (early_return) {
1416     return;
1417   }
1418 
1419   if (PA_UNLIKELY(!object)) {
1420     return;
1421   }
1422 
1423   // Fetch the root from the address, and not SlotSpanMetadata. This is
1424   // important, as obtaining it from SlotSpanMetadata is a slow operation
1425   // (looking into the metadata area, and following a pointer), which can induce
1426   // cache coherency traffic (since they're read on every free(), and written to
1427   // on any malloc()/free() that is not a hit in the thread cache). This way we
1428   // change the critical path from object -> slot_span -> root into two
1429   // *parallel* ones:
1430   // 1. object -> root
1431   // 2. object -> slot_span (inside FreeInline)
1432   uintptr_t object_addr = internal::ObjectPtr2Addr(object);
1433   auto* root = FromAddrInFirstSuperpage(object_addr);
1434   root->FreeInline<flags | FreeFlags::kNoHooks>(object);
1435 }
1436 
1437 template <FreeFlags flags>
FreeInline(void * object)1438 PA_ALWAYS_INLINE void PartitionRoot::FreeInline(void* object) {
1439   // The correct PartitionRoot might not be deducible if the |object| originates
1440   // from an override hook.
1441   bool early_return = FreeProlog<flags>(object, this);
1442   if (early_return) {
1443     return;
1444   }
1445 
1446   if (PA_UNLIKELY(!object)) {
1447     return;
1448   }
1449 
1450   // Almost all calls to FreeNoNooks() will end up writing to |*object|, the
1451   // only cases where we don't would be delayed free() in PCScan, but |*object|
1452   // can be cold in cache.
1453   PA_PREFETCH(object);
1454 
1455   // On Android, malloc() interception is more fragile than on other
1456   // platforms, as we use wrapped symbols. However, the pools allow us to
1457   // quickly tell that a pointer was allocated with PartitionAlloc.
1458   //
1459   // This is a crash to detect imperfect symbol interception. However, we can
1460   // forward allocations we don't own to the system malloc() implementation in
1461   // these rare cases, assuming that some remain.
1462   //
1463   // On Android Chromecast devices, this is already checked in PartitionFree()
1464   // in the shim.
1465 #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
1466     (BUILDFLAG(IS_ANDROID) && !BUILDFLAG(PA_IS_CAST_ANDROID))
1467   uintptr_t object_addr = internal::ObjectPtr2Addr(object);
1468   PA_CHECK(IsManagedByPartitionAlloc(object_addr));
1469 #endif
1470 
1471   SlotSpanMetadata* slot_span = SlotSpanMetadata::FromObject(object);
1472   PA_DCHECK(PartitionRoot::FromSlotSpanMetadata(slot_span) == this);
1473 
1474 #if BUILDFLAG(HAS_MEMORY_TAGGING)
1475   if (PA_LIKELY(IsMemoryTaggingEnabled())) {
1476     const size_t slot_size = slot_span->bucket->slot_size;
1477     if (PA_LIKELY(slot_size <= internal::kMaxMemoryTaggingSize)) {
1478       // slot_span is untagged at this point, so we have to recover its tag
1479       // again to increment and provide use-after-free mitigations.
1480       void* retagged_slot_start = internal::TagMemoryRangeIncrement(
1481           ObjectToTaggedSlotStart(object), slot_size);
1482       // Incrementing the MTE-tag in the memory range invalidates the |object|'s
1483       // tag, so it must be retagged.
1484       object = TaggedSlotStartToObject(retagged_slot_start);
1485     }
1486   }
1487 #else   // BUILDFLAG(HAS_MEMORY_TAGGING)
1488   // We are going to read from |*slot_span| in all branches, but haven't done it
1489   // yet.
1490   //
1491   // TODO(crbug.com/1207307): It would be much better to avoid touching
1492   // |*slot_span| at all on the fast path, or at least to separate its read-only
1493   // parts (i.e. bucket pointer) from the rest. Indeed, every thread cache miss
1494   // (or batch fill) will *write* to |slot_span->freelist_head|, leading to
1495   // cacheline ping-pong.
1496   //
1497   // Don't do it when memory tagging is enabled, as |*slot_span| has already
1498   // been touched above.
1499   PA_PREFETCH(slot_span);
1500 #endif  // BUILDFLAG(HAS_MEMORY_TAGGING)
1501 
1502   uintptr_t slot_start = ObjectToSlotStart(object);
1503   PA_DCHECK(slot_span == SlotSpanMetadata::FromSlotStart(slot_start));
1504 
1505   if constexpr (ContainsFlags(flags, FreeFlags::kZap)) {
1506     if (settings.zapping_by_free_flags) {
1507       internal::SecureMemset(internal::SlotStartAddr2Ptr(slot_start),
1508                              internal::kFreedByte,
1509                              GetSlotUsableSize(slot_span));
1510     }
1511   }
1512   // TODO(https://crbug.com/1497380): Collecting objects for
1513   // `kSchedulerLoopQuarantineBranch` here means it "delays" other checks (BRP
1514   // refcount, cookie, etc.)
1515   // For better debuggability, we should do these checks before quarantining.
1516   if constexpr (ContainsFlags(flags, FreeFlags::kSchedulerLoopQuarantine)) {
1517     if (settings.scheduler_loop_quarantine) {
1518 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
1519       // TODO(keishi): Add PA_LIKELY when brp is fully enabled as |brp_enabled|
1520       // will be false only for the aligned partition.
1521       if (brp_enabled()) {
1522         auto* ref_count = InSlotMetadataPointerFromSlotStartAndSize(
1523             slot_start, slot_span->bucket->slot_size);
1524         ref_count->PreReleaseFromAllocator();
1525       }
1526 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
1527       GetSchedulerLoopQuarantineBranch().Quarantine(object, slot_span,
1528                                                     slot_start);
1529       return;
1530     }
1531   }
1532 
1533 #if BUILDFLAG(USE_STARSCAN)
1534   // TODO(bikineev): Change the condition to PA_LIKELY once PCScan is enabled by
1535   // default.
1536   if (PA_UNLIKELY(ShouldQuarantine(object))) {
1537     // PCScan safepoint. Call before potentially scheduling scanning task.
1538     PCScan::JoinScanIfNeeded();
1539     if (PA_LIKELY(internal::IsManagedByNormalBuckets(slot_start))) {
1540       PCScan::MoveToQuarantine(object, GetSlotUsableSize(slot_span), slot_start,
1541                                slot_span->bucket->slot_size);
1542       return;
1543     }
1544   }
1545 #endif  // BUILDFLAG(USE_STARSCAN)
1546 
1547   FreeNoHooksImmediate(object, slot_span, slot_start);
1548 }
1549 
FreeNoHooksImmediate(void * object,SlotSpanMetadata * slot_span,uintptr_t slot_start)1550 PA_ALWAYS_INLINE void PartitionRoot::FreeNoHooksImmediate(
1551     void* object,
1552     SlotSpanMetadata* slot_span,
1553     uintptr_t slot_start) {
1554   // The thread cache is added "in the middle" of the main allocator, that is:
1555   // - After all the cookie/in-slot metadata management
1556   // - Before the "raw" allocator.
1557   //
1558   // On the deallocation side:
1559   // 1. Check cookie/in-slot metadata, adjust the pointer
1560   // 2. Deallocation
1561   //   a. Return to the thread cache if possible. If it succeeds, return.
1562   //   b. Otherwise, call the "raw" allocator <-- Locking
1563   PA_DCHECK(object);
1564   PA_DCHECK(slot_span);
1565   DCheckIsValidSlotSpan(slot_span);
1566   PA_DCHECK(slot_start);
1567 
1568   // Layout inside the slot:
1569   //   |...object...|[empty]|[cookie]|[unused]|[metadata]|
1570   //   <--------(a)--------->
1571   //                        <--(b)--->   +    <---(b)---->
1572   //   <-------------(c)------------->   +    <---(c)---->
1573   //     (a) usable_size
1574   //     (b) extras
1575   //     (c) utilized_slot_size
1576   //
1577   // Note: in-slot metadata and cookie can be 0-sized.
1578   //
1579   // For more context, see the other "Layout inside the slot" comment inside
1580   // AllocInternalNoHooks().
1581 
1582   if (settings.use_cookie) {
1583     // Verify the cookie after the allocated region.
1584     // If this assert fires, you probably corrupted memory.
1585     internal::PartitionCookieCheckValue(static_cast<unsigned char*>(object) +
1586                                         GetSlotUsableSize(slot_span));
1587   }
1588 
1589 #if BUILDFLAG(USE_STARSCAN)
1590   // TODO(bikineev): Change the condition to PA_LIKELY once PCScan is enabled by
1591   // default.
1592   if (PA_UNLIKELY(IsQuarantineEnabled())) {
1593     if (PA_LIKELY(internal::IsManagedByNormalBuckets(slot_start))) {
1594       // Mark the state in the state bitmap as freed.
1595       internal::StateBitmapFromAddr(slot_start)->Free(slot_start);
1596     }
1597   }
1598 #endif  // BUILDFLAG(USE_STARSCAN)
1599 
1600 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
1601   if (PA_LIKELY(brp_enabled())) {
1602     auto* ref_count = InSlotMetadataPointerFromSlotStartAndSize(
1603         slot_start, slot_span->bucket->slot_size);
1604     // If there are no more references to the allocation, it can be freed
1605     // immediately. Otherwise, defer the operation and zap the memory to turn
1606     // potential use-after-free issues into unexploitable crashes.
1607     if (PA_UNLIKELY(!ref_count->IsAliveWithNoKnownRefs())) {
1608       QuarantineForBrp(slot_span, object);
1609     }
1610 
1611     if (PA_UNLIKELY(!(ref_count->ReleaseFromAllocator()))) {
1612       total_size_of_brp_quarantined_bytes.fetch_add(
1613           slot_span->GetSlotSizeForBookkeeping(), std::memory_order_relaxed);
1614       total_count_of_brp_quarantined_slots.fetch_add(1,
1615                                                      std::memory_order_relaxed);
1616       cumulative_size_of_brp_quarantined_bytes.fetch_add(
1617           slot_span->GetSlotSizeForBookkeeping(), std::memory_order_relaxed);
1618       cumulative_count_of_brp_quarantined_slots.fetch_add(
1619           1, std::memory_order_relaxed);
1620       return;
1621     }
1622   }
1623 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
1624 
1625   // memset() can be really expensive.
1626 #if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
1627   internal::DebugMemset(internal::SlotStartAddr2Ptr(slot_start),
1628                         internal::kFreedByte, slot_span->GetUtilizedSlotSize());
1629 #elif PA_CONFIG(ZERO_RANDOMLY_ON_FREE)
1630   // `memset` only once in a while: we're trading off safety for time
1631   // efficiency.
1632   if (PA_UNLIKELY(internal::RandomPeriod()) &&
1633       !IsDirectMappedBucket(slot_span->bucket)) {
1634     internal::SecureMemset(internal::SlotStartAddr2Ptr(slot_start), 0,
1635                            slot_span->GetUtilizedSlotSize());
1636   }
1637 #endif  // PA_CONFIG(ZERO_RANDOMLY_ON_FREE)
1638 
1639   RawFreeWithThreadCache(slot_start, slot_span);
1640 }
1641 
FreeInSlotSpan(uintptr_t slot_start,SlotSpanMetadata * slot_span)1642 PA_ALWAYS_INLINE void PartitionRoot::FreeInSlotSpan(
1643     uintptr_t slot_start,
1644     SlotSpanMetadata* slot_span) {
1645   DecreaseTotalSizeOfAllocatedBytes(slot_start,
1646                                     slot_span->GetSlotSizeForBookkeeping());
1647 #if BUILDFLAG(USE_FREESLOT_BITMAP)
1648   if (!slot_span->bucket->is_direct_mapped()) {
1649     internal::FreeSlotBitmapMarkSlotAsFree(slot_start);
1650   }
1651 #endif
1652 
1653   return slot_span->Free(slot_start, this,
1654                          PartitionRoot::get_freelist_dispatcher());
1655 }
1656 
RawFree(uintptr_t slot_start)1657 PA_ALWAYS_INLINE void PartitionRoot::RawFree(uintptr_t slot_start) {
1658   SlotSpanMetadata* slot_span = SlotSpanMetadata::FromSlotStart(slot_start);
1659   RawFree(slot_start, slot_span);
1660 }
1661 
1662 #if PA_CONFIG(IS_NONCLANG_MSVC)
1663 // MSVC only supports inline assembly on x86. This preprocessor directive
1664 // is intended to be a replacement for the same.
1665 //
1666 // TODO(crbug.com/1351310): Make sure inlining doesn't degrade this into
1667 // a no-op or similar. The documentation doesn't say.
1668 #pragma optimize("", off)
1669 #endif
RawFree(uintptr_t slot_start,SlotSpanMetadata * slot_span)1670 PA_ALWAYS_INLINE void PartitionRoot::RawFree(uintptr_t slot_start,
1671                                              SlotSpanMetadata* slot_span) {
1672   // At this point we are about to acquire the lock, so we try to minimize the
1673   // risk of blocking inside the locked section.
1674   //
1675   // For allocations that are not direct-mapped, there will always be a store at
1676   // the beginning of |*slot_start|, to link the freelist. This is why there is
1677   // a prefetch of it at the beginning of the free() path.
1678   //
1679   // However, the memory which is being freed can be very cold (for instance
1680   // during browser shutdown, when various caches are finally completely freed),
1681   // and so moved to either compressed memory or swap. This means that touching
1682   // it here can cause a major page fault. This is in turn will cause
1683   // descheduling of the thread *while locked*. Since we don't have priority
1684   // inheritance locks on most platforms, avoiding long locked periods relies on
1685   // the OS having proper priority boosting. There is evidence
1686   // (crbug.com/1228523) that this is not always the case on Windows, and a very
1687   // low priority background thread can block the main one for a long time,
1688   // leading to hangs.
1689   //
1690   // To mitigate that, make sure that we fault *before* locking. Note that this
1691   // is useless for direct-mapped allocations (which are very rare anyway), and
1692   // that this path is *not* taken for thread cache bucket purge (since it calls
1693   // RawFreeLocked()). This is intentional, as the thread cache is purged often,
1694   // and the memory has a consequence the memory has already been touched
1695   // recently (to link the thread cache freelist).
1696   *static_cast<volatile uintptr_t*>(internal::SlotStartAddr2Ptr(slot_start)) =
1697       0;
1698   // Note: even though we write to slot_start + sizeof(void*) as well, due to
1699   // alignment constraints, the two locations are always going to be in the same
1700   // OS page. No need to write to the second one as well.
1701   //
1702   // Do not move the store above inside the locked section.
1703 #if !(PA_CONFIG(IS_NONCLANG_MSVC))
1704   __asm__ __volatile__("" : : "r"(slot_start) : "memory");
1705 #endif
1706 
1707   ::partition_alloc::internal::ScopedGuard guard{
1708       internal::PartitionRootLock(this)};
1709   FreeInSlotSpan(slot_start, slot_span);
1710 }
1711 #if PA_CONFIG(IS_NONCLANG_MSVC)
1712 #pragma optimize("", on)
1713 #endif
1714 
RawFreeBatch(FreeListEntry * head,FreeListEntry * tail,size_t size,SlotSpanMetadata * slot_span)1715 PA_ALWAYS_INLINE void PartitionRoot::RawFreeBatch(FreeListEntry* head,
1716                                                   FreeListEntry* tail,
1717                                                   size_t size,
1718                                                   SlotSpanMetadata* slot_span) {
1719   PA_DCHECK(head);
1720   PA_DCHECK(tail);
1721   PA_DCHECK(size > 0);
1722   PA_DCHECK(slot_span);
1723   DCheckIsValidSlotSpan(slot_span);
1724   // The passed freelist is likely to be just built up, which means that the
1725   // corresponding pages were faulted in (without acquiring the lock). So there
1726   // is no need to touch pages manually here before the lock.
1727   ::partition_alloc::internal::ScopedGuard guard{
1728       internal::PartitionRootLock(this)};
1729   // TODO(thiabaud): Fix the accounting here. The size is correct, but the
1730   // pointer is not. This only affects local tools that record each allocation,
1731   // not our metrics.
1732   DecreaseTotalSizeOfAllocatedBytes(
1733       0u, slot_span->GetSlotSizeForBookkeeping() * size);
1734 
1735   slot_span->AppendFreeList(head, tail, size, this,
1736                             this->get_freelist_dispatcher());
1737 }
1738 
RawFreeWithThreadCache(uintptr_t slot_start,SlotSpanMetadata * slot_span)1739 PA_ALWAYS_INLINE void PartitionRoot::RawFreeWithThreadCache(
1740     uintptr_t slot_start,
1741     SlotSpanMetadata* slot_span) {
1742   // PA_LIKELY: performance-sensitive partitions have a thread cache,
1743   // direct-mapped allocations are uncommon.
1744   ThreadCache* thread_cache = GetThreadCache();
1745   if (PA_LIKELY(ThreadCache::IsValid(thread_cache) &&
1746                 !IsDirectMappedBucket(slot_span->bucket))) {
1747     size_t bucket_index =
1748         static_cast<size_t>(slot_span->bucket - this->buckets);
1749     size_t slot_size;
1750     if (PA_LIKELY(thread_cache->MaybePutInCache(slot_start, bucket_index,
1751                                                 &slot_size))) {
1752       // This is a fast path, avoid calling GetSlotUsableSize() in Release
1753       // builds as it is costlier. Copy its small bucket path instead.
1754       PA_DCHECK(!slot_span->CanStoreRawSize());
1755       size_t usable_size = AdjustSizeForExtrasSubtract(slot_size);
1756       PA_DCHECK(usable_size == GetSlotUsableSize(slot_span));
1757       thread_cache->RecordDeallocation(usable_size);
1758       return;
1759     }
1760   }
1761 
1762   if (PA_LIKELY(ThreadCache::IsValid(thread_cache))) {
1763     // Accounting must be done outside `RawFree()`, as it's also called from
1764     // the thread cache. We would double-count otherwise.
1765     //
1766     // GetSlotUsableSize() will always give the correct result, and we are in
1767     // a slow path here (since the thread cache case returned earlier).
1768     size_t usable_size = GetSlotUsableSize(slot_span);
1769     thread_cache->RecordDeallocation(usable_size);
1770   }
1771   RawFree(slot_start, slot_span);
1772 }
1773 
RawFreeLocked(uintptr_t slot_start)1774 PA_ALWAYS_INLINE void PartitionRoot::RawFreeLocked(uintptr_t slot_start) {
1775   SlotSpanMetadata* slot_span = SlotSpanMetadata::FromSlotStart(slot_start);
1776   // Direct-mapped deallocation releases then re-acquires the lock. The caller
1777   // may not expect that, but we never call this function on direct-mapped
1778   // allocations.
1779   PA_DCHECK(!IsDirectMappedBucket(slot_span->bucket));
1780   FreeInSlotSpan(slot_start, slot_span);
1781 }
1782 
FromSlotSpanMetadata(SlotSpanMetadata * slot_span)1783 PA_ALWAYS_INLINE PartitionRoot* PartitionRoot::FromSlotSpanMetadata(
1784     SlotSpanMetadata* slot_span) {
1785   auto* extent_entry = reinterpret_cast<SuperPageExtentEntry*>(
1786       reinterpret_cast<uintptr_t>(slot_span) & internal::SystemPageBaseMask());
1787   return extent_entry->root;
1788 }
1789 
FromFirstSuperPage(uintptr_t super_page)1790 PA_ALWAYS_INLINE PartitionRoot* PartitionRoot::FromFirstSuperPage(
1791     uintptr_t super_page) {
1792   PA_DCHECK(internal::IsReservationStart(super_page));
1793   auto* extent_entry = internal::PartitionSuperPageToExtent(super_page);
1794   PartitionRoot* root = extent_entry->root;
1795   PA_DCHECK(root->inverted_self == ~reinterpret_cast<uintptr_t>(root));
1796   return root;
1797 }
1798 
FromAddrInFirstSuperpage(uintptr_t address)1799 PA_ALWAYS_INLINE PartitionRoot* PartitionRoot::FromAddrInFirstSuperpage(
1800     uintptr_t address) {
1801   uintptr_t super_page = address & internal::kSuperPageBaseMask;
1802   PA_DCHECK(internal::IsReservationStart(super_page));
1803   return FromFirstSuperPage(super_page);
1804 }
1805 
IncreaseTotalSizeOfAllocatedBytes(uintptr_t addr,size_t len,size_t raw_size)1806 PA_ALWAYS_INLINE void PartitionRoot::IncreaseTotalSizeOfAllocatedBytes(
1807     uintptr_t addr,
1808     size_t len,
1809     size_t raw_size) {
1810   total_size_of_allocated_bytes += len;
1811   max_size_of_allocated_bytes =
1812       std::max(max_size_of_allocated_bytes, total_size_of_allocated_bytes);
1813 #if BUILDFLAG(RECORD_ALLOC_INFO)
1814   partition_alloc::internal::RecordAllocOrFree(addr | 0x01, raw_size);
1815 #endif  // BUILDFLAG(RECORD_ALLOC_INFO)
1816 }
1817 
DecreaseTotalSizeOfAllocatedBytes(uintptr_t addr,size_t len)1818 PA_ALWAYS_INLINE void PartitionRoot::DecreaseTotalSizeOfAllocatedBytes(
1819     uintptr_t addr,
1820     size_t len) {
1821   // An underflow here means we've miscounted |total_size_of_allocated_bytes|
1822   // somewhere.
1823   PA_DCHECK(total_size_of_allocated_bytes >= len);
1824   total_size_of_allocated_bytes -= len;
1825 #if BUILDFLAG(RECORD_ALLOC_INFO)
1826   partition_alloc::internal::RecordAllocOrFree(addr | 0x00, len);
1827 #endif  // BUILDFLAG(RECORD_ALLOC_INFO)
1828 }
1829 
IncreaseCommittedPages(size_t len)1830 PA_ALWAYS_INLINE void PartitionRoot::IncreaseCommittedPages(size_t len) {
1831   const auto old_total =
1832       total_size_of_committed_pages.fetch_add(len, std::memory_order_relaxed);
1833 
1834   const auto new_total = old_total + len;
1835 
1836   // This function is called quite frequently; to avoid performance problems, we
1837   // don't want to hold a lock here, so we use compare and exchange instead.
1838   size_t expected = max_size_of_committed_pages.load(std::memory_order_relaxed);
1839   size_t desired;
1840   do {
1841     desired = std::max(expected, new_total);
1842   } while (!max_size_of_committed_pages.compare_exchange_weak(
1843       expected, desired, std::memory_order_relaxed, std::memory_order_relaxed));
1844 }
1845 
DecreaseCommittedPages(size_t len)1846 PA_ALWAYS_INLINE void PartitionRoot::DecreaseCommittedPages(size_t len) {
1847   total_size_of_committed_pages.fetch_sub(len, std::memory_order_relaxed);
1848 }
1849 
DecommitSystemPagesForData(uintptr_t address,size_t length,PageAccessibilityDisposition accessibility_disposition)1850 PA_ALWAYS_INLINE void PartitionRoot::DecommitSystemPagesForData(
1851     uintptr_t address,
1852     size_t length,
1853     PageAccessibilityDisposition accessibility_disposition) {
1854   internal::ScopedSyscallTimer timer{this};
1855   DecommitSystemPages(address, length, accessibility_disposition);
1856   DecreaseCommittedPages(length);
1857 }
1858 
1859 // Not unified with TryRecommitSystemPagesForData() to preserve error codes.
RecommitSystemPagesForData(uintptr_t address,size_t length,PageAccessibilityDisposition accessibility_disposition,bool request_tagging)1860 PA_ALWAYS_INLINE void PartitionRoot::RecommitSystemPagesForData(
1861     uintptr_t address,
1862     size_t length,
1863     PageAccessibilityDisposition accessibility_disposition,
1864     bool request_tagging) {
1865   internal::ScopedSyscallTimer timer{this};
1866 
1867   auto page_accessibility = GetPageAccessibility(request_tagging);
1868   bool ok = TryRecommitSystemPages(address, length, page_accessibility,
1869                                    accessibility_disposition);
1870   if (PA_UNLIKELY(!ok)) {
1871     // Decommit some memory and retry. The alternative is crashing.
1872     DecommitEmptySlotSpans();
1873     RecommitSystemPages(address, length, page_accessibility,
1874                         accessibility_disposition);
1875   }
1876 
1877   IncreaseCommittedPages(length);
1878 }
1879 
1880 template <bool already_locked>
TryRecommitSystemPagesForDataInternal(uintptr_t address,size_t length,PageAccessibilityDisposition accessibility_disposition,bool request_tagging)1881 PA_ALWAYS_INLINE bool PartitionRoot::TryRecommitSystemPagesForDataInternal(
1882     uintptr_t address,
1883     size_t length,
1884     PageAccessibilityDisposition accessibility_disposition,
1885     bool request_tagging) {
1886   internal::ScopedSyscallTimer timer{this};
1887 
1888   auto page_accessibility = GetPageAccessibility(request_tagging);
1889   bool ok = TryRecommitSystemPages(address, length, page_accessibility,
1890                                    accessibility_disposition);
1891   if (PA_UNLIKELY(!ok)) {
1892     {
1893       // Decommit some memory and retry. The alternative is crashing.
1894       if constexpr (!already_locked) {
1895         ::partition_alloc::internal::ScopedGuard guard(
1896             internal::PartitionRootLock(this));
1897         DecommitEmptySlotSpans();
1898       } else {
1899         internal::PartitionRootLock(this).AssertAcquired();
1900         DecommitEmptySlotSpans();
1901       }
1902     }
1903     ok = TryRecommitSystemPages(address, length, page_accessibility,
1904                                 accessibility_disposition);
1905   }
1906 
1907   if (ok) {
1908     IncreaseCommittedPages(length);
1909   }
1910 
1911   return ok;
1912 }
1913 
1914 PA_ALWAYS_INLINE bool
TryRecommitSystemPagesForDataWithAcquiringLock(uintptr_t address,size_t length,PageAccessibilityDisposition accessibility_disposition,bool request_tagging)1915 PartitionRoot::TryRecommitSystemPagesForDataWithAcquiringLock(
1916     uintptr_t address,
1917     size_t length,
1918     PageAccessibilityDisposition accessibility_disposition,
1919     bool request_tagging) {
1920   return TryRecommitSystemPagesForDataInternal<false>(
1921       address, length, accessibility_disposition, request_tagging);
1922 }
1923 
1924 PA_ALWAYS_INLINE
TryRecommitSystemPagesForDataLocked(uintptr_t address,size_t length,PageAccessibilityDisposition accessibility_disposition,bool request_tagging)1925 bool PartitionRoot::TryRecommitSystemPagesForDataLocked(
1926     uintptr_t address,
1927     size_t length,
1928     PageAccessibilityDisposition accessibility_disposition,
1929     bool request_tagging) {
1930   return TryRecommitSystemPagesForDataInternal<true>(
1931       address, length, accessibility_disposition, request_tagging);
1932 }
1933 
1934 // static
1935 //
1936 // Returns the size available to the app. It can be equal or higher than the
1937 // requested size. If higher, the overage won't exceed what's actually usable
1938 // by the app without a risk of running out of an allocated region or into
1939 // PartitionAlloc's internal data. Used as malloc_usable_size and malloc_size.
1940 //
1941 // |ptr| should preferably point to the beginning of an object returned from
1942 // malloc() et al., but it doesn't have to. crbug.com/1292646 shows an example
1943 // where this isn't the case. Note, an inner object pointer won't work for
1944 // direct map, unless it is within the first partition page.
GetUsableSize(void * ptr)1945 PA_ALWAYS_INLINE size_t PartitionRoot::GetUsableSize(void* ptr) {
1946   // malloc_usable_size() is expected to handle NULL gracefully and return 0.
1947   if (!ptr) {
1948     return 0;
1949   }
1950   auto* slot_span = SlotSpanMetadata::FromObjectInnerPtr(ptr);
1951   auto* root = FromSlotSpanMetadata(slot_span);
1952   return root->GetSlotUsableSize(slot_span);
1953 }
1954 
1955 PA_ALWAYS_INLINE size_t
GetUsableSizeWithMac11MallocSizeHack(void * ptr)1956 PartitionRoot::GetUsableSizeWithMac11MallocSizeHack(void* ptr) {
1957   // malloc_usable_size() is expected to handle NULL gracefully and return 0.
1958   if (!ptr) {
1959     return 0;
1960   }
1961   auto* slot_span = SlotSpanMetadata::FromObjectInnerPtr(ptr);
1962   auto* root = FromSlotSpanMetadata(slot_span);
1963   size_t usable_size = root->GetSlotUsableSize(slot_span);
1964 #if PA_CONFIG(MAYBE_ENABLE_MAC11_MALLOC_SIZE_HACK)
1965   // Check |mac11_malloc_size_hack_enabled_| flag first as this doesn't
1966   // concern OS versions other than macOS 11.
1967   if (PA_UNLIKELY(root->settings.mac11_malloc_size_hack_enabled_ &&
1968                   usable_size ==
1969                       root->settings.mac11_malloc_size_hack_usable_size_)) {
1970     auto [slot_start, slot_size] =
1971         internal::PartitionAllocGetSlotStartAndSizeInBRPPool(UntagPtr(ptr));
1972     auto* ref_count =
1973         InSlotMetadataPointerFromSlotStartAndSize(slot_start, slot_size);
1974     if (ref_count->NeedsMac11MallocSizeHack()) {
1975       return internal::kMac11MallocSizeHackRequestedSize;
1976     }
1977   }
1978 #endif  // PA_CONFIG(MAYBE_ENABLE_MAC11_MALLOC_SIZE_HACK)
1979 
1980   return usable_size;
1981 }
1982 
1983 // Returns the page configuration to use when mapping slot spans for a given
1984 // partition root. ReadWriteTagged is used on MTE-enabled systems for
1985 // PartitionRoots supporting it.
1986 PA_ALWAYS_INLINE PageAccessibilityConfiguration
GetPageAccessibility(bool request_tagging)1987 PartitionRoot::GetPageAccessibility(bool request_tagging) const {
1988   PageAccessibilityConfiguration::Permissions permissions =
1989       PageAccessibilityConfiguration::kReadWrite;
1990 #if BUILDFLAG(HAS_MEMORY_TAGGING)
1991   if (IsMemoryTaggingEnabled() && request_tagging) {
1992     permissions = PageAccessibilityConfiguration::kReadWriteTagged;
1993   }
1994 #endif
1995 #if BUILDFLAG(ENABLE_THREAD_ISOLATION)
1996   return PageAccessibilityConfiguration(permissions, settings.thread_isolation);
1997 #else
1998   return PageAccessibilityConfiguration(permissions);
1999 #endif
2000 }
2001 
2002 PA_ALWAYS_INLINE PageAccessibilityConfiguration
PageAccessibilityWithThreadIsolationIfEnabled(PageAccessibilityConfiguration::Permissions permissions)2003 PartitionRoot::PageAccessibilityWithThreadIsolationIfEnabled(
2004     PageAccessibilityConfiguration::Permissions permissions) const {
2005 #if BUILDFLAG(ENABLE_THREAD_ISOLATION)
2006   return PageAccessibilityConfiguration(permissions, settings.thread_isolation);
2007 #endif
2008   return PageAccessibilityConfiguration(permissions);
2009 }
2010 
2011 // Return the capacity of the underlying slot (adjusted for extras). This
2012 // doesn't mean this capacity is readily available. It merely means that if
2013 // a new allocation (or realloc) happened with that returned value, it'd use
2014 // the same amount of underlying memory.
2015 PA_ALWAYS_INLINE size_t
AllocationCapacityFromSlotStart(uintptr_t slot_start)2016 PartitionRoot::AllocationCapacityFromSlotStart(uintptr_t slot_start) const {
2017   auto* slot_span = SlotSpanMetadata::FromSlotStart(slot_start);
2018   return AdjustSizeForExtrasSubtract(slot_span->bucket->slot_size);
2019 }
2020 
2021 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
2022 PA_ALWAYS_INLINE internal::InSlotMetadata*
InSlotMetadataPointerFromSlotStartAndSize(uintptr_t slot_start,size_t slot_size)2023 PartitionRoot::InSlotMetadataPointerFromSlotStartAndSize(uintptr_t slot_start,
2024                                                          size_t slot_size) {
2025   return internal::InSlotMetadataPointer(slot_start, slot_size);
2026 }
2027 
2028 PA_ALWAYS_INLINE internal::InSlotMetadata*
InSlotMetadataPointerFromObjectForTesting(void * object)2029 PartitionRoot::InSlotMetadataPointerFromObjectForTesting(void* object) const {
2030   uintptr_t slot_start = ObjectToSlotStart(object);
2031   auto* slot_span = SlotSpanMetadata::FromSlotStart(slot_start);
2032   return InSlotMetadataPointerFromSlotStartAndSize(
2033       slot_start, slot_span->bucket->slot_size);
2034 }
2035 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
2036 
2037 // static
2038 PA_ALWAYS_INLINE uint16_t
SizeToBucketIndex(size_t size,BucketDistribution bucket_distribution)2039 PartitionRoot::SizeToBucketIndex(size_t size,
2040                                  BucketDistribution bucket_distribution) {
2041   switch (bucket_distribution) {
2042     case BucketDistribution::kNeutral:
2043       return internal::BucketIndexLookup::GetIndexForNeutralBuckets(size);
2044     case BucketDistribution::kDenser:
2045       return internal::BucketIndexLookup::GetIndexForDenserBuckets(size);
2046   }
2047 }
2048 
2049 template <AllocFlags flags>
AllocInternal(size_t requested_size,size_t slot_span_alignment,const char * type_name)2050 PA_ALWAYS_INLINE void* PartitionRoot::AllocInternal(size_t requested_size,
2051                                                     size_t slot_span_alignment,
2052                                                     const char* type_name) {
2053   static_assert(AreValidFlags(flags));
2054   PA_DCHECK((slot_span_alignment >= internal::PartitionPageSize()) &&
2055             std::has_single_bit(slot_span_alignment));
2056   static_assert(!ContainsFlags(
2057       flags, AllocFlags::kMemoryShouldBeTaggedForMte));  // Internal only.
2058 
2059   constexpr bool no_hooks = ContainsFlags(flags, AllocFlags::kNoHooks);
2060   bool hooks_enabled;
2061 
2062   if constexpr (!no_hooks) {
2063     PA_DCHECK(initialized);
2064 
2065 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
2066     if constexpr (!ContainsFlags(flags, AllocFlags::kNoMemoryToolOverride)) {
2067       if (!PartitionRoot::AllocWithMemoryToolProlog<flags>(requested_size)) {
2068         // Early return if AllocWithMemoryToolProlog returns false
2069         return nullptr;
2070       }
2071       constexpr bool zero_fill = ContainsFlags(flags, AllocFlags::kZeroFill);
2072       void* result =
2073           zero_fill ? calloc(1, requested_size) : malloc(requested_size);
2074       if constexpr (!ContainsFlags(flags, AllocFlags::kReturnNull)) {
2075         PA_CHECK(result);
2076       }
2077       return result;
2078     }
2079 #endif  // defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
2080     void* object = nullptr;
2081     hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
2082     if (hooks_enabled) {
2083       auto additional_flags = AllocFlags::kNone;
2084 #if BUILDFLAG(HAS_MEMORY_TAGGING)
2085       if (IsMemoryTaggingEnabled()) {
2086         additional_flags |= AllocFlags::kMemoryShouldBeTaggedForMte;
2087       }
2088 #endif
2089       // The override hooks will return false if it can't handle the request,
2090       // i.e. due to unsupported flags. In this case, we forward the allocation
2091       // request to the default mechanisms.
2092       // TODO(crbug.com/1137393): See if we can make the forwarding more verbose
2093       // to ensure that this situation doesn't go unnoticed.
2094       if (PartitionAllocHooks::AllocationOverrideHookIfEnabled(
2095               &object, flags | additional_flags, requested_size, type_name)) {
2096         PartitionAllocHooks::AllocationObserverHookIfEnabled(
2097             CreateAllocationNotificationData(object, requested_size,
2098                                              type_name));
2099         return object;
2100       }
2101     }
2102   }
2103 
2104   void* const object =
2105       AllocInternalNoHooks<flags>(requested_size, slot_span_alignment);
2106 
2107   if constexpr (!no_hooks) {
2108     if (PA_UNLIKELY(hooks_enabled)) {
2109       PartitionAllocHooks::AllocationObserverHookIfEnabled(
2110           CreateAllocationNotificationData(object, requested_size, type_name));
2111     }
2112   }
2113 
2114   return object;
2115 }
2116 
2117 template <AllocFlags flags>
AllocInternalNoHooks(size_t requested_size,size_t slot_span_alignment)2118 PA_ALWAYS_INLINE void* PartitionRoot::AllocInternalNoHooks(
2119     size_t requested_size,
2120     size_t slot_span_alignment) {
2121   static_assert(AreValidFlags(flags));
2122 
2123   // The thread cache is added "in the middle" of the main allocator, that is:
2124   // - After all the cookie/in-slot metadata management
2125   // - Before the "raw" allocator.
2126   //
2127   // That is, the general allocation flow is:
2128   // 1. Adjustment of requested size to make room for extras
2129   // 2. Allocation:
2130   //   a. Call to the thread cache, if it succeeds, go to step 3.
2131   //   b. Otherwise, call the "raw" allocator <-- Locking
2132   // 3. Handle cookie/in-slot metadata, zero allocation if required
2133 
2134   size_t raw_size = AdjustSizeForExtrasAdd(requested_size);
2135   PA_CHECK(raw_size >= requested_size);  // check for overflows
2136 
2137   // We should only call |SizeToBucketIndex| at most once when allocating.
2138   // Otherwise, we risk having |bucket_distribution| changed
2139   // underneath us (between calls to |SizeToBucketIndex| during the same call),
2140   // which would result in an inconsistent state.
2141   uint16_t bucket_index =
2142       SizeToBucketIndex(raw_size, this->GetBucketDistribution());
2143   size_t usable_size;
2144   bool is_already_zeroed = false;
2145   uintptr_t slot_start = 0;
2146   size_t slot_size = 0;
2147 
2148 #if BUILDFLAG(USE_STARSCAN)
2149   const bool is_quarantine_enabled = IsQuarantineEnabled();
2150   // PCScan safepoint. Call before trying to allocate from cache.
2151   // TODO(bikineev): Change the condition to PA_LIKELY once PCScan is enabled by
2152   // default.
2153   if (PA_UNLIKELY(is_quarantine_enabled)) {
2154     PCScan::JoinScanIfNeeded();
2155   }
2156 #endif  // BUILDFLAG(USE_STARSCAN)
2157 
2158   auto* thread_cache = GetOrCreateThreadCache();
2159 
2160   // Don't use thread cache if higher order alignment is requested, because the
2161   // thread cache will not be able to satisfy it.
2162   //
2163   // PA_LIKELY: performance-sensitive partitions use the thread cache.
2164   if (PA_LIKELY(ThreadCache::IsValid(thread_cache) &&
2165                 slot_span_alignment <= internal::PartitionPageSize())) {
2166     // Note: getting slot_size from the thread cache rather than by
2167     // `buckets[bucket_index].slot_size` to avoid touching `buckets` on the fast
2168     // path.
2169     slot_start = thread_cache->GetFromCache(bucket_index, &slot_size);
2170 
2171     // PA_LIKELY: median hit rate in the thread cache is 95%, from metrics.
2172     if (PA_LIKELY(slot_start)) {
2173       // This follows the logic of SlotSpanMetadata::GetUsableSize for small
2174       // buckets, which is too expensive to call here.
2175       // Keep it in sync!
2176       usable_size = AdjustSizeForExtrasSubtract(slot_size);
2177 
2178 #if BUILDFLAG(PA_DCHECK_IS_ON)
2179       // Make sure that the allocated pointer comes from the same place it would
2180       // for a non-thread cache allocation.
2181       SlotSpanMetadata* slot_span = SlotSpanMetadata::FromSlotStart(slot_start);
2182       DCheckIsValidSlotSpan(slot_span);
2183       PA_DCHECK(slot_span->bucket == &bucket_at(bucket_index));
2184       PA_DCHECK(slot_span->bucket->slot_size == slot_size);
2185       PA_DCHECK(usable_size == GetSlotUsableSize(slot_span));
2186       // All large allocations must go through the RawAlloc path to correctly
2187       // set |usable_size|.
2188       PA_DCHECK(!slot_span->CanStoreRawSize());
2189       PA_DCHECK(!slot_span->bucket->is_direct_mapped());
2190 #endif
2191     } else {
2192       slot_start =
2193           RawAlloc<flags>(buckets + bucket_index, raw_size, slot_span_alignment,
2194                           &usable_size, &slot_size, &is_already_zeroed);
2195     }
2196   } else {
2197     slot_start =
2198         RawAlloc<flags>(buckets + bucket_index, raw_size, slot_span_alignment,
2199                         &usable_size, &slot_size, &is_already_zeroed);
2200   }
2201 
2202   if (PA_UNLIKELY(!slot_start)) {
2203     return nullptr;
2204   }
2205 
2206   if (PA_LIKELY(ThreadCache::IsValid(thread_cache))) {
2207     thread_cache->RecordAllocation(usable_size);
2208   }
2209 
2210   // Layout inside the slot:
2211   //   |...object...|[empty]|[cookie]|[unused]|[metadata]|
2212   //   <----(a)----->
2213   //   <--------(b)--------->
2214   //                        <--(c)--->   +    <---(c)---->
2215   //   <----(d)----->   +   <--(d)--->   +    <---(d)---->
2216   //   <-------------(e)------------->   +    <---(e)---->
2217   //   <-----------------------(f)----------------------->
2218   //     (a) requested_size
2219   //     (b) usable_size
2220   //     (c) extras
2221   //     (d) raw_size
2222   //     (e) utilized_slot_size
2223   //     (f) slot_size
2224   //
2225   // Notes:
2226   // - Cookie exists only in the BUILDFLAG(PA_DCHECK_IS_ON) case.
2227   // - Think of raw_size as the minimum size required internally to satisfy
2228   //   the allocation request (i.e. requested_size + extras)
2229   // - At most one "empty" or "unused" space can occur at a time. They occur
2230   //   when slot_size is larger than raw_size. "unused" applies only to large
2231   //   allocations (direct-mapped and single-slot slot spans) and "empty" only
2232   //   to small allocations.
2233   //   Why either-or, one might ask? We make an effort to put the trailing
2234   //   cookie as close to data as possible to catch overflows (often
2235   //   off-by-one), but that's possible only if we have enough space in metadata
2236   //   to save raw_size, i.e. only for large allocations. For small allocations,
2237   //   we have no other choice than putting the cookie at the very end of the
2238   //   slot, thus creating the "empty" space.
2239   // - Unlike "unused", "empty" counts towards usable_size, because the app can
2240   //   query for it and use this space without a need for reallocation.
2241   // - In-slot metadata may or may not exist in the slot. Currently it exists
2242   //   only when BRP is used.
2243   // - If slot_start is not SystemPageSize()-aligned (possible only for small
2244   //   allocations), in-slot metadata is stored at the end of the slot.
2245   //   Otherwise it is stored in a special table placed after the super page
2246   //   metadata. For simplicity, the space for in-slot metadata is still
2247   //   reserved at the end of the slot, even though redundant.
2248 
2249   void* object = SlotStartToObject(slot_start);
2250 
2251   // Add the cookie after the allocation.
2252   if (settings.use_cookie) {
2253     internal::PartitionCookieWriteValue(static_cast<unsigned char*>(object) +
2254                                         usable_size);
2255   }
2256 
2257   // Fill the region kUninitializedByte (on debug builds, if not requested to 0)
2258   // or 0 (if requested and not 0 already).
2259   constexpr bool zero_fill = ContainsFlags(flags, AllocFlags::kZeroFill);
2260   // PA_LIKELY: operator new() calls malloc(), not calloc().
2261   if constexpr (!zero_fill) {
2262     // memset() can be really expensive.
2263 #if BUILDFLAG(PA_EXPENSIVE_DCHECKS_ARE_ON)
2264     internal::DebugMemset(object, internal::kUninitializedByte, usable_size);
2265 #endif
2266   } else if (!is_already_zeroed) {
2267     memset(object, 0, usable_size);
2268   }
2269 
2270 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
2271   if (PA_LIKELY(brp_enabled())) {
2272     bool needs_mac11_malloc_size_hack = false;
2273 #if PA_CONFIG(MAYBE_ENABLE_MAC11_MALLOC_SIZE_HACK)
2274     // Only apply hack to size 32 allocations on macOS 11. There is a buggy
2275     // assertion that malloc_size() equals sizeof(class_rw_t) which is 32.
2276     if (PA_UNLIKELY(settings.mac11_malloc_size_hack_enabled_ &&
2277                     requested_size ==
2278                         internal::kMac11MallocSizeHackRequestedSize)) {
2279       needs_mac11_malloc_size_hack = true;
2280     }
2281 #endif  // PA_CONFIG(MAYBE_ENABLE_MAC11_MALLOC_SIZE_HACK)
2282     auto* ref_count =
2283         new (InSlotMetadataPointerFromSlotStartAndSize(slot_start, slot_size))
2284             internal::InSlotMetadata(needs_mac11_malloc_size_hack);
2285 #if PA_CONFIG(IN_SLOT_METADATA_STORE_REQUESTED_SIZE)
2286     ref_count->SetRequestedSize(requested_size);
2287 #else
2288     (void)ref_count;
2289 #endif
2290   }
2291 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
2292 
2293 #if BUILDFLAG(USE_STARSCAN)
2294   // TODO(bikineev): Change the condition to PA_LIKELY once PCScan is enabled by
2295   // default.
2296   if (PA_UNLIKELY(is_quarantine_enabled)) {
2297     if (PA_LIKELY(internal::IsManagedByNormalBuckets(slot_start))) {
2298       // Mark the corresponding bits in the state bitmap as allocated.
2299       internal::StateBitmapFromAddr(slot_start)->Allocate(slot_start);
2300     }
2301   }
2302 #endif  // BUILDFLAG(USE_STARSCAN)
2303 
2304   return object;
2305 }
2306 
2307 template <AllocFlags flags>
RawAlloc(Bucket * bucket,size_t raw_size,size_t slot_span_alignment,size_t * usable_size,size_t * slot_size,bool * is_already_zeroed)2308 PA_ALWAYS_INLINE uintptr_t PartitionRoot::RawAlloc(Bucket* bucket,
2309                                                    size_t raw_size,
2310                                                    size_t slot_span_alignment,
2311                                                    size_t* usable_size,
2312                                                    size_t* slot_size,
2313                                                    bool* is_already_zeroed) {
2314   ::partition_alloc::internal::ScopedGuard guard{
2315       internal::PartitionRootLock(this)};
2316   return AllocFromBucket<flags>(bucket, raw_size, slot_span_alignment,
2317                                 usable_size, slot_size, is_already_zeroed);
2318 }
2319 
2320 template <AllocFlags flags>
AlignedAllocInline(size_t alignment,size_t requested_size)2321 PA_ALWAYS_INLINE void* PartitionRoot::AlignedAllocInline(
2322     size_t alignment,
2323     size_t requested_size) {
2324   // Aligned allocation support relies on the natural alignment guarantees of
2325   // PartitionAlloc. Specifically, it relies on the fact that slots within a
2326   // slot span are aligned to slot size, from the beginning of the span.
2327   //
2328   // For alignments <=PartitionPageSize(), the code below adjusts the request
2329   // size to be a power of two, no less than alignment. Since slot spans are
2330   // aligned to PartitionPageSize(), which is also a power of two, this will
2331   // automatically guarantee alignment on the adjusted size boundary, thanks to
2332   // the natural alignment described above.
2333   //
2334   // For alignments >PartitionPageSize(), we need to pass the request down the
2335   // stack to only give us a slot span aligned to this more restrictive
2336   // boundary. In the current implementation, this code path will always
2337   // allocate a new slot span and hand us the first slot, so no need to adjust
2338   // the request size. As a consequence, allocating many small objects with
2339   // such a high alignment can cause a non-negligable fragmentation,
2340   // particularly if these allocations are back to back.
2341   // TODO(bartekn): We should check that this is not causing issues in practice.
2342   //
2343   // This relies on the fact that there are no extras before the allocation, as
2344   // they'd shift the returned allocation from the beginning of the slot, thus
2345   // messing up alignment. Extras after the allocation are acceptable, but they
2346   // have to be taken into account in the request size calculation to avoid
2347   // crbug.com/1185484.
2348 
2349   // This is mandated by |posix_memalign()|, so should never fire.
2350   PA_CHECK(std::has_single_bit(alignment));
2351   // Catch unsupported alignment requests early.
2352   PA_CHECK(alignment <= internal::kMaxSupportedAlignment);
2353   size_t raw_size = AdjustSizeForExtrasAdd(requested_size);
2354 
2355   size_t adjusted_size = requested_size;
2356   if (alignment <= internal::PartitionPageSize()) {
2357     // Handle cases such as size = 16, alignment = 64.
2358     // Wastes memory when a large alignment is requested with a small size, but
2359     // this is hard to avoid, and should not be too common.
2360     if (PA_UNLIKELY(raw_size < alignment)) {
2361       raw_size = alignment;
2362     } else {
2363       // PartitionAlloc only guarantees alignment for power-of-two sized
2364       // allocations. To make sure this applies here, round up the allocation
2365       // size.
2366       raw_size = static_cast<size_t>(1)
2367                  << (int{sizeof(size_t) * 8} - std::countl_zero(raw_size - 1));
2368     }
2369     PA_DCHECK(std::has_single_bit(raw_size));
2370     // Adjust back, because AllocInternalNoHooks/Alloc will adjust it again.
2371     adjusted_size = AdjustSizeForExtrasSubtract(raw_size);
2372 
2373     // Overflow check. adjusted_size must be larger or equal to requested_size.
2374     if (PA_UNLIKELY(adjusted_size < requested_size)) {
2375       if constexpr (ContainsFlags(flags, AllocFlags::kReturnNull)) {
2376         return nullptr;
2377       }
2378       // OutOfMemoryDeathTest.AlignedAlloc requires
2379       // base::TerminateBecauseOutOfMemory (invoked by
2380       // PartitionExcessiveAllocationSize).
2381       internal::PartitionExcessiveAllocationSize(requested_size);
2382       // internal::PartitionExcessiveAllocationSize(size) causes OOM_CRASH.
2383       PA_NOTREACHED();
2384     }
2385   }
2386 
2387   // Slot spans are naturally aligned on partition page size, but make sure you
2388   // don't pass anything less, because it'll mess up callee's calculations.
2389   size_t slot_span_alignment =
2390       std::max(alignment, internal::PartitionPageSize());
2391   void* object =
2392       AllocInternal<flags>(adjusted_size, slot_span_alignment, nullptr);
2393 
2394   // |alignment| is a power of two, but the compiler doesn't necessarily know
2395   // that. A regular % operation is very slow, make sure to use the equivalent,
2396   // faster form.
2397   // No need to MTE-untag, as it doesn't change alignment.
2398   PA_CHECK(!(reinterpret_cast<uintptr_t>(object) & (alignment - 1)));
2399 
2400   return object;
2401 }
2402 
2403 template <AllocFlags alloc_flags, FreeFlags free_flags>
ReallocInline(void * ptr,size_t new_size,const char * type_name)2404 void* PartitionRoot::ReallocInline(void* ptr,
2405                                    size_t new_size,
2406                                    const char* type_name) {
2407 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
2408   if (!PartitionRoot::AllocWithMemoryToolProlog<alloc_flags>(new_size)) {
2409     // Early return if AllocWithMemoryToolProlog returns false
2410     return nullptr;
2411   }
2412   void* result = realloc(ptr, new_size);
2413   if constexpr (!ContainsFlags(alloc_flags, AllocFlags::kReturnNull)) {
2414     PA_CHECK(result);
2415   }
2416   return result;
2417 #else
2418   if (PA_UNLIKELY(!ptr)) {
2419     return AllocInternal<alloc_flags>(new_size, internal::PartitionPageSize(),
2420                                       type_name);
2421   }
2422 
2423   if (PA_UNLIKELY(!new_size)) {
2424     FreeInUnknownRoot<free_flags>(ptr);
2425     return nullptr;
2426   }
2427 
2428   if (new_size > internal::MaxDirectMapped()) {
2429     if constexpr (ContainsFlags(alloc_flags, AllocFlags::kReturnNull)) {
2430       return nullptr;
2431     }
2432     internal::PartitionExcessiveAllocationSize(new_size);
2433   }
2434 
2435   constexpr bool no_hooks = ContainsFlags(alloc_flags, AllocFlags::kNoHooks);
2436   const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled();
2437   bool overridden = false;
2438   size_t old_usable_size;
2439   if (PA_UNLIKELY(!no_hooks && hooks_enabled)) {
2440     overridden = PartitionAllocHooks::ReallocOverrideHookIfEnabled(
2441         &old_usable_size, ptr);
2442   }
2443   if (PA_LIKELY(!overridden)) {
2444     // |ptr| may have been allocated in another root.
2445     SlotSpanMetadata* slot_span = SlotSpanMetadata::FromObject(ptr);
2446     auto* old_root = PartitionRoot::FromSlotSpanMetadata(slot_span);
2447     bool success = false;
2448     bool tried_in_place_for_direct_map = false;
2449     {
2450       ::partition_alloc::internal::ScopedGuard guard{
2451           internal::PartitionRootLock(old_root)};
2452       // TODO(crbug.com/1257655): See if we can afford to make this a CHECK.
2453       DCheckIsValidSlotSpan(slot_span);
2454       old_usable_size = old_root->GetSlotUsableSize(slot_span);
2455 
2456       if (PA_UNLIKELY(slot_span->bucket->is_direct_mapped())) {
2457         tried_in_place_for_direct_map = true;
2458         // We may be able to perform the realloc in place by changing the
2459         // accessibility of memory pages and, if reducing the size, decommitting
2460         // them.
2461         success = old_root->TryReallocInPlaceForDirectMap(slot_span, new_size);
2462       }
2463     }
2464     if (success) {
2465       if (PA_UNLIKELY(!no_hooks && hooks_enabled)) {
2466         PartitionAllocHooks::ReallocObserverHookIfEnabled(
2467             CreateFreeNotificationData(ptr),
2468             CreateAllocationNotificationData(ptr, new_size, type_name));
2469       }
2470       return ptr;
2471     }
2472 
2473     if (PA_LIKELY(!tried_in_place_for_direct_map)) {
2474       if (old_root->TryReallocInPlaceForNormalBuckets(ptr, slot_span,
2475                                                       new_size)) {
2476         return ptr;
2477       }
2478     }
2479   }
2480 
2481   // This realloc cannot be resized in-place. Sadness.
2482   void* ret = AllocInternal<alloc_flags>(
2483       new_size, internal::PartitionPageSize(), type_name);
2484   if (!ret) {
2485     if constexpr (ContainsFlags(alloc_flags, AllocFlags::kReturnNull)) {
2486       return nullptr;
2487     }
2488     internal::PartitionExcessiveAllocationSize(new_size);
2489   }
2490 
2491   memcpy(ret, ptr, std::min(old_usable_size, new_size));
2492   FreeInUnknownRoot<free_flags>(
2493       ptr);  // Implicitly protects the old ptr on MTE systems.
2494   return ret;
2495 #endif
2496 }
2497 
2498 // Return the capacity of the underlying slot (adjusted for extras) that'd be
2499 // used to satisfy a request of |size|. This doesn't mean this capacity would be
2500 // readily available. It merely means that if an allocation happened with that
2501 // returned value, it'd use the same amount of underlying memory as the
2502 // allocation with |size|.
2503 PA_ALWAYS_INLINE size_t
AllocationCapacityFromRequestedSize(size_t size)2504 PartitionRoot::AllocationCapacityFromRequestedSize(size_t size) const {
2505 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
2506   return size;
2507 #else
2508   PA_DCHECK(PartitionRoot::initialized);
2509   size = AdjustSizeForExtrasAdd(size);
2510   auto& bucket = bucket_at(SizeToBucketIndex(size, GetBucketDistribution()));
2511   PA_DCHECK(!bucket.slot_size || bucket.slot_size >= size);
2512   PA_DCHECK(!(bucket.slot_size % internal::kSmallestBucket));
2513 
2514   if (PA_LIKELY(!bucket.is_direct_mapped())) {
2515     size = bucket.slot_size;
2516   } else if (size > internal::MaxDirectMapped()) {
2517     // Too large to allocate => return the size unchanged.
2518   } else {
2519     size = GetDirectMapSlotSize(size);
2520   }
2521   size = AdjustSizeForExtrasSubtract(size);
2522   return size;
2523 #endif
2524 }
2525 
GetOrCreateThreadCache()2526 ThreadCache* PartitionRoot::GetOrCreateThreadCache() {
2527   ThreadCache* thread_cache = nullptr;
2528   if (PA_LIKELY(settings.with_thread_cache)) {
2529     thread_cache = ThreadCache::Get();
2530     if (PA_UNLIKELY(!ThreadCache::IsValid(thread_cache))) {
2531       thread_cache = MaybeInitThreadCache();
2532     }
2533   }
2534   return thread_cache;
2535 }
2536 
GetThreadCache()2537 ThreadCache* PartitionRoot::GetThreadCache() {
2538   return PA_LIKELY(settings.with_thread_cache) ? ThreadCache::Get() : nullptr;
2539 }
2540 
2541 // private.
2542 internal::LightweightQuarantineBranch&
GetSchedulerLoopQuarantineBranch()2543 PartitionRoot::GetSchedulerLoopQuarantineBranch() {
2544   ThreadCache* thread_cache = GetThreadCache();
2545   if (PA_LIKELY(ThreadCache::IsValid(thread_cache))) {
2546     return thread_cache->GetSchedulerLoopQuarantineBranch();
2547   } else {
2548     return *scheduler_loop_quarantine->get();
2549   }
2550 }
2551 
2552 // Explicitly declare common template instantiations to reduce compile time.
2553 #define EXPORT_TEMPLATE                       \
2554   extern template PA_EXPORT_TEMPLATE_DECLARE( \
2555       PA_COMPONENT_EXPORT(PARTITION_ALLOC))
2556 EXPORT_TEMPLATE void* PartitionRoot::Alloc<AllocFlags::kNone>(size_t,
2557                                                               const char*);
2558 EXPORT_TEMPLATE void* PartitionRoot::Alloc<AllocFlags::kReturnNull>(
2559     size_t,
2560     const char*);
2561 EXPORT_TEMPLATE void*
2562 PartitionRoot::Realloc<AllocFlags::kNone, FreeFlags::kNone>(void*,
2563                                                             size_t,
2564                                                             const char*);
2565 EXPORT_TEMPLATE void*
2566 PartitionRoot::Realloc<AllocFlags::kReturnNull, FreeFlags::kNone>(void*,
2567                                                                   size_t,
2568                                                                   const char*);
2569 EXPORT_TEMPLATE void* PartitionRoot::AlignedAlloc<AllocFlags::kNone>(size_t,
2570                                                                      size_t);
2571 #undef EXPORT_TEMPLATE
2572 
2573 #if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
2574 // Usage in `raw_ptr_backup_ref_impl.cc` is notable enough to merit a
2575 // non-internal alias.
2576 using ::partition_alloc::internal::PartitionAllocGetSlotStartAndSizeInBRPPool;
2577 #endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
2578 
2579 #if BUILDFLAG(IS_APPLE) && BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
2580 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
2581 void PartitionAllocMallocHookOnBeforeForkInParent();
2582 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
2583 void PartitionAllocMallocHookOnAfterForkInParent();
2584 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
2585 void PartitionAllocMallocHookOnAfterForkInChild();
2586 #endif  // BUILDFLAG(IS_APPLE) && BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
2587 
2588 }  // namespace partition_alloc
2589 
2590 #endif  // PARTITION_ALLOC_PARTITION_ROOT_H_
2591