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