blob: be07597c15a83b74e860d29471ea06918c4f0d3b [file]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "partition_alloc/gwp_asan_support.h"
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
#if PA_BUILDFLAG(ENABLE_GWP_ASAN_SUPPORT)
#include "partition_alloc/build_config.h"
#include "partition_alloc/in_slot_metadata.h"
#include "partition_alloc/page_allocator_constants.h"
#include "partition_alloc/partition_alloc_base/no_destructor.h"
#include "partition_alloc/partition_alloc_check.h"
#include "partition_alloc/partition_bucket.h"
#include "partition_alloc/partition_lock.h"
#include "partition_alloc/partition_page.h"
#include "partition_alloc/partition_root.h"
namespace partition_alloc {
namespace {
PartitionOptions GwpAsanPartitionOptions() {
PartitionOptions options;
options.backup_ref_ptr = PartitionOptions::kEnabled;
// GWP-ASan does not reserve space for cookies.
options.use_cookie_if_supported = PartitionOptions::kDisabled;
return options;
}
PartitionRoot* RootInstance() {
static internal::base::NoDestructor<PartitionRoot> root(
GwpAsanPartitionOptions());
return root.get();
}
} // namespace
// static
void* GwpAsanSupport::MapRegion(size_t slot_count,
std::vector<uint16_t>& free_list) {
PA_CHECK(slot_count > 0);
static PartitionRoot* root = RootInstance();
const size_t kSlotSize = 2 * internal::SystemPageSize();
uint16_t bucket_index = PartitionRoot::SizeToBucketIndex(
kSlotSize, root->GetBucketDistribution());
auto* bucket = PA_UNSAFE_TODO(root->buckets_ + bucket_index);
const size_t kSuperPagePayloadStartOffset =
internal::SuperPagePayloadStartOffset();
PA_CHECK(kSuperPagePayloadStartOffset % kSlotSize == 0);
const size_t kSuperPageGwpAsanSlotAreaBeginOffset =
kSuperPagePayloadStartOffset;
const size_t kSuperPageGwpAsanSlotAreaEndOffset =
internal::SuperPagePayloadEndOffset();
const size_t kSuperPageGwpAsanSlotAreaSize =
kSuperPageGwpAsanSlotAreaEndOffset - kSuperPageGwpAsanSlotAreaBeginOffset;
const size_t kSlotsPerSlotSpan = bucket->get_bytes_per_span() / kSlotSize;
const size_t kSlotsPerSuperPage =
kSuperPageGwpAsanSlotAreaSize / (kSlotsPerSlotSpan * kSlotSize);
size_t super_page_count = 1 + ((slot_count - 1) / kSlotsPerSuperPage);
PA_CHECK(super_page_count <=
std::numeric_limits<size_t>::max() / kSuperPageSize);
uintptr_t super_page_span_start;
{
internal::ScopedGuard locker{internal::PartitionRootLock(root)};
super_page_span_start = bucket->AllocNewSuperPageSpanForGwpAsan(
root, super_page_count, AllocFlags::kReturnNull);
if (!super_page_span_start) {
return nullptr;
}
#if PA_BUILDFLAG(PA_ARCH_CPU_64_BITS)
// Mapping the GWP-ASan region in to the lower 32-bits of address space
// makes it much more likely that a bad pointer dereference points into
// our region and triggers a false positive report. We rely on the fact
// that PA address pools are never allocated in the first 4GB due to
// their alignment requirements.
PA_CHECK(super_page_span_start >= (1ULL << 32));
#endif // PA_BUILDFLAG(PA_ARCH_CPU_64_BITS)
uintptr_t super_page_span_end =
super_page_span_start + super_page_count * kSuperPageSize;
PA_CHECK(super_page_span_start < super_page_span_end);
for (uintptr_t super_page = super_page_span_start;
super_page < super_page_span_end; super_page += kSuperPageSize) {
auto* page_metadata =
internal::PartitionSuperPageToMetadataArea(super_page, root);
// Index 0 is invalid because it is the super page extent metadata.
for (size_t partition_page_idx = 1;
partition_page_idx + bucket->get_pages_per_slot_span() <
internal::NumPartitionPagesPerSuperPage();
partition_page_idx += bucket->get_pages_per_slot_span()) {
auto* slot_span_metadata =
&PA_UNSAFE_TODO(page_metadata[partition_page_idx])
.slot_span_metadata;
bucket->InitializeSlotSpanForGwpAsan(slot_span_metadata);
auto slot_span_start = internal::SlotSpanMetadata::ToSlotSpanStart(
slot_span_metadata, root);
for (uintptr_t slot_idx = 0; slot_idx < kSlotsPerSlotSpan; ++slot_idx) {
auto slot_start =
slot_span_start.GetNthSlotStart(slot_idx, kSlotSize);
PartitionRoot::InSlotMetadataPointerFromSlotStartAndSize(slot_start,
kSlotSize)
->InitializeForGwpAsan();
size_t global_slot_idx = (slot_start.value() - super_page_span_start -
kSuperPageGwpAsanSlotAreaBeginOffset) /
kSlotSize;
PA_DCHECK(global_slot_idx < std::numeric_limits<uint16_t>::max());
free_list.push_back(global_slot_idx);
if (free_list.size() == slot_count) {
return reinterpret_cast<void*>(
super_page_span_start + kSuperPageGwpAsanSlotAreaBeginOffset -
internal::SystemPageSize()); // Depends on the PA guard region
// in front of the super page
// payload area.
}
}
}
}
}
PA_NOTREACHED();
}
// static
bool GwpAsanSupport::CanReuse(uintptr_t slot_start) {
const size_t kSlotSize = 2 * internal::SystemPageSize();
return PartitionRoot::InSlotMetadataPointerFromSlotStartAndSize(
internal::UntaggedSlotStart::Unchecked(slot_start), kSlotSize)
->CanBeReusedByGwpAsan();
}
// static
void GwpAsanSupport::DestructForTesting() {
static PartitionRoot* root = RootInstance();
root->ResetForTesting(true); // IN-TEST
}
} // namespace partition_alloc
#endif // PA_BUILDFLAG(ENABLE_GWP_ASAN_SUPPORT)