blob: 8b6a7082d7b62a77ef43db23be49e13b3d56b93a [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/allocator/partition_allocator/page_allocator.h"
#include <stdlib.h>
#include <string.h>
#include "base/allocator/partition_allocator/address_space_randomization.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_POSIX)
#include <setjmp.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/time.h>
#include "starboard/types.h"
#endif // defined(OS_POSIX)
#if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
namespace base {
namespace {
// Any number of bytes that can be allocated with no trouble.
constexpr size_t kEasyAllocSize =
(1024 * 1024) & ~(kPageAllocationGranularity - 1);
// A huge amount of memory, greater than or equal to the ASLR space.
constexpr size_t kHugeMemoryAmount =
std::max(internal::kASLRMask, std::size_t{2} * internal::kASLRMask);
} // namespace
TEST(PageAllocatorTest, Rounding) {
EXPECT_EQ(0u, RoundUpToSystemPage(0u));
EXPECT_EQ(kSystemPageSize, RoundUpToSystemPage(1));
EXPECT_EQ(kSystemPageSize, RoundUpToSystemPage(kSystemPageSize - 1));
EXPECT_EQ(kSystemPageSize, RoundUpToSystemPage(kSystemPageSize));
EXPECT_EQ(2 * kSystemPageSize, RoundUpToSystemPage(kSystemPageSize + 1));
EXPECT_EQ(0u, RoundDownToSystemPage(0u));
EXPECT_EQ(0u, RoundDownToSystemPage(kSystemPageSize - 1));
EXPECT_EQ(kSystemPageSize, RoundDownToSystemPage(kSystemPageSize));
EXPECT_EQ(kSystemPageSize, RoundDownToSystemPage(kSystemPageSize + 1));
EXPECT_EQ(kSystemPageSize, RoundDownToSystemPage(2 * kSystemPageSize - 1));
EXPECT_EQ(0u, RoundUpToPageAllocationGranularity(0u));
EXPECT_EQ(kPageAllocationGranularity, RoundUpToPageAllocationGranularity(1));
EXPECT_EQ(kPageAllocationGranularity,
RoundUpToPageAllocationGranularity(kPageAllocationGranularity - 1));
EXPECT_EQ(kPageAllocationGranularity,
RoundUpToPageAllocationGranularity(kPageAllocationGranularity));
EXPECT_EQ(2 * kPageAllocationGranularity,
RoundUpToPageAllocationGranularity(kPageAllocationGranularity + 1));
EXPECT_EQ(0u, RoundDownToPageAllocationGranularity(0u));
EXPECT_EQ(
0u, RoundDownToPageAllocationGranularity(kPageAllocationGranularity - 1));
EXPECT_EQ(kPageAllocationGranularity,
RoundDownToPageAllocationGranularity(kPageAllocationGranularity));
EXPECT_EQ(kPageAllocationGranularity, RoundDownToPageAllocationGranularity(
kPageAllocationGranularity + 1));
EXPECT_EQ(
kPageAllocationGranularity,
RoundDownToPageAllocationGranularity(2 * kPageAllocationGranularity - 1));
}
// Test that failed page allocations invoke base::ReleaseReservation().
// We detect this by making a reservation and ensuring that after failure, we
// can make a new reservation.
TEST(PageAllocatorTest, AllocFailure) {
// Release any reservation made by another test.
ReleaseReservation();
// We can make a reservation.
EXPECT_TRUE(ReserveAddressSpace(kEasyAllocSize));
// We can't make another reservation until we trigger an allocation failure.
EXPECT_FALSE(ReserveAddressSpace(kEasyAllocSize));
size_t size = kHugeMemoryAmount;
// Skip the test for sanitizers and platforms with ASLR turned off.
if (size == 0)
return;
void* result = AllocPages(nullptr, size, kPageAllocationGranularity,
PageInaccessible, PageTag::kChromium, false);
if (result == nullptr) {
// We triggered allocation failure. Our reservation should have been
// released, and we should be able to make a new reservation.
EXPECT_TRUE(ReserveAddressSpace(kEasyAllocSize));
ReleaseReservation();
return;
}
// We couldn't fail. Make sure reservation is still there.
EXPECT_FALSE(ReserveAddressSpace(kEasyAllocSize));
}
// TODO(crbug.com/765801): Test failed on chromium.win/Win10 Tests x64.
#if defined(OS_WIN) && defined(ARCH_CPU_64_BITS)
#define MAYBE_ReserveAddressSpace DISABLED_ReserveAddressSpace
#else
#define MAYBE_ReserveAddressSpace ReserveAddressSpace
#endif // defined(OS_WIN) && defined(ARCH_CPU_64_BITS)
// Test that reserving address space can fail.
TEST(PageAllocatorTest, MAYBE_ReserveAddressSpace) {
// Release any reservation made by another test.
ReleaseReservation();
size_t size = kHugeMemoryAmount;
// Skip the test for sanitizers and platforms with ASLR turned off.
if (size == 0)
return;
bool success = ReserveAddressSpace(size);
if (!success) {
EXPECT_TRUE(ReserveAddressSpace(kEasyAllocSize));
return;
}
// We couldn't fail. Make sure reservation is still there.
EXPECT_FALSE(ReserveAddressSpace(kEasyAllocSize));
}
TEST(PageAllocatorTest, AllocAndFreePages) {
void* buffer = AllocPages(nullptr, kPageAllocationGranularity,
kPageAllocationGranularity, PageReadWrite,
PageTag::kChromium, true);
EXPECT_TRUE(buffer);
int* buffer0 = reinterpret_cast<int*>(buffer);
*buffer0 = 42;
EXPECT_EQ(42, *buffer0);
FreePages(buffer, kPageAllocationGranularity);
}
// Test permission setting on POSIX, where we can set a trap handler.
#if defined(OS_POSIX)
namespace {
sigjmp_buf g_continuation;
void SignalHandler(int signal, siginfo_t* info, void*) {
siglongjmp(g_continuation, 1);
}
} // namespace
// On Mac, sometimes we get SIGBUS instead of SIGSEGV, so handle that too.
#if defined(OS_MACOSX)
#define EXTRA_FAULT_BEGIN_ACTION() \
struct sigaction old_bus_action; \
sigaction(SIGBUS, &action, &old_bus_action);
#define EXTRA_FAULT_END_ACTION() sigaction(SIGBUS, &old_bus_action, nullptr);
#else
#define EXTRA_FAULT_BEGIN_ACTION()
#define EXTRA_FAULT_END_ACTION()
#endif
// Install a signal handler so we can catch the fault we're about to trigger.
#define FAULT_TEST_BEGIN() \
struct sigaction action = {}; \
struct sigaction old_action = {}; \
action.sa_sigaction = SignalHandler; \
sigemptyset(&action.sa_mask); \
action.sa_flags = SA_SIGINFO; \
sigaction(SIGSEGV, &action, &old_action); \
EXTRA_FAULT_BEGIN_ACTION(); \
int const save_sigs = 1; \
if (!sigsetjmp(g_continuation, save_sigs)) {
// Fault generating code goes here...
// Handle when sigsetjmp returns nonzero (we are returning from our handler).
#define FAULT_TEST_END() \
} \
else { \
sigaction(SIGSEGV, &old_action, nullptr); \
EXTRA_FAULT_END_ACTION(); \
}
TEST(PageAllocatorTest, InaccessiblePages) {
void* buffer = AllocPages(nullptr, kPageAllocationGranularity,
kPageAllocationGranularity, PageInaccessible,
PageTag::kChromium, true);
EXPECT_TRUE(buffer);
FAULT_TEST_BEGIN();
// Reading from buffer should fault.
int* buffer0 = reinterpret_cast<int*>(buffer);
int buffer0_contents = *buffer0;
EXPECT_EQ(buffer0_contents, *buffer0);
EXPECT_TRUE(false);
FAULT_TEST_END();
FreePages(buffer, kPageAllocationGranularity);
}
TEST(PageAllocatorTest, ReadExecutePages) {
void* buffer = AllocPages(nullptr, kPageAllocationGranularity,
kPageAllocationGranularity, PageReadExecute,
PageTag::kChromium, true);
EXPECT_TRUE(buffer);
int* buffer0 = reinterpret_cast<int*>(buffer);
// Reading from buffer should succeed.
int buffer0_contents = *buffer0;
FAULT_TEST_BEGIN();
// Writing to buffer should fault.
*buffer0 = ~buffer0_contents;
EXPECT_TRUE(false);
FAULT_TEST_END();
// Make sure no write occurred.
EXPECT_EQ(buffer0_contents, *buffer0);
FreePages(buffer, kPageAllocationGranularity);
}
#endif // defined(OS_POSIX)
} // namespace base
#endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)