• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_WIN_H_
6 #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_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       return PAGE_EXECUTE_READWRITE;
96     case PageAccessibilityConfiguration::kInaccessible:
97     case PageAccessibilityConfiguration::kInaccessibleWillJitLater:
98       return PAGE_NOACCESS;
99   }
100   PA_NOTREACHED();
101 }
102 
SystemAllocPagesInternal(uintptr_t hint,size_t length,PageAccessibilityConfiguration accessibility,PageTag page_tag,int file_descriptor_for_shared_alloc)103 uintptr_t SystemAllocPagesInternal(
104     uintptr_t hint,
105     size_t length,
106     PageAccessibilityConfiguration accessibility,
107     PageTag page_tag,
108     [[maybe_unused]] int file_descriptor_for_shared_alloc) {
109   const DWORD access_flag = GetAccessFlags(accessibility);
110   const DWORD type_flags =
111       (access_flag == PAGE_NOACCESS) ? MEM_RESERVE : (MEM_RESERVE | MEM_COMMIT);
112   void* ret = VirtualAllocWithRetry(reinterpret_cast<void*>(hint), length,
113                                     type_flags, access_flag);
114   if (ret == nullptr) {
115     s_allocPageErrorCode = GetLastError();
116   }
117   return reinterpret_cast<uintptr_t>(ret);
118 }
119 
TrimMappingInternal(uintptr_t base_address,size_t base_length,size_t trim_length,PageAccessibilityConfiguration accessibility,size_t pre_slack,size_t post_slack)120 uintptr_t TrimMappingInternal(uintptr_t base_address,
121                               size_t base_length,
122                               size_t trim_length,
123                               PageAccessibilityConfiguration accessibility,
124                               size_t pre_slack,
125                               size_t post_slack) {
126   uintptr_t ret = base_address;
127   if (pre_slack || post_slack) {
128     // We cannot resize the allocation run. Free it and retry at the aligned
129     // address within the freed range.
130     ret = base_address + pre_slack;
131     FreePages(base_address, base_length);
132     ret = SystemAllocPages(ret, trim_length, accessibility, PageTag::kChromium);
133   }
134   return ret;
135 }
136 
TrySetSystemPagesAccessInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility)137 bool TrySetSystemPagesAccessInternal(
138     uintptr_t address,
139     size_t length,
140     PageAccessibilityConfiguration accessibility) {
141   void* ptr = reinterpret_cast<void*>(address);
142   if (GetAccessFlags(accessibility) == PAGE_NOACCESS) {
143     return VirtualFree(ptr, length, MEM_DECOMMIT) != 0;
144   }
145   // Call the retry path even though this function can fail, because callers of
146   // this are likely to crash the process when this function fails, and we don't
147   // want that for transient failures.
148   return nullptr != VirtualAllocWithRetry(ptr, length, MEM_COMMIT,
149                                           GetAccessFlags(accessibility));
150 }
151 
SetSystemPagesAccessInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility)152 void SetSystemPagesAccessInternal(
153     uintptr_t address,
154     size_t length,
155     PageAccessibilityConfiguration accessibility) {
156   void* ptr = reinterpret_cast<void*>(address);
157   const DWORD access_flag = GetAccessFlags(accessibility);
158   if (access_flag == PAGE_NOACCESS) {
159     if (!VirtualFree(ptr, length, MEM_DECOMMIT)) {
160       // We check `GetLastError` for `ERROR_SUCCESS` here so that in a crash
161       // report we get the error number.
162       PA_CHECK(static_cast<uint32_t>(ERROR_SUCCESS) == GetLastError());
163     }
164   } else {
165     if (!VirtualAllocWithRetry(ptr, length, MEM_COMMIT, access_flag)) {
166       int32_t error = GetLastError();
167       if (error == ERROR_COMMITMENT_LIMIT) {
168         OOM_CRASH(length);
169       }
170       // We check `GetLastError` for `ERROR_SUCCESS` here so that in a crash
171       // report we get the error number.
172       PA_CHECK(ERROR_SUCCESS == error);
173     }
174   }
175 }
176 
FreePagesInternal(uintptr_t address,size_t length)177 void FreePagesInternal(uintptr_t address, size_t length) {
178   PA_CHECK(VirtualFree(reinterpret_cast<void*>(address), 0, MEM_RELEASE));
179 }
180 
DecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityDisposition accessibility_disposition)181 void DecommitSystemPagesInternal(
182     uintptr_t address,
183     size_t length,
184     PageAccessibilityDisposition accessibility_disposition) {
185   // Ignore accessibility_disposition, because decommitting is equivalent to
186   // making pages inaccessible.
187   SetSystemPagesAccess(address, length,
188                        PageAccessibilityConfiguration(
189                            PageAccessibilityConfiguration::kInaccessible));
190 }
191 
DecommitAndZeroSystemPagesInternal(uintptr_t address,size_t length,PageTag page_tag)192 void DecommitAndZeroSystemPagesInternal(uintptr_t address,
193                                         size_t length,
194                                         PageTag page_tag) {
195   // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree:
196   // "If a page is decommitted but not released, its state changes to reserved.
197   // Subsequently, you can call VirtualAlloc to commit it, or VirtualFree to
198   // release it. Attempts to read from or write to a reserved page results in an
199   // access violation exception."
200   // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
201   // for MEM_COMMIT: "The function also guarantees that when the caller later
202   // initially accesses the memory, the contents will be zero."
203   PA_CHECK(VirtualFree(reinterpret_cast<void*>(address), length, MEM_DECOMMIT));
204 }
205 
RecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility,PageAccessibilityDisposition accessibility_disposition)206 void RecommitSystemPagesInternal(
207     uintptr_t address,
208     size_t length,
209     PageAccessibilityConfiguration accessibility,
210     PageAccessibilityDisposition accessibility_disposition) {
211   // Ignore accessibility_disposition, because decommitting is equivalent to
212   // making pages inaccessible.
213   SetSystemPagesAccess(address, length, accessibility);
214 }
215 
TryRecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility,PageAccessibilityDisposition accessibility_disposition)216 bool TryRecommitSystemPagesInternal(
217     uintptr_t address,
218     size_t length,
219     PageAccessibilityConfiguration accessibility,
220     PageAccessibilityDisposition accessibility_disposition) {
221   // Ignore accessibility_disposition, because decommitting is equivalent to
222   // making pages inaccessible.
223   return TrySetSystemPagesAccess(address, length, accessibility);
224 }
225 
DiscardSystemPagesInternal(uintptr_t address,size_t length)226 void DiscardSystemPagesInternal(uintptr_t address, size_t length) {
227   void* ptr = reinterpret_cast<void*>(address);
228   // Use DiscardVirtualMemory when available because it releases faster than
229   // MEM_RESET.
230   DWORD ret = DiscardVirtualMemory(ptr, length);
231   // DiscardVirtualMemory is buggy in Win10 SP0, so fall back to MEM_RESET on
232   // failure.
233   if (ret) {
234     PA_CHECK(VirtualAllocWithRetry(ptr, length, MEM_RESET, PAGE_READWRITE));
235   }
236 }
237 
238 }  // namespace partition_alloc::internal
239 
240 #endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_WIN_H_
241