blob: cbc9f99f0e383b8ac7b2a67a6eb85b471e75025d [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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
#pragma allow_unsafe_libc_calls
#endif
#include "base/memory/safety_checks.h"
#include <new>
#include "base/allocator/partition_alloc_features.h"
#include "base/feature_list.h"
#include "partition_alloc/partition_address_space.h"
#include "partition_alloc/tagging.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 object: should have |base::internal::kAdvancedMemorySafetyChecks|.
struct AnotherAdvancedChecks {
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];
};
struct PrivateInheritanceWithInheritMacro : private AdvancedChecks {
INHERIT_MEMORY_SAFETY_CHECKS(AdvancedChecks);
};
static_assert(
is_memory_safety_checked<PrivateInheritanceWithInheritMacro,
MemorySafetyCheck::kForcePartitionAlloc>);
struct PrivateInheritanceWithDefaultMacro : private AdvancedChecks {
DEFAULT_MEMORY_SAFETY_CHECKS();
};
static_assert(
!is_memory_safety_checked<PrivateInheritanceWithDefaultMacro,
MemorySafetyCheck::kForcePartitionAlloc>);
struct MultipleInheritanceWithInheritMacro : AdvancedChecks,
AnotherAdvancedChecks {
INHERIT_MEMORY_SAFETY_CHECKS(AdvancedChecks);
};
static_assert(
is_memory_safety_checked<MultipleInheritanceWithInheritMacro,
MemorySafetyCheck::kForcePartitionAlloc>);
struct MultipleInheritanceWithDefaultMacro : AdvancedChecks,
AnotherAdvancedChecks {
DEFAULT_MEMORY_SAFETY_CHECKS();
};
static_assert(
!is_memory_safety_checked<MultipleInheritanceWithDefaultMacro,
MemorySafetyCheck::kForcePartitionAlloc>);
struct AdvancedChecksWithPartialOverwrite {
ADVANCED_MEMORY_SAFETY_CHECKS(kNone, kForcePartitionAlloc);
public:
char data[16];
};
static_assert(
!is_memory_safety_checked<AdvancedChecksWithPartialOverwrite,
MemorySafetyCheck::kForcePartitionAlloc>);
struct InheritanceWithPartialOverwrite : private AdvancedChecks {
INHERIT_MEMORY_SAFETY_CHECKS(AdvancedChecks, kNone, kForcePartitionAlloc);
};
static_assert(
!is_memory_safety_checked<InheritanceWithPartialOverwrite,
MemorySafetyCheck::kForcePartitionAlloc>);
// 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 PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
EXPECT_TRUE(partition_alloc::IsManagedByPartitionAlloc(
reinterpret_cast<uintptr_t>(partition_alloc::UntagPtr(ptr2))));
#endif // PA_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 PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
EXPECT_TRUE(partition_alloc::IsManagedByPartitionAlloc(
reinterpret_cast<uintptr_t>(partition_alloc::UntagPtr(ptr2))));
#endif // PA_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 PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
EXPECT_TRUE(partition_alloc::IsManagedByPartitionAlloc(
reinterpret_cast<uintptr_t>(partition_alloc::UntagPtr(ptr3))));
#endif // PA_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 PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
TEST(MemorySafetyCheckTest, SchedulerLoopQuarantine) {
// The check is performed only if `kPartitionAllocSchedulerLoopQuarantine` is
// enabled. `base::ScopedFeatureList` does not work here because the default
// `PartitionRoot` is configured before running this test.
if (!base::FeatureList::IsEnabled(
base::features::kPartitionAllocSchedulerLoopQuarantine)) {
return;
}
static_assert(
!is_memory_safety_checked<DefaultChecks,
MemorySafetyCheck::kSchedulerLoopQuarantine>);
static_assert(
is_memory_safety_checked<AdvancedChecks,
MemorySafetyCheck::kSchedulerLoopQuarantine>);
auto* root =
base::internal::GetPartitionRootForMemorySafetyCheckedAllocation();
partition_alloc::internal::
ScopedSchedulerLoopQuarantineBranchAccessorForTesting branch(root);
auto* ptr1 = new DefaultChecks();
ASSERT_NE(ptr1, nullptr);
delete ptr1;
EXPECT_FALSE(branch.IsQuarantined(ptr1));
auto* ptr2 = new AdvancedChecks();
ASSERT_NE(ptr2, nullptr);
memset(ptr2->data, 'A', sizeof(ptr2->data));
delete ptr2;
EXPECT_TRUE(branch.IsQuarantined(ptr2));
// Dereferencing `ptr` is still undefined behavior, but we can say it is
// somewhat defined as this test is gated behind
// `PA_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(ptr2->data[0], 'A');
EXPECT_NE(ptr2->data[15], 'A');
branch.Purge();
}
#endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
} // namespace