1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_H_
6 #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_H_
7
8 #include <cstddef>
9 #include <cstdint>
10
11 #include "base/allocator/partition_allocator/page_allocator_constants.h"
12 #include "base/allocator/partition_allocator/partition_alloc_base/compiler_specific.h"
13 #include "base/allocator/partition_allocator/partition_alloc_base/component_export.h"
14 #include "base/allocator/partition_allocator/partition_alloc_buildflags.h"
15 #include "build/build_config.h"
16
17 namespace partition_alloc {
18
19 struct PageAccessibilityConfiguration {
20 enum Permissions {
21 kInaccessible,
22 kRead,
23 kReadWrite,
24 // This flag is mapped to kReadWrite on systems that
25 // don't support MTE.
26 kReadWriteTagged,
27 // This flag is mapped to kReadExecute on systems
28 // that don't support Arm's BTI.
29 kReadExecuteProtected,
30 kReadExecute,
31 // This flag is deprecated and will go away soon.
32 // TODO(bbudge) Remove this as soon as V8 doesn't need RWX pages.
33 kReadWriteExecute,
34 };
35
36 #if BUILDFLAG(ENABLE_PKEYS)
PageAccessibilityConfigurationPageAccessibilityConfiguration37 constexpr explicit PageAccessibilityConfiguration(Permissions permissions)
38 : permissions(permissions), pkey(0) {}
PageAccessibilityConfigurationPageAccessibilityConfiguration39 constexpr PageAccessibilityConfiguration(Permissions permissions, int pkey)
40 : permissions(permissions), pkey(pkey) {}
41 #else
PageAccessibilityConfigurationPageAccessibilityConfiguration42 constexpr explicit PageAccessibilityConfiguration(Permissions permissions)
43 : permissions(permissions) {}
44 #endif // BUILDFLAG(ENABLE_PKEYS)
45
46 Permissions permissions;
47 #if BUILDFLAG(ENABLE_PKEYS)
48 // Tag the page with a Memory Protection Key. Use 0 for none.
49 int pkey;
50 #endif // BUILDFLAG(ENABLE_PKEYS)
51 };
52
53 // Use for De/RecommitSystemPages API.
54 enum class PageAccessibilityDisposition {
55 // Enforces permission update (Decommit will set to
56 // PageAccessibilityConfiguration::kInaccessible;
57 // Recommit will set to whatever was requested, other than
58 // PageAccessibilityConfiguration::kInaccessible).
59 kRequireUpdate,
60 // Will not update permissions, if the platform supports that (POSIX & Fuchsia
61 // only).
62 kAllowKeepForPerf,
63 };
64
65 // macOS supports tagged memory regions, to help in debugging. On Android,
66 // these tags are used to name anonymous mappings.
67 enum class PageTag {
68 kFirst = 240, // Minimum tag value.
69 kSimulation = 251, // Memory simulator tool.
70 kBlinkGC = 252, // Blink GC pages.
71 kPartitionAlloc = 253, // PartitionAlloc, no matter the partition.
72 kChromium = 254, // Chromium page.
73 kV8 = 255, // V8 heap pages.
74 kLast = kV8 // Maximum tag value.
75 };
76
77 // See
78 // https://github.com/apple-oss-distributions/xnu/blob/5c2921b07a2480ab43ec66f5b9e41cb872bc554f/osfmk/mach/vm_statistics.h#L687
79 static_assert(
80 static_cast<int>(PageTag::kLast) < 256,
81 "Tags are only 1 byte long on macOS, see vm_statistics.h in XNU.");
82
83 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
84 uintptr_t NextAlignedWithOffset(uintptr_t ptr,
85 uintptr_t alignment,
86 uintptr_t requested_offset);
87
88 // Allocates one or more pages.
89 //
90 // The requested |address| is just a hint; the actual address returned may
91 // differ. The returned address will be aligned to |align_offset| modulo |align|
92 // bytes.
93 //
94 // |length|, |align| and |align_offset| are in bytes, and must be a multiple of
95 // |PageAllocationGranularity()|. |length| and |align| must be non-zero.
96 // |align_offset| must be less than |align|. |align| must be a power of two.
97 //
98 // If |address| is 0/nullptr, then a suitable and randomized address will be
99 // chosen automatically.
100 //
101 // |accessibility| controls the permission of the allocated pages.
102 // PageAccessibilityConfiguration::kInaccessible means uncommitted.
103 //
104 // |page_tag| is used on some platforms to identify the source of the
105 // allocation. Use PageTag::kChromium as a catch-all category.
106 //
107 // |file_descriptor_for_shared_alloc| is only used in mapping the shadow
108 // pools to the same physical address as the real one in
109 // PartitionAddressSpace::Init(). It should be ignored in other cases.
110 //
111 // This call will return 0/nullptr if the allocation cannot be satisfied.
112 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
113 uintptr_t AllocPages(size_t length,
114 size_t align,
115 PageAccessibilityConfiguration accessibility,
116 PageTag page_tag,
117 int file_descriptor_for_shared_alloc = -1);
118 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
119 uintptr_t AllocPages(uintptr_t address,
120 size_t length,
121 size_t align,
122 PageAccessibilityConfiguration accessibility,
123 PageTag page_tag);
124 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
125 void* AllocPages(void* address,
126 size_t length,
127 size_t align,
128 PageAccessibilityConfiguration accessibility,
129 PageTag page_tag);
130 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
131 uintptr_t AllocPagesWithAlignOffset(
132 uintptr_t address,
133 size_t length,
134 size_t align,
135 size_t align_offset,
136 PageAccessibilityConfiguration page_accessibility,
137 PageTag page_tag,
138 int file_descriptor_for_shared_alloc = -1);
139
140 // Frees one or more pages starting at |address| and continuing for |length|
141 // bytes.
142 //
143 // |address| and |length| must match a previous call to |AllocPages|. Therefore,
144 // |address| must be aligned to |PageAllocationGranularity()| bytes, and
145 // |length| must be a multiple of |PageAllocationGranularity()|.
146 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
147 void FreePages(uintptr_t address, size_t length);
148 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
149 void FreePages(void* address, size_t length);
150
151 // Marks one or more system pages, starting at |address| with the given
152 // |page_accessibility|. |length| must be a multiple of |SystemPageSize()|
153 // bytes.
154 //
155 // Returns true if the permission change succeeded. In most cases you must
156 // |CHECK| the result.
157 [[nodiscard]] PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool TrySetSystemPagesAccess(
158 uintptr_t address,
159 size_t length,
160 PageAccessibilityConfiguration page_accessibility);
161 [[nodiscard]] PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool TrySetSystemPagesAccess(
162 void* address,
163 size_t length,
164 PageAccessibilityConfiguration page_accessibility);
165
166 // Marks one or more system pages, starting at |address| with the given
167 // |page_accessibility|. |length| must be a multiple of |SystemPageSize()|
168 // bytes.
169 //
170 // Performs a CHECK that the operation succeeds.
171 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
172 void SetSystemPagesAccess(uintptr_t address,
173 size_t length,
174 PageAccessibilityConfiguration page_accessibility);
175 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
176 void SetSystemPagesAccess(void* address,
177 size_t length,
178 PageAccessibilityConfiguration page_accessibility);
179
180 // Decommits one or more system pages starting at |address| and continuing for
181 // |length| bytes. |address| and |length| must be aligned to a system page
182 // boundary.
183 //
184 // This API will crash if the operation cannot be performed!
185 //
186 // If disposition is PageAccessibilityDisposition::kRequireUpdate (recommended),
187 // the decommitted pages will be made inaccessible before the call returns.
188 // While it is always a programming error to access decommitted pages without
189 // first recommitting them, callers may use
190 // PageAccessibilityDisposition::kAllowKeepForPerf to allow the implementation
191 // to skip changing permissions (use with care), for performance reasons (see
192 // crrev.com/c/2567282 and crrev.com/c/2563038 for perf regressions encountered
193 // in the past). Implementations may choose to always modify permissions, hence
194 // accessing those pages may or may not trigger a fault.
195 //
196 // Decommitting means that physical resources (RAM or swap/pagefile) backing the
197 // allocated virtual address range may be released back to the system, but the
198 // address space is still allocated to the process (possibly using up page table
199 // entries or other accounting resources). There is no guarantee that the pages
200 // are zeroed, unless |DecommittedMemoryIsAlwaysZeroed()| is true.
201 //
202 // This operation may not be atomic on some platforms.
203 //
204 // Note: "Committed memory" is a Windows Memory Subsystem concept that ensures
205 // processes will not fault when touching a committed memory region. There is
206 // no analogue in the POSIX & Fuchsia memory API where virtual memory pages are
207 // best-effort allocated resources on the first touch. If
208 // PageAccessibilityDisposition::kRequireUpdate disposition is used, this API
209 // behaves in a platform-agnostic way by simulating the Windows "decommit" state
210 // by both discarding the region (allowing the OS to avoid swap operations)
211 // *and* changing the page protections so accesses fault.
212 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
213 void DecommitSystemPages(
214 uintptr_t address,
215 size_t length,
216 PageAccessibilityDisposition accessibility_disposition);
217 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
218 void DecommitSystemPages(
219 void* address,
220 size_t length,
221 PageAccessibilityDisposition accessibility_disposition);
222
223 // Decommits one or more system pages starting at |address| and continuing for
224 // |length| bytes. |address| and |length| must be aligned to a system page
225 // boundary.
226 //
227 // In contrast to |DecommitSystemPages|, this API guarantees that the pages are
228 // zeroed and will always mark the region as inaccessible (the equivalent of
229 // setting them to PageAccessibilityConfiguration::kInaccessible).
230 //
231 // This API will crash if the operation cannot be performed.
232 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
233 void DecommitAndZeroSystemPages(uintptr_t address, size_t length);
234 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
235 void DecommitAndZeroSystemPages(void* address, size_t length);
236
237 // Whether decommitted memory is guaranteed to be zeroed when it is
238 // recommitted. Do not assume that this will not change over time.
PA_COMPONENT_EXPORT(PARTITION_ALLOC)239 constexpr PA_COMPONENT_EXPORT(
240 PARTITION_ALLOC) bool DecommittedMemoryIsAlwaysZeroed() {
241 #if BUILDFLAG(IS_APPLE)
242 return false;
243 #else
244 return true;
245 #endif
246 }
247
248 // (Re)Commits one or more system pages, starting at |address| and continuing
249 // for |length| bytes with the given |page_accessibility| (must not be
250 // PageAccessibilityConfiguration::kInaccessible). |address| and |length|
251 // must be aligned to a system page boundary.
252 //
253 // This API will crash if the operation cannot be performed!
254 //
255 // If disposition is PageAccessibilityConfiguration::kRequireUpdate, the calls
256 // updates the pages to |page_accessibility|. This can be used regardless of
257 // what disposition was used to decommit the pages.
258 // PageAccessibilityConfiguration::kAllowKeepForPerf allows the implementation
259 // to leave the page permissions, if that improves performance. This option can
260 // only be used if the pages were previously accessible and decommitted with
261 // that same option.
262 //
263 // The memory will be zeroed when it is committed for the first time. However,
264 // there is no such guarantee when memory is recommitted, unless
265 // |DecommittedMemoryIsAlwaysZeroed()| is true.
266 //
267 // This operation may not be atomic on some platforms.
268 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
269 void RecommitSystemPages(
270 uintptr_t address,
271 size_t length,
272 PageAccessibilityConfiguration page_accessibility,
273 PageAccessibilityDisposition accessibility_disposition);
274
275 // Like RecommitSystemPages(), but returns false instead of crashing.
276 [[nodiscard]] PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool TryRecommitSystemPages(
277 uintptr_t address,
278 size_t length,
279 PageAccessibilityConfiguration page_accessibility,
280 PageAccessibilityDisposition accessibility_disposition);
281
282 // Discard one or more system pages starting at |address| and continuing for
283 // |length| bytes. |length| must be a multiple of |SystemPageSize()|.
284 //
285 // Discarding is a hint to the system that the page is no longer required. The
286 // hint may:
287 // - Do nothing.
288 // - Discard the page immediately, freeing up physical pages.
289 // - Discard the page at some time in the future in response to memory
290 // pressure.
291 //
292 // Only committed pages should be discarded. Discarding a page does not decommit
293 // it, and it is valid to discard an already-discarded page. A read or write to
294 // a discarded page will not fault.
295 //
296 // Reading from a discarded page may return the original page content, or a page
297 // full of zeroes.
298 //
299 // Writing to a discarded page is the only guaranteed way to tell the system
300 // that the page is required again. Once written to, the content of the page is
301 // guaranteed stable once more. After being written to, the page content may be
302 // based on the original page content, or a page of zeroes.
303 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
304 void DiscardSystemPages(uintptr_t address, size_t length);
305 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
306 void DiscardSystemPages(void* address, size_t length);
307
308 // Rounds up |address| to the next multiple of |SystemPageSize()|. Returns
309 // 0 for an |address| of 0.
310 PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR uintptr_t
RoundUpToSystemPage(uintptr_t address)311 RoundUpToSystemPage(uintptr_t address) {
312 return (address + internal::SystemPageOffsetMask()) &
313 internal::SystemPageBaseMask();
314 }
315
316 // Rounds down |address| to the previous multiple of |SystemPageSize()|. Returns
317 // 0 for an |address| of 0.
318 PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR uintptr_t
RoundDownToSystemPage(uintptr_t address)319 RoundDownToSystemPage(uintptr_t address) {
320 return address & internal::SystemPageBaseMask();
321 }
322
323 // Rounds up |address| to the next multiple of |PageAllocationGranularity()|.
324 // Returns 0 for an |address| of 0.
325 PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR uintptr_t
RoundUpToPageAllocationGranularity(uintptr_t address)326 RoundUpToPageAllocationGranularity(uintptr_t address) {
327 return (address + internal::PageAllocationGranularityOffsetMask()) &
328 internal::PageAllocationGranularityBaseMask();
329 }
330
331 // Rounds down |address| to the previous multiple of
332 // |PageAllocationGranularity()|. Returns 0 for an |address| of 0.
333 PA_ALWAYS_INLINE PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR uintptr_t
RoundDownToPageAllocationGranularity(uintptr_t address)334 RoundDownToPageAllocationGranularity(uintptr_t address) {
335 return address & internal::PageAllocationGranularityBaseMask();
336 }
337
338 // Reserves (at least) |size| bytes of address space, aligned to
339 // |PageAllocationGranularity()|. This can be called early on to make it more
340 // likely that large allocations will succeed. Returns true if the reservation
341 // succeeded, false if the reservation failed or a reservation was already made.
342 PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool ReserveAddressSpace(size_t size);
343
344 // Releases any reserved address space. |AllocPages| calls this automatically on
345 // an allocation failure. External allocators may also call this on failure.
346 //
347 // Returns true when an existing reservation was released.
348 PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool ReleaseReservation();
349
350 // Returns true if there is currently an address space reservation.
351 PA_COMPONENT_EXPORT(PARTITION_ALLOC) bool HasReservationForTesting();
352
353 // Returns |errno| (POSIX) or the result of |GetLastError| (Windows) when |mmap|
354 // (POSIX) or |VirtualAlloc| (Windows) fails.
355 PA_COMPONENT_EXPORT(PARTITION_ALLOC) uint32_t GetAllocPageErrorCode();
356
357 // Returns the total amount of mapped pages from all clients of
358 // PageAllocator. These pages may or may not be committed. This is mostly useful
359 // to assess address space pressure.
360 PA_COMPONENT_EXPORT(PARTITION_ALLOC) size_t GetTotalMappedSize();
361
362 #if BUILDFLAG(IS_WIN)
363 // Sets whether to retry the allocation of pages when a commit failure
364 // happens. This doesn't cover cases where the system is out of address space,
365 // or reaches another limit.
366 PA_COMPONENT_EXPORT(PARTITION_ALLOC)
367 void SetRetryOnCommitFailure(bool retry_on_commit_failure);
368 bool GetRetryOnCommitFailure();
369 #endif // BUILDFLAG(IS_WIN)
370
371 } // namespace partition_alloc
372
373 #endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_H_
374