1 // Copyright 2018 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 PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_WIN_H_
6 #define PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_WIN_H_
7
8 #include <cstdint>
9
10 #include "partition_alloc/oom.h"
11 #include "partition_alloc/page_allocator.h"
12 #include "partition_alloc/page_allocator_internal.h"
13 #include "partition_alloc/partition_alloc_base/notreached.h"
14 #include "partition_alloc/partition_alloc_buildflags.h"
15 #include "partition_alloc/partition_alloc_check.h"
16
17 namespace partition_alloc::internal {
18
19 // |VirtualAlloc| will fail if allocation at the hint address is blocked.
20 constexpr bool kHintIsAdvisory = false;
21 std::atomic<int32_t> s_allocPageErrorCode{ERROR_SUCCESS};
22
IsOutOfMemory(DWORD error)23 bool IsOutOfMemory(DWORD error) {
24 // From
25 // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
26 switch (error) {
27 // Page file is being extended.
28 case ERROR_COMMITMENT_MINIMUM:
29 // Page file is too small.
30 case ERROR_COMMITMENT_LIMIT:
31 #if BUILDFLAG(HAS_64_BIT_POINTERS)
32 // Not enough memory resources are available to process this command.
33 //
34 // It is not entirely clear whether this error pertains to out of address
35 // space errors, or the kernel being out of memory. Only include it for 64
36 // bit architectures, since address space issues are unlikely there.
37 case ERROR_NOT_ENOUGH_MEMORY:
38 #endif
39 case ERROR_PAGEFILE_QUOTA:
40 // Insufficient quota to complete the requested service.
41 return true;
42 default:
43 return false;
44 }
45 }
46
VirtualAllocWithRetry(void * address,size_t size,DWORD type_flags,DWORD access_flags)47 void* VirtualAllocWithRetry(void* address,
48 size_t size,
49 DWORD type_flags,
50 DWORD access_flags) {
51 void* ret = nullptr;
52 // Failure to commit memory can be temporary, in at least two cases:
53 // - The page file is getting extended.
54 // - Another process terminates (most likely because of OOM)
55 //
56 // Wait and retry, since the alternative is crashing. Note that if we
57 // selectively apply this... hum... beautiful hack to some process types only,
58 // "some process crashing" may very well be one of ours, which may be
59 // desirable (e.g. some processes like the browser are more important than
60 // others).
61 //
62 // This approach has been shown to be effective for Firefox, see
63 // crbug.com/1392738 for context. Constants below are accordingly taken from
64 // Firefox as well.
65 constexpr int kMaxTries = 10;
66 constexpr int kDelayMs = 50;
67
68 bool should_retry = GetRetryOnCommitFailure() && (type_flags & MEM_COMMIT) &&
69 (access_flags != PAGE_NOACCESS);
70 for (int tries = 0; tries < kMaxTries; tries++) {
71 ret = VirtualAlloc(address, size, type_flags, access_flags);
72 // Only retry for commit failures. If this is an address space problem
73 // (e.g. caller asked for an address which is not available), this is
74 // unlikely to be resolved by waiting.
75 if (ret || !should_retry || !IsOutOfMemory(GetLastError())) {
76 break;
77 }
78
79 Sleep(kDelayMs);
80 }
81 return ret;
82 }
83
GetAccessFlags(PageAccessibilityConfiguration accessibility)84 int GetAccessFlags(PageAccessibilityConfiguration accessibility) {
85 switch (accessibility.permissions) {
86 case PageAccessibilityConfiguration::kRead:
87 return PAGE_READONLY;
88 case PageAccessibilityConfiguration::kReadWrite:
89 case PageAccessibilityConfiguration::kReadWriteTagged:
90 return PAGE_READWRITE;
91 case PageAccessibilityConfiguration::kReadExecute:
92 case PageAccessibilityConfiguration::kReadExecuteProtected:
93 return PAGE_EXECUTE_READ;
94 case PageAccessibilityConfiguration::kReadWriteExecute:
95 case PageAccessibilityConfiguration::kReadWriteExecuteProtected:
96 return PAGE_EXECUTE_READWRITE;
97 case PageAccessibilityConfiguration::kInaccessible:
98 case PageAccessibilityConfiguration::kInaccessibleWillJitLater:
99 return PAGE_NOACCESS;
100 }
101 PA_NOTREACHED();
102 }
103
SystemAllocPagesInternal(uintptr_t hint,size_t length,PageAccessibilityConfiguration accessibility,PageTag page_tag,int file_descriptor_for_shared_alloc)104 uintptr_t SystemAllocPagesInternal(
105 uintptr_t hint,
106 size_t length,
107 PageAccessibilityConfiguration accessibility,
108 PageTag page_tag,
109 [[maybe_unused]] int file_descriptor_for_shared_alloc) {
110 const DWORD access_flag = GetAccessFlags(accessibility);
111 const DWORD type_flags =
112 (access_flag == PAGE_NOACCESS) ? MEM_RESERVE : (MEM_RESERVE | MEM_COMMIT);
113 void* ret = VirtualAllocWithRetry(reinterpret_cast<void*>(hint), length,
114 type_flags, access_flag);
115 if (ret == nullptr) {
116 s_allocPageErrorCode = GetLastError();
117 }
118 return reinterpret_cast<uintptr_t>(ret);
119 }
120
TrimMappingInternal(uintptr_t base_address,size_t base_length,size_t trim_length,PageAccessibilityConfiguration accessibility,size_t pre_slack,size_t post_slack)121 uintptr_t TrimMappingInternal(uintptr_t base_address,
122 size_t base_length,
123 size_t trim_length,
124 PageAccessibilityConfiguration accessibility,
125 size_t pre_slack,
126 size_t post_slack) {
127 uintptr_t ret = base_address;
128 if (pre_slack || post_slack) {
129 // We cannot resize the allocation run. Free it and retry at the aligned
130 // address within the freed range.
131 ret = base_address + pre_slack;
132 FreePages(base_address, base_length);
133 ret = SystemAllocPages(ret, trim_length, accessibility, PageTag::kChromium);
134 }
135 return ret;
136 }
137
TrySetSystemPagesAccessInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility)138 bool TrySetSystemPagesAccessInternal(
139 uintptr_t address,
140 size_t length,
141 PageAccessibilityConfiguration accessibility) {
142 void* ptr = reinterpret_cast<void*>(address);
143 if (GetAccessFlags(accessibility) == PAGE_NOACCESS) {
144 return VirtualFree(ptr, length, MEM_DECOMMIT) != 0;
145 }
146 // Call the retry path even though this function can fail, because callers of
147 // this are likely to crash the process when this function fails, and we don't
148 // want that for transient failures.
149 return nullptr != VirtualAllocWithRetry(ptr, length, MEM_COMMIT,
150 GetAccessFlags(accessibility));
151 }
152
SetSystemPagesAccessInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility)153 void SetSystemPagesAccessInternal(
154 uintptr_t address,
155 size_t length,
156 PageAccessibilityConfiguration accessibility) {
157 void* ptr = reinterpret_cast<void*>(address);
158 const DWORD access_flag = GetAccessFlags(accessibility);
159 if (access_flag == PAGE_NOACCESS) {
160 if (!VirtualFree(ptr, length, MEM_DECOMMIT)) {
161 // We check `GetLastError` for `ERROR_SUCCESS` here so that in a crash
162 // report we get the error number.
163 PA_CHECK(static_cast<uint32_t>(ERROR_SUCCESS) == GetLastError());
164 }
165 } else {
166 if (!VirtualAllocWithRetry(ptr, length, MEM_COMMIT, access_flag)) {
167 int32_t error = GetLastError();
168 if (error == ERROR_COMMITMENT_LIMIT ||
169 error == ERROR_COMMITMENT_MINIMUM) {
170 OOM_CRASH(length);
171 }
172 // We check `GetLastError` for `ERROR_SUCCESS` here so that in a crash
173 // report we get the error number.
174 PA_CHECK(ERROR_SUCCESS == error);
175 }
176 }
177 }
178
FreePagesInternal(uintptr_t address,size_t length)179 void FreePagesInternal(uintptr_t address, size_t length) {
180 PA_CHECK(VirtualFree(reinterpret_cast<void*>(address), 0, MEM_RELEASE));
181 }
182
DecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityDisposition accessibility_disposition)183 void DecommitSystemPagesInternal(
184 uintptr_t address,
185 size_t length,
186 PageAccessibilityDisposition accessibility_disposition) {
187 // Ignore accessibility_disposition, because decommitting is equivalent to
188 // making pages inaccessible.
189 SetSystemPagesAccess(address, length,
190 PageAccessibilityConfiguration(
191 PageAccessibilityConfiguration::kInaccessible));
192 }
193
DecommitAndZeroSystemPagesInternal(uintptr_t address,size_t length,PageTag page_tag)194 bool DecommitAndZeroSystemPagesInternal(uintptr_t address,
195 size_t length,
196 PageTag page_tag) {
197 // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree:
198 // "If a page is decommitted but not released, its state changes to reserved.
199 // Subsequently, you can call VirtualAlloc to commit it, or VirtualFree to
200 // release it. Attempts to read from or write to a reserved page results in an
201 // access violation exception."
202 // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
203 // for MEM_COMMIT: "The function also guarantees that when the caller later
204 // initially accesses the memory, the contents will be zero."
205 PA_CHECK(VirtualFree(reinterpret_cast<void*>(address), length, MEM_DECOMMIT));
206 return true;
207 }
208
RecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility,PageAccessibilityDisposition accessibility_disposition)209 void RecommitSystemPagesInternal(
210 uintptr_t address,
211 size_t length,
212 PageAccessibilityConfiguration accessibility,
213 PageAccessibilityDisposition accessibility_disposition) {
214 // Ignore accessibility_disposition, because decommitting is equivalent to
215 // making pages inaccessible.
216 SetSystemPagesAccess(address, length, accessibility);
217 }
218
TryRecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility,PageAccessibilityDisposition accessibility_disposition)219 bool TryRecommitSystemPagesInternal(
220 uintptr_t address,
221 size_t length,
222 PageAccessibilityConfiguration accessibility,
223 PageAccessibilityDisposition accessibility_disposition) {
224 // Ignore accessibility_disposition, because decommitting is equivalent to
225 // making pages inaccessible.
226 return TrySetSystemPagesAccess(address, length, accessibility);
227 }
228
DiscardSystemPagesInternal(uintptr_t address,size_t length)229 void DiscardSystemPagesInternal(uintptr_t address, size_t length) {
230 void* ptr = reinterpret_cast<void*>(address);
231 // Use DiscardVirtualMemory when available because it releases faster than
232 // MEM_RESET.
233 DWORD ret = DiscardVirtualMemory(ptr, length);
234 // DiscardVirtualMemory is buggy in Win10 SP0, so fall back to MEM_RESET on
235 // failure.
236 if (ret) {
237 PA_CHECK(VirtualAllocWithRetry(ptr, length, MEM_RESET, PAGE_READWRITE));
238 }
239 }
240
241 } // namespace partition_alloc::internal
242
243 #endif // PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_WIN_H_
244