1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/base/allocator/partition_allocator/page_allocator.h"
6 
7 #include <limits.h>
8 
9 #include <atomic>
10 
11 #include "build/build_config.h"
12 #include "third_party/base/allocator/partition_allocator/address_space_randomization.h"
13 #include "third_party/base/allocator/partition_allocator/page_allocator_internal.h"
14 #include "third_party/base/allocator/partition_allocator/spin_lock.h"
15 #include "third_party/base/bits.h"
16 #include "third_party/base/logging.h"
17 #include "third_party/base/numerics/safe_math.h"
18 
19 #if defined(OS_WIN)
20 #include <windows.h>
21 #endif
22 
23 #if defined(OS_WIN)
24 #include "third_party/base/allocator/partition_allocator/page_allocator_internals_win.h"
25 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
26 #include "third_party/base/allocator/partition_allocator/page_allocator_internals_posix.h"
27 #else
28 #error Platform not supported.
29 #endif
30 
31 namespace pdfium {
32 namespace base {
33 
34 namespace {
35 
36 // We may reserve/release address space on different threads.
GetReserveLock()37 subtle::SpinLock* GetReserveLock() {
38   static subtle::SpinLock* s_reserveLock = nullptr;
39   if (!s_reserveLock)
40     s_reserveLock = new subtle::SpinLock();
41   return s_reserveLock;
42 }
43 
44 // We only support a single block of reserved address space.
45 void* s_reservation_address = nullptr;
46 size_t s_reservation_size = 0;
47 
AllocPagesIncludingReserved(void * address,size_t length,PageAccessibilityConfiguration accessibility,PageTag page_tag,bool commit)48 void* AllocPagesIncludingReserved(void* address,
49                                   size_t length,
50                                   PageAccessibilityConfiguration accessibility,
51                                   PageTag page_tag,
52                                   bool commit) {
53   void* ret =
54       SystemAllocPages(address, length, accessibility, page_tag, commit);
55   if (ret == nullptr) {
56     const bool cant_alloc_length = kHintIsAdvisory || address == nullptr;
57     if (cant_alloc_length) {
58       // The system cannot allocate |length| bytes. Release any reserved address
59       // space and try once more.
60       ReleaseReservation();
61       ret = SystemAllocPages(address, length, accessibility, page_tag, commit);
62     }
63   }
64   return ret;
65 }
66 
67 // Trims |base| to given |trim_length| and |alignment|.
68 //
69 // On failure, on Windows, this function returns nullptr and frees |base|.
TrimMapping(void * base,size_t base_length,size_t trim_length,uintptr_t alignment,PageAccessibilityConfiguration accessibility,bool commit)70 void* TrimMapping(void* base,
71                   size_t base_length,
72                   size_t trim_length,
73                   uintptr_t alignment,
74                   PageAccessibilityConfiguration accessibility,
75                   bool commit) {
76   size_t pre_slack = reinterpret_cast<uintptr_t>(base) & (alignment - 1);
77   if (pre_slack) {
78     pre_slack = alignment - pre_slack;
79   }
80   size_t post_slack = base_length - pre_slack - trim_length;
81   DCHECK(base_length >= trim_length || pre_slack || post_slack);
82   DCHECK(pre_slack < base_length);
83   DCHECK(post_slack < base_length);
84   return TrimMappingInternal(base, base_length, trim_length, accessibility,
85                              commit, pre_slack, post_slack);
86 }
87 
88 }  // namespace
89 
SystemAllocPages(void * hint,size_t length,PageAccessibilityConfiguration accessibility,PageTag page_tag,bool commit)90 void* SystemAllocPages(void* hint,
91                        size_t length,
92                        PageAccessibilityConfiguration accessibility,
93                        PageTag page_tag,
94                        bool commit) {
95   DCHECK(!(length & kPageAllocationGranularityOffsetMask));
96   DCHECK(!(reinterpret_cast<uintptr_t>(hint) &
97            kPageAllocationGranularityOffsetMask));
98   DCHECK(commit || accessibility == PageInaccessible);
99   return SystemAllocPagesInternal(hint, length, accessibility, page_tag,
100                                   commit);
101 }
102 
AllocPages(void * address,size_t length,size_t align,PageAccessibilityConfiguration accessibility,PageTag page_tag,bool commit)103 void* AllocPages(void* address,
104                  size_t length,
105                  size_t align,
106                  PageAccessibilityConfiguration accessibility,
107                  PageTag page_tag,
108                  bool commit) {
109   DCHECK(length >= kPageAllocationGranularity);
110   DCHECK(!(length & kPageAllocationGranularityOffsetMask));
111   DCHECK(align >= kPageAllocationGranularity);
112   // Alignment must be power of 2 for masking math to work.
113   DCHECK(pdfium::base::bits::IsPowerOfTwo(align));
114   DCHECK(!(reinterpret_cast<uintptr_t>(address) &
115            kPageAllocationGranularityOffsetMask));
116   uintptr_t align_offset_mask = align - 1;
117   uintptr_t align_base_mask = ~align_offset_mask;
118   DCHECK(!(reinterpret_cast<uintptr_t>(address) & align_offset_mask));
119 
120   // If the client passed null as the address, choose a good one.
121   if (address == nullptr) {
122     address = GetRandomPageBase();
123     address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) &
124                                       align_base_mask);
125   }
126 
127   // First try to force an exact-size, aligned allocation from our random base.
128 #if defined(ARCH_CPU_32_BITS)
129   // On 32 bit systems, first try one random aligned address, and then try an
130   // aligned address derived from the value of |ret|.
131   constexpr int kExactSizeTries = 2;
132 #else
133   // On 64 bit systems, try 3 random aligned addresses.
134   constexpr int kExactSizeTries = 3;
135 #endif
136 
137   for (int i = 0; i < kExactSizeTries; ++i) {
138     void* ret = AllocPagesIncludingReserved(address, length, accessibility,
139                                             page_tag, commit);
140     if (ret != nullptr) {
141       // If the alignment is to our liking, we're done.
142       if (!(reinterpret_cast<uintptr_t>(ret) & align_offset_mask))
143         return ret;
144       // Free the memory and try again.
145       FreePages(ret, length);
146     } else {
147       // |ret| is null; if this try was unhinted, we're OOM.
148       if (kHintIsAdvisory || address == nullptr)
149         return nullptr;
150     }
151 
152 #if defined(ARCH_CPU_32_BITS)
153     // For small address spaces, try the first aligned address >= |ret|. Note
154     // |ret| may be null, in which case |address| becomes null.
155     address = reinterpret_cast<void*>(
156         (reinterpret_cast<uintptr_t>(ret) + align_offset_mask) &
157         align_base_mask);
158 #else  // defined(ARCH_CPU_64_BITS)
159     // Keep trying random addresses on systems that have a large address space.
160     address = GetRandomPageBase();
161     address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) &
162                                       align_base_mask);
163 #endif
164   }
165 
166   // Make a larger allocation so we can force alignment.
167   size_t try_length = length + (align - kPageAllocationGranularity);
168   CHECK(try_length >= length);
169   void* ret;
170 
171   do {
172     // Continue randomizing only on POSIX.
173     address = kHintIsAdvisory ? GetRandomPageBase() : nullptr;
174     ret = AllocPagesIncludingReserved(address, try_length, accessibility,
175                                       page_tag, commit);
176     // The retries are for Windows, where a race can steal our mapping on
177     // resize.
178   } while (ret != nullptr &&
179            (ret = TrimMapping(ret, try_length, length, align, accessibility,
180                               commit)) == nullptr);
181 
182   return ret;
183 }
184 
FreePages(void * address,size_t length)185 void FreePages(void* address, size_t length) {
186   DCHECK(!(reinterpret_cast<uintptr_t>(address) &
187            kPageAllocationGranularityOffsetMask));
188   DCHECK(!(length & kPageAllocationGranularityOffsetMask));
189   FreePagesInternal(address, length);
190 }
191 
TrySetSystemPagesAccess(void * address,size_t length,PageAccessibilityConfiguration accessibility)192 bool TrySetSystemPagesAccess(void* address,
193                              size_t length,
194                              PageAccessibilityConfiguration accessibility) {
195   DCHECK(!(length & kSystemPageOffsetMask));
196   return TrySetSystemPagesAccessInternal(address, length, accessibility);
197 }
198 
SetSystemPagesAccess(void * address,size_t length,PageAccessibilityConfiguration accessibility)199 void SetSystemPagesAccess(void* address,
200                           size_t length,
201                           PageAccessibilityConfiguration accessibility) {
202   DCHECK(!(length & kSystemPageOffsetMask));
203   SetSystemPagesAccessInternal(address, length, accessibility);
204 }
205 
DecommitSystemPages(void * address,size_t length)206 void DecommitSystemPages(void* address, size_t length) {
207   DCHECK_EQ(0UL, length & kSystemPageOffsetMask);
208   DecommitSystemPagesInternal(address, length);
209 }
210 
RecommitSystemPages(void * address,size_t length,PageAccessibilityConfiguration accessibility)211 bool RecommitSystemPages(void* address,
212                          size_t length,
213                          PageAccessibilityConfiguration accessibility) {
214   DCHECK_EQ(0UL, length & kSystemPageOffsetMask);
215   DCHECK(PageInaccessible != accessibility);
216   return RecommitSystemPagesInternal(address, length, accessibility);
217 }
218 
DiscardSystemPages(void * address,size_t length)219 void DiscardSystemPages(void* address, size_t length) {
220   DCHECK_EQ(0UL, length & kSystemPageOffsetMask);
221   DiscardSystemPagesInternal(address, length);
222 }
223 
ReserveAddressSpace(size_t size)224 bool ReserveAddressSpace(size_t size) {
225   // To avoid deadlock, call only SystemAllocPages.
226   subtle::SpinLock::Guard guard(*GetReserveLock());
227   if (s_reservation_address == nullptr) {
228     void* mem = SystemAllocPages(nullptr, size, PageInaccessible,
229                                  PageTag::kChromium, false);
230     if (mem != nullptr) {
231       // We guarantee this alignment when reserving address space.
232       DCHECK(!(reinterpret_cast<uintptr_t>(mem) &
233                kPageAllocationGranularityOffsetMask));
234       s_reservation_address = mem;
235       s_reservation_size = size;
236       return true;
237     }
238   }
239   return false;
240 }
241 
ReleaseReservation()242 void ReleaseReservation() {
243   // To avoid deadlock, call only FreePages.
244   subtle::SpinLock::Guard guard(*GetReserveLock());
245   if (s_reservation_address != nullptr) {
246     FreePages(s_reservation_address, s_reservation_size);
247     s_reservation_address = nullptr;
248     s_reservation_size = 0;
249   }
250 }
251 
GetAllocPageErrorCode()252 uint32_t GetAllocPageErrorCode() {
253   return s_allocPageErrorCode;
254 }
255 
256 }  // namespace base
257 }  // namespace pdfium
258