| // 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) |