blob: a6f410269f8278e74997732f5e80c015ca030975 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_ADDRESS_POOL_MANAGER_BITMAP_H_
#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_ADDRESS_POOL_MANAGER_BITMAP_H_
#include <array>
#include <atomic>
#include <bitset>
#include <limits>
#include "build/build_config.h"
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
#include "partition_alloc/partition_alloc_base/component_export.h"
#include "partition_alloc/partition_alloc_buildflags.h"
#include "partition_alloc/partition_alloc_check.h"
#include "partition_alloc/partition_alloc_constants.h"
#include "partition_alloc/partition_lock.h"
#if !BUILDFLAG(HAS_64_BIT_POINTERS)
namespace partition_alloc {
namespace internal {
// AddressPoolManagerBitmap is a set of bitmaps that track whether a given
// address is in a pool that supports BackupRefPtr, or in a pool that doesn't
// support it. All PartitionAlloc allocations must be in either of the pools.
//
// This code is specific to 32-bit systems.
class PA_COMPONENT_EXPORT(PARTITION_ALLOC) AddressPoolManagerBitmap {
public:
static constexpr uint64_t kGiB = 1024 * 1024 * 1024ull;
static constexpr uint64_t kAddressSpaceSize = 4ull * kGiB;
// For BRP pool, we use partition page granularity to eliminate the guard
// pages from the bitmap at the ends:
// - Eliminating the guard page at the beginning is needed so that pointers
// to the end of an allocation that immediately precede a super page in BRP
// pool don't accidentally fall into that pool.
// - Eliminating the guard page at the end is to ensure that the last page
// of the address space isn't in the BRP pool. This allows using sentinels
// like reinterpret_cast<void*>(-1) without a risk of triggering BRP logic
// on an invalid address. (Note, 64-bit systems don't have this problem as
// the upper half of the address space always belongs to the OS.)
//
// Note, direct map allocations also belong to this pool. The same logic as
// above applies. It is important to note, however, that the granularity used
// here has to be a minimum of partition page size and direct map allocation
// granularity. Since DirectMapAllocationGranularity() is no smaller than
// PageAllocationGranularity(), we don't need to decrease the bitmap
// granularity any further.
static constexpr size_t kBitShiftOfBRPPoolBitmap = PartitionPageShift();
static constexpr size_t kBytesPer1BitOfBRPPoolBitmap = PartitionPageSize();
static_assert(kBytesPer1BitOfBRPPoolBitmap == 1 << kBitShiftOfBRPPoolBitmap,
"");
static constexpr size_t kGuardOffsetOfBRPPoolBitmap = 1;
static constexpr size_t kGuardBitsOfBRPPoolBitmap = 2;
static constexpr size_t kBRPPoolBits =
kAddressSpaceSize / kBytesPer1BitOfBRPPoolBitmap;
// Regular pool may include both normal bucket and direct map allocations, so
// the bitmap granularity has to be at least as small as
// DirectMapAllocationGranularity(). No need to eliminate guard pages at the
// ends, as this is a BackupRefPtr-specific concern, hence no need to lower
// the granularity to partition page size.
static constexpr size_t kBitShiftOfRegularPoolBitmap =
DirectMapAllocationGranularityShift();
static constexpr size_t kBytesPer1BitOfRegularPoolBitmap =
DirectMapAllocationGranularity();
static_assert(kBytesPer1BitOfRegularPoolBitmap ==
1 << kBitShiftOfRegularPoolBitmap,
"");
static constexpr size_t kRegularPoolBits =
kAddressSpaceSize / kBytesPer1BitOfRegularPoolBitmap;
// Returns false for nullptr.
static bool IsManagedByRegularPool(uintptr_t address) {
static_assert(
std::numeric_limits<uintptr_t>::max() >> kBitShiftOfRegularPoolBitmap <
regular_pool_bits_.size(),
"The bitmap is too small, will result in unchecked out of bounds "
"accesses.");
// It is safe to read |regular_pool_bits_| without a lock since the caller
// is responsible for guaranteeing that the address is inside a valid
// allocation and the deallocation call won't race with this call.
return PA_TS_UNCHECKED_READ(
regular_pool_bits_)[address >> kBitShiftOfRegularPoolBitmap];
}
// Returns false for nullptr.
static bool IsManagedByBRPPool(uintptr_t address) {
static_assert(std::numeric_limits<uintptr_t>::max() >>
kBitShiftOfBRPPoolBitmap < brp_pool_bits_.size(),
"The bitmap is too small, will result in unchecked out of "
"bounds accesses.");
// It is safe to read |brp_pool_bits_| without a lock since the caller
// is responsible for guaranteeing that the address is inside a valid
// allocation and the deallocation call won't race with this call.
return PA_TS_UNCHECKED_READ(
brp_pool_bits_)[address >> kBitShiftOfBRPPoolBitmap];
}
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
static void BanSuperPageFromBRPPool(uintptr_t address) {
brp_forbidden_super_page_map_[address >> kSuperPageShift].store(
true, std::memory_order_relaxed);
}
static bool IsAllowedSuperPageForBRPPool(uintptr_t address) {
// The only potentially dangerous scenario, in which this check is used, is
// when the assignment of the first raw_ptr<T> object for an address
// allocated outside the BRP pool is racing with the allocation of a new
// super page at the same address. We assume that if raw_ptr<T> is being
// initialized with a raw pointer, the associated allocation is "alive";
// otherwise, the issue should be fixed by rewriting the raw pointer
// variable as raw_ptr<T>. In the worst case, when such a fix is
// impossible, we should just undo the raw pointer -> raw_ptr<T> rewrite of
// the problematic field. If the above assumption holds, the existing
// allocation will prevent us from reserving the super-page region and,
// thus, having the race condition. Since we rely on that external
// synchronization, the relaxed memory ordering should be sufficient.
return !brp_forbidden_super_page_map_[address >> kSuperPageShift].load(
std::memory_order_relaxed);
}
static void IncrementBlocklistHitCount() { ++blocklist_hit_count_; }
#endif // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
private:
friend class AddressPoolManager;
static Lock& GetLock();
static std::bitset<kRegularPoolBits> regular_pool_bits_
PA_GUARDED_BY(GetLock());
static std::bitset<kBRPPoolBits> brp_pool_bits_ PA_GUARDED_BY(GetLock());
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
static std::array<std::atomic_bool, kAddressSpaceSize / kSuperPageSize>
brp_forbidden_super_page_map_;
static std::atomic_size_t blocklist_hit_count_;
#endif // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
};
} // namespace internal
// Returns false for nullptr.
PA_ALWAYS_INLINE bool IsManagedByPartitionAlloc(uintptr_t address) {
// When ENABLE_BACKUP_REF_PTR_SUPPORT is off, BRP pool isn't used.
// No need to add IsManagedByConfigurablePool, because Configurable Pool
// doesn't exist on 32-bit.
#if !BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
PA_DCHECK(!internal::AddressPoolManagerBitmap::IsManagedByBRPPool(address));
#endif
return internal::AddressPoolManagerBitmap::IsManagedByRegularPool(address)
#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
|| internal::AddressPoolManagerBitmap::IsManagedByBRPPool(address)
#endif
;
}
// Returns false for nullptr.
PA_ALWAYS_INLINE bool IsManagedByPartitionAllocRegularPool(uintptr_t address) {
return internal::AddressPoolManagerBitmap::IsManagedByRegularPool(address);
}
// Returns false for nullptr.
PA_ALWAYS_INLINE bool IsManagedByPartitionAllocBRPPool(uintptr_t address) {
return internal::AddressPoolManagerBitmap::IsManagedByBRPPool(address);
}
// Returns false for nullptr.
PA_ALWAYS_INLINE bool IsManagedByPartitionAllocConfigurablePool(
uintptr_t address) {
// The Configurable Pool is only available on 64-bit builds.
return false;
}
PA_ALWAYS_INLINE bool IsConfigurablePoolAvailable() {
// The Configurable Pool is only available on 64-bit builds.
return false;
}
} // namespace partition_alloc
#endif // !BUILDFLAG(HAS_64_BIT_POINTERS)
#endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_ADDRESS_POOL_MANAGER_BITMAP_H_