blob: 3328510a4f4a34776580833374a9f64e8ab9ab0a [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <new>
#include "base/allocator/partition_allocator/src/partition_alloc/partition_address_space.h"
#include "base/memory/safety_checks.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using base::internal::is_memory_safety_checked;
using base::internal::MemorySafetyCheck;
// Normal object: should be targeted by no additional |MemorySafetyCheck|.
struct DefaultChecks {
public:
char data[16];
};
// Annotated object: should have |base::internal::kAdvancedMemorySafetyChecks|.
struct AdvancedChecks {
ADVANCED_MEMORY_SAFETY_CHECKS();
public:
char data[16];
};
// Annotated and aligned object for testing aligned allocations.
constexpr int kLargeAlignment = 2 * __STDCPP_DEFAULT_NEW_ALIGNMENT__;
struct alignas(kLargeAlignment) AlignedAdvancedChecks {
ADVANCED_MEMORY_SAFETY_CHECKS();
public:
char data[16];
};
// The macro may hook memory allocation/deallocation but should forward the
// request to PA or any other allocator via
// |HandleMemorySafetyCheckedOperator***|.
TEST(MemorySafetyCheckTest, AllocatorFunctions) {
static_assert(
!is_memory_safety_checked<DefaultChecks,
MemorySafetyCheck::kForcePartitionAlloc>);
static_assert(
is_memory_safety_checked<AdvancedChecks,
MemorySafetyCheck::kForcePartitionAlloc>);
static_assert(
is_memory_safety_checked<AlignedAdvancedChecks,
MemorySafetyCheck::kForcePartitionAlloc>);
// void* operator new(std::size_t count);
auto* ptr1 = new DefaultChecks();
auto* ptr2 = new AdvancedChecks();
EXPECT_NE(ptr1, nullptr);
EXPECT_NE(ptr2, nullptr);
// AdvancedChecks is kForcePartitionAlloc.
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
EXPECT_TRUE(partition_alloc::IsManagedByPartitionAlloc(
reinterpret_cast<uintptr_t>(ptr2)));
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// void operator delete(void* ptr);
delete ptr1;
delete ptr2;
// void* operator new(std::size_t count, std::align_val_t alignment)
ptr1 = new (std::align_val_t(64)) DefaultChecks();
ptr2 = new (std::align_val_t(64)) AdvancedChecks();
EXPECT_NE(ptr1, nullptr);
EXPECT_NE(ptr2, nullptr);
// AdvancedChecks is kForcePartitionAlloc.
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
EXPECT_TRUE(partition_alloc::IsManagedByPartitionAlloc(
reinterpret_cast<uintptr_t>(ptr2)));
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// void operator delete(void* ptr, std::align_val_t alignment)
::operator delete(ptr1, std::align_val_t(64));
AdvancedChecks::operator delete(ptr2, std::align_val_t(64));
// void* operator new(std::size_t count, std::align_val_t alignment)
auto* ptr3 = new AlignedAdvancedChecks();
EXPECT_NE(ptr3, nullptr);
// AlignedAdvancedChecks is kForcePartitionAlloc.
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
EXPECT_TRUE(partition_alloc::IsManagedByPartitionAlloc(
reinterpret_cast<uintptr_t>(ptr3)));
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// void operator delete(void* ptr, std::align_val_t alignment)
delete ptr3;
// void* operator new(std::size_t, void* ptr)
alignas(AlignedAdvancedChecks) char data[32];
ptr1 = new (data) DefaultChecks();
ptr2 = new (data) AdvancedChecks();
ptr3 = new (data) AlignedAdvancedChecks();
}
#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
TEST(MemorySafetyCheckTest, SchedulerLoopQuarantine) {
static_assert(
!is_memory_safety_checked<DefaultChecks,
MemorySafetyCheck::kSchedulerLoopQuarantine>);
static_assert(
is_memory_safety_checked<AdvancedChecks,
MemorySafetyCheck::kSchedulerLoopQuarantine>);
constexpr size_t kCapacityInBytes = 1024;
auto* root =
base::internal::GetPartitionRootForMemorySafetyCheckedAllocation();
auto& branch = root->GetSchedulerLoopQuarantineBranchForTesting();
size_t original_capacity_in_bytes = branch.GetRoot().GetCapacityInBytes();
branch.GetRoot().SetCapacityInBytesForTesting(kCapacityInBytes);
auto* ptr1 = new DefaultChecks();
EXPECT_NE(ptr1, nullptr);
delete ptr1;
EXPECT_FALSE(branch.IsQuarantinedForTesting(ptr1));
auto* ptr2 = new AdvancedChecks();
EXPECT_NE(ptr2, nullptr);
delete ptr2;
EXPECT_TRUE(branch.IsQuarantinedForTesting(ptr2));
branch.Purge();
branch.GetRoot().SetCapacityInBytesForTesting(original_capacity_in_bytes);
}
TEST(MemorySafetyCheckTest, ZapOnFree) {
static_assert(
!is_memory_safety_checked<DefaultChecks, MemorySafetyCheck::kZapOnFree>);
static_assert(
is_memory_safety_checked<AdvancedChecks, MemorySafetyCheck::kZapOnFree>);
{
// Without kZapOnFree.
auto* ptr = new DefaultChecks();
EXPECT_NE(ptr, nullptr);
delete ptr;
// *ptr is undefined.
}
{
// With kZapOnFree.
auto* ptr = new AdvancedChecks();
EXPECT_NE(ptr, nullptr);
memset(ptr->data, 'A', sizeof(ptr->data));
delete ptr;
// Dereferencing `ptr` is still undefiner behavior, but we can say it is
// somewhat defined as this test is gated behind
// `BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)`.
// I believe behavior here is concrete enough to be tested, but it can be
// affected by changes in PA. Please disable this test if it flakes.
EXPECT_NE(ptr->data[0], 'A');
}
}
#endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
} // namespace