// Copyright (c) 2013 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 "third_party/base/allocator/partition_allocator/page_allocator.h" #include #include #include "build/build_config.h" #include "third_party/base/allocator/partition_allocator/address_space_randomization.h" #include "third_party/base/allocator/partition_allocator/page_allocator_internal.h" #include "third_party/base/allocator/partition_allocator/spin_lock.h" #include "third_party/base/logging.h" #include "third_party/base/numerics/safe_math.h" #if defined(OS_WIN) #include #endif #if defined(OS_WIN) #include "third_party/base/allocator/partition_allocator/page_allocator_internals_win.h" #elif defined(OS_POSIX) || defined(OS_FUCHSIA) #include "third_party/base/allocator/partition_allocator/page_allocator_internals_posix.h" #else #error Platform not supported. #endif namespace pdfium { namespace base { namespace { // We may reserve/release address space on different threads. subtle::SpinLock* GetReserveLock() { static subtle::SpinLock* s_reserveLock = nullptr; if (!s_reserveLock) s_reserveLock = new subtle::SpinLock(); return s_reserveLock; } // We only support a single block of reserved address space. void* s_reservation_address = nullptr; size_t s_reservation_size = 0; void* AllocPagesIncludingReserved(void* address, size_t length, PageAccessibilityConfiguration accessibility, PageTag page_tag, bool commit) { void* ret = SystemAllocPages(address, length, accessibility, page_tag, commit); if (ret == nullptr) { const bool cant_alloc_length = kHintIsAdvisory || address == nullptr; if (cant_alloc_length) { // The system cannot allocate |length| bytes. Release any reserved address // space and try once more. ReleaseReservation(); ret = SystemAllocPages(address, length, accessibility, page_tag, commit); } } return ret; } // Trims |base| to given |trim_length| and |alignment|. // // On failure, on Windows, this function returns nullptr and frees |base|. void* TrimMapping(void* base, size_t base_length, size_t trim_length, uintptr_t alignment, PageAccessibilityConfiguration accessibility, bool commit) { size_t pre_slack = reinterpret_cast(base) & (alignment - 1); if (pre_slack) { pre_slack = alignment - pre_slack; } size_t post_slack = base_length - pre_slack - trim_length; DCHECK(base_length >= trim_length || pre_slack || post_slack); DCHECK(pre_slack < base_length); DCHECK(post_slack < base_length); return TrimMappingInternal(base, base_length, trim_length, accessibility, commit, pre_slack, post_slack); } } // namespace void* SystemAllocPages(void* hint, size_t length, PageAccessibilityConfiguration accessibility, PageTag page_tag, bool commit) { DCHECK(!(length & kPageAllocationGranularityOffsetMask)); DCHECK(!(reinterpret_cast(hint) & kPageAllocationGranularityOffsetMask)); DCHECK(commit || accessibility == PageInaccessible); return SystemAllocPagesInternal(hint, length, accessibility, page_tag, commit); } void* AllocPages(void* address, size_t length, size_t align, PageAccessibilityConfiguration accessibility, PageTag page_tag, bool commit) { DCHECK(length >= kPageAllocationGranularity); DCHECK(!(length & kPageAllocationGranularityOffsetMask)); DCHECK(align >= kPageAllocationGranularity); // Alignment must be power of 2 for masking math to work. DCHECK_EQ(align & (align - 1), 0UL); DCHECK(!(reinterpret_cast(address) & kPageAllocationGranularityOffsetMask)); uintptr_t align_offset_mask = align - 1; uintptr_t align_base_mask = ~align_offset_mask; DCHECK(!(reinterpret_cast(address) & align_offset_mask)); #if defined(OS_LINUX) && defined(ARCH_CPU_64_BITS) // On 64 bit Linux, we may need to adjust the address space limit for // guarded allocations. if (length >= kMinimumGuardedMemorySize) { CHECK(PageInaccessible == accessibility); CHECK(!commit); if (!AdjustAddressSpaceLimit(base::checked_cast(length))) { // Fall through. Try the allocation, since we may have a reserve. } } #endif // If the client passed null as the address, choose a good one. if (address == nullptr) { address = GetRandomPageBase(); address = reinterpret_cast(reinterpret_cast(address) & align_base_mask); } // First try to force an exact-size, aligned allocation from our random base. #if defined(ARCH_CPU_32_BITS) // On 32 bit systems, first try one random aligned address, and then try an // aligned address derived from the value of |ret|. constexpr int kExactSizeTries = 2; #else // On 64 bit systems, try 3 random aligned addresses. constexpr int kExactSizeTries = 3; #endif for (int i = 0; i < kExactSizeTries; ++i) { void* ret = AllocPagesIncludingReserved(address, length, accessibility, page_tag, commit); if (ret != nullptr) { // If the alignment is to our liking, we're done. if (!(reinterpret_cast(ret) & align_offset_mask)) return ret; // Free the memory and try again. FreePages(ret, length); } else { // |ret| is null; if this try was unhinted, we're OOM. if (kHintIsAdvisory || address == nullptr) return nullptr; } #if defined(ARCH_CPU_32_BITS) // For small address spaces, try the first aligned address >= |ret|. Note // |ret| may be null, in which case |address| becomes null. address = reinterpret_cast( (reinterpret_cast(ret) + align_offset_mask) & align_base_mask); #else // defined(ARCH_CPU_64_BITS) // Keep trying random addresses on systems that have a large address space. address = GetRandomPageBase(); address = reinterpret_cast(reinterpret_cast(address) & align_base_mask); #endif } // Make a larger allocation so we can force alignment. size_t try_length = length + (align - kPageAllocationGranularity); CHECK(try_length >= length); void* ret; do { // Continue randomizing only on POSIX. address = kHintIsAdvisory ? GetRandomPageBase() : nullptr; ret = AllocPagesIncludingReserved(address, try_length, accessibility, page_tag, commit); // The retries are for Windows, where a race can steal our mapping on // resize. } while (ret != nullptr && (ret = TrimMapping(ret, try_length, length, align, accessibility, commit)) == nullptr); return ret; } void FreePages(void* address, size_t length) { DCHECK(!(reinterpret_cast(address) & kPageAllocationGranularityOffsetMask)); DCHECK(!(length & kPageAllocationGranularityOffsetMask)); FreePagesInternal(address, length); } bool SetSystemPagesAccess(void* address, size_t length, PageAccessibilityConfiguration accessibility) { DCHECK(!(length & kSystemPageOffsetMask)); return SetSystemPagesAccessInternal(address, length, accessibility); } void DecommitSystemPages(void* address, size_t length) { DCHECK_EQ(0UL, length & kSystemPageOffsetMask); DecommitSystemPagesInternal(address, length); } bool RecommitSystemPages(void* address, size_t length, PageAccessibilityConfiguration accessibility) { DCHECK_EQ(0UL, length & kSystemPageOffsetMask); DCHECK(PageInaccessible != accessibility); return RecommitSystemPagesInternal(address, length, accessibility); } void DiscardSystemPages(void* address, size_t length) { DCHECK_EQ(0UL, length & kSystemPageOffsetMask); DiscardSystemPagesInternal(address, length); } bool ReserveAddressSpace(size_t size) { // To avoid deadlock, call only SystemAllocPages. subtle::SpinLock::Guard guard(*GetReserveLock()); if (s_reservation_address == nullptr) { void* mem = SystemAllocPages(nullptr, size, PageInaccessible, PageTag::kChromium, false); if (mem != nullptr) { // We guarantee this alignment when reserving address space. DCHECK(!(reinterpret_cast(mem) & kPageAllocationGranularityOffsetMask)); s_reservation_address = mem; s_reservation_size = size; return true; } } return false; } void ReleaseReservation() { // To avoid deadlock, call only FreePages. subtle::SpinLock::Guard guard(*GetReserveLock()); if (s_reservation_address != nullptr) { FreePages(s_reservation_address, s_reservation_size); s_reservation_address = nullptr; s_reservation_size = 0; } } uint32_t GetAllocPageErrorCode() { return s_allocPageErrorCode; } } // namespace base } // namespace pdfium