#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#define _CRT_SECURE_NO_WARNINGS
#include "base/process/memory.h"
#include <stddef.h>
#include <limits>
#include <tuple>
#include <vector>
#include "base/allocator/allocator_check.h"
#include "base/compiler_specific.h"
#include "base/debug/alias.h"
#include "base/memory/aligned_memory.h"
#include "base/memory/page_size.h"
#include "build/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/page_allocator.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif
#if BUILDFLAG(IS_POSIX)
#include <errno.h>
#endif
#if BUILDFLAG(IS_MAC)
#include <malloc/malloc.h>
#include "base/check_op.h"
#include "base/process/memory_unittest_mac.h"
#include "partition_alloc/shim/allocator_interception_apple.h"
#include "partition_alloc/shim/allocator_shim.h"
#endif
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#include <malloc.h>
#include "base/test/malloc_wrapper.h"
#endif
#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
#endif
#if BUILDFLAG(IS_WIN)
#if defined(COMPILER_MSVC)
#if defined(_WIN64)
typedef __int64 ssize_t;
#else
typedef long ssize_t;
#endif
#endif
typedef BOOL (WINAPI* HeapQueryFn) \
(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T);
#endif
#if BUILDFLAG(IS_MAC)
static void callFree(void *ptr) {
free(ptr);
}
TEST(ProcessMemoryTest, MacTerminateOnHeapCorruption) {
#if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
allocator_shim::InitializeAllocatorShim();
#endif
char buf[9];
asm("" : "=m"(buf));
#if ARCH_CPU_64_BITS
ASSERT_DEATH(callFree(buf), "");
#elif defined(ADDRESS_SANITIZER)
ASSERT_DEATH(callFree(buf), "attempting free on address which "
"was not malloc\\(\\)-ed");
#else
ADD_FAILURE() << "This test is not supported in this build configuration.";
#endif
#if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
allocator_shim::UninterceptMallocZonesForTesting();
#endif
}
#endif
#if PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
TEST(MemoryTest, AllocatorShimWorking) { … }
#endif
#if !BUILDFLAG(IS_OPENBSD) && PA_BUILDFLAG(USE_ALLOCATOR_SHIM) && \
!defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
namespace {
#if BUILDFLAG(IS_WIN)
#define ASSERT_OOM_DEATH …
#else
#define ASSERT_OOM_DEATH(statement) …
#endif
}
class OutOfMemoryTest : public testing::Test { … };
class OutOfMemoryDeathTest : public OutOfMemoryTest { … };
TEST_F(OutOfMemoryDeathTest, New) { … }
TEST_F(OutOfMemoryDeathTest, NewArray) { … }
TEST_F(OutOfMemoryDeathTest, Malloc) { … }
TEST_F(OutOfMemoryDeathTest, Realloc) { … }
TEST_F(OutOfMemoryDeathTest, Calloc) { … }
TEST_F(OutOfMemoryDeathTest, AlignedAlloc) { … }
#if BUILDFLAG(IS_WIN)
TEST_F(OutOfMemoryDeathTest, AlignedRealloc) {
if (ShouldSkipTest()) {
return;
}
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr =
_aligned_realloc(nullptr, test_size_, 8);
});
}
namespace {
constexpr uint32_t kUnhandledExceptionExitCode = 0xBADA55;
LONG WINAPI ExitingUnhandledExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) {
_exit(kUnhandledExceptionExitCode);
}
}
TEST_F(OutOfMemoryDeathTest, NewHandlerGeneratesUnhandledException) {
ASSERT_EXIT(
{
SetUpInDeathAssert();
SetUnhandledExceptionFilter(&ExitingUnhandledExceptionFilter);
[[maybe_unused]] void* volatile ptr = new char[test_size_];
},
testing::ExitedWithCode(kUnhandledExceptionExitCode), "");
}
#endif
#if !BUILDFLAG(IS_MAC) && !defined(COMPONENT_BUILD)
TEST_F(OutOfMemoryDeathTest, SecurityNew) {
if (ShouldSkipTest()) {
return;
}
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr = operator new(insecure_test_size_);
});
}
TEST_F(OutOfMemoryDeathTest, SecurityNewArray) {
if (ShouldSkipTest()) {
return;
}
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr = new char[insecure_test_size_];
});
}
TEST_F(OutOfMemoryDeathTest, SecurityMalloc) {
if (ShouldSkipTest()) {
return;
}
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr = malloc(insecure_test_size_);
});
}
TEST_F(OutOfMemoryDeathTest, SecurityRealloc) {
if (ShouldSkipTest()) {
return;
}
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr = realloc(nullptr, insecure_test_size_);
});
}
TEST_F(OutOfMemoryDeathTest, SecurityCalloc) {
if (ShouldSkipTest()) {
return;
}
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr =
calloc(1024, insecure_test_size_ / 1024L);
});
}
TEST_F(OutOfMemoryDeathTest, SecurityAlignedAlloc) {
if (ShouldSkipTest()) {
return;
}
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr =
base::AlignedAlloc(insecure_test_size_, 8);
});
}
#if BUILDFLAG(IS_WIN)
TEST_F(OutOfMemoryDeathTest, SecurityAlignedRealloc) {
if (ShouldSkipTest()) {
return;
}
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr =
_aligned_realloc(nullptr, insecure_test_size_, 8);
});
}
#endif
#endif
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
TEST_F(OutOfMemoryDeathTest, Valloc) { … }
TEST_F(OutOfMemoryDeathTest, SecurityValloc) { … }
TEST_F(OutOfMemoryDeathTest, Pvalloc) { … }
TEST_F(OutOfMemoryDeathTest, SecurityPvalloc) { … }
TEST_F(OutOfMemoryDeathTest, Memalign) { … }
TEST_F(OutOfMemoryDeathTest, ViaSharedLibraries) { … }
#endif
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID)
TEST_F(OutOfMemoryDeathTest, Posix_memalign) { … }
#endif
#if BUILDFLAG(IS_MAC)
TEST_F(OutOfMemoryDeathTest, MallocPurgeable) {
malloc_zone_t* zone = malloc_default_purgeable_zone();
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr = malloc_zone_malloc(zone, test_size_);
});
}
TEST_F(OutOfMemoryDeathTest, ReallocPurgeable) {
malloc_zone_t* zone = malloc_default_purgeable_zone();
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr =
malloc_zone_realloc(zone, nullptr, test_size_);
});
}
TEST_F(OutOfMemoryDeathTest, CallocPurgeable) {
malloc_zone_t* zone = malloc_default_purgeable_zone();
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr =
malloc_zone_calloc(zone, 1024, test_size_ / 1024L);
});
}
TEST_F(OutOfMemoryDeathTest, VallocPurgeable) {
malloc_zone_t* zone = malloc_default_purgeable_zone();
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr = malloc_zone_valloc(zone, test_size_);
});
}
TEST_F(OutOfMemoryDeathTest, PosixMemalignPurgeable) {
malloc_zone_t* zone = malloc_default_purgeable_zone();
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* volatile ptr =
malloc_zone_memalign(zone, 8, test_size_);
});
}
TEST_F(OutOfMemoryDeathTest, CFAllocatorMalloc) {
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* ptr;
while ((ptr = base::AllocateViaCFAllocatorMalloc(signed_test_size_))) {
}
});
}
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
#define MAYBE_CFAllocatorSystemDefault …
#else
#define MAYBE_CFAllocatorSystemDefault …
#endif
TEST_F(OutOfMemoryDeathTest, MAYBE_CFAllocatorSystemDefault) {
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* ptr;
while (
(ptr = base::AllocateViaCFAllocatorSystemDefault(signed_test_size_))) {
}
});
}
#if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
#define MAYBE_CFAllocatorMallocZone …
#else
#define MAYBE_CFAllocatorMallocZone …
#endif
TEST_F(OutOfMemoryDeathTest, MAYBE_CFAllocatorMallocZone) {
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
[[maybe_unused]] void* ptr;
while ((ptr = base::AllocateViaCFAllocatorMallocZone(signed_test_size_))) {
}
});
}
#endif
class OutOfMemoryHandledTest : public OutOfMemoryTest { … };
#if BUILDFLAG(IS_WIN)
namespace {
DWORD HandleOutOfMemoryException(EXCEPTION_POINTERS* exception_ptrs,
size_t expected_size) {
EXPECT_EQ(base::win::kOomExceptionCode,
exception_ptrs->ExceptionRecord->ExceptionCode);
EXPECT_LE(1U, exception_ptrs->ExceptionRecord->NumberParameters);
EXPECT_EQ(expected_size,
exception_ptrs->ExceptionRecord->ExceptionInformation[0]);
return EXCEPTION_EXECUTE_HANDLER;
}
}
TEST_F(OutOfMemoryTest, TerminateBecauseOutOfMemoryReportsAllocSize) {
#if defined(ARCH_CPU_64_BITS)
const size_t kAttemptedAllocationSize = 0xBADA55F00DULL;
#else
const size_t kAttemptedAllocationSize = 0xBADA55;
#endif
__try {
base::TerminateBecauseOutOfMemory(kAttemptedAllocationSize);
} __except (HandleOutOfMemoryException(GetExceptionInformation(),
kAttemptedAllocationSize)) {
}
}
#endif
#if defined(ARCH_CPU_32_BITS) && \
(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS))
void TestAllocationsReleaseReservation(void* (*alloc_fn)(size_t),
void (*free_fn)(void*)) {
partition_alloc::ReleaseReservation();
base::EnableTerminationOnOutOfMemory();
constexpr size_t kMiB = 1 << 20;
constexpr size_t kReservationSize = 512 * kMiB;
size_t reservation_size = kReservationSize;
while (!partition_alloc::ReserveAddressSpace(reservation_size)) {
reservation_size -= 16 * kMiB;
}
ASSERT_TRUE(partition_alloc::HasReservationForTesting());
ASSERT_GT(reservation_size, 0u);
size_t allocation_size = reservation_size / 2;
std::vector<void*> areas;
areas.reserve(((2 * 4096 * kMiB) / allocation_size) + 1);
while (true) {
void* area = alloc_fn(allocation_size / 2);
ASSERT_TRUE(area);
areas.push_back(area);
if (!partition_alloc::HasReservationForTesting())
break;
}
EXPECT_GE(areas.size(), 2u)
<< "Should be able to allocate without releasing the reservation";
for (void* ptr : areas)
free_fn(ptr);
}
TEST_F(OutOfMemoryHandledTest, MallocReleasesReservation) {
TestAllocationsReleaseReservation(malloc, free);
}
TEST_F(OutOfMemoryHandledTest, NewReleasesReservation) {
TestAllocationsReleaseReservation(
[](size_t size) { return static_cast<void*>(new char[size]); },
[](void* ptr) { delete[] static_cast<char*>(ptr); });
}
#endif
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_UncheckedMallocDies …
#define MAYBE_UncheckedCallocDies …
TEST_F(OutOfMemoryDeathTest, MAYBE_UncheckedMallocDies) {
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
void* data;
std::ignore = base::UncheckedMalloc(test_size_, &data);
});
}
TEST_F(OutOfMemoryDeathTest, MAYBE_UncheckedCallocDies) {
ASSERT_OOM_DEATH({
SetUpInDeathAssert();
void* data;
std::ignore = base::UncheckedCalloc(1, test_size_, &data);
});
}
#else
TEST_F(OutOfMemoryHandledTest, UncheckedMalloc) { … }
TEST_F(OutOfMemoryHandledTest, UncheckedCalloc) { … }
#endif
#endif
#if BUILDFLAG(IS_MAC) && PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
size_t need_a_static_initializer = []() {
void* ptr;
constexpr size_t kRequestedSize = 1000u;
bool ok = base::UncheckedMalloc(kRequestedSize, &ptr);
CHECK(ok);
size_t actual_size = malloc_size(ptr);
CHECK_GE(actual_size, kRequestedSize);
free(ptr);
return actual_size;
}();
#endif