1 // Copyright 2019 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 // This file implements memory allocation primitives for PageAllocator using
6 // Fuchsia's VMOs (Virtual Memory Objects). VMO API is documented in
7 // https://fuchsia.dev/fuchsia-src/zircon/objects/vm_object . A VMO is a kernel
8 // object that corresponds to a set of memory pages. VMO pages may be mapped
9 // to an address space. The code below creates VMOs for each memory allocations
10 // and maps them to the default address space of the current process.
11
12 #ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_INTERNALS_FUCHSIA_H_
13 #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_INTERNALS_FUCHSIA_H_
14
15 #include <lib/zx/vmar.h>
16 #include <lib/zx/vmo.h>
17
18 #include <cstdint>
19
20 #include "base/allocator/partition_allocator/page_allocator.h"
21 #include "base/allocator/partition_allocator/partition_alloc_base/fuchsia/fuchsia_logging.h"
22 #include "base/allocator/partition_allocator/partition_alloc_check.h"
23 #include "base/allocator/partition_allocator/partition_alloc_notreached.h"
24
25 namespace partition_alloc::internal {
26
27 namespace {
28
29 // Returns VMO name for a PageTag.
PageTagToName(PageTag tag)30 const char* PageTagToName(PageTag tag) {
31 switch (tag) {
32 case PageTag::kBlinkGC:
33 return "cr_blink_gc";
34 case PageTag::kPartitionAlloc:
35 return "cr_partition_alloc";
36 case PageTag::kChromium:
37 return "cr_chromium";
38 case PageTag::kV8:
39 return "cr_v8";
40 default:
41 PA_DCHECK(false);
42 return "";
43 }
44 }
45
PageAccessibilityToZxVmOptions(PageAccessibilityConfiguration accessibility)46 zx_vm_option_t PageAccessibilityToZxVmOptions(
47 PageAccessibilityConfiguration accessibility) {
48 switch (accessibility.permissions) {
49 case PageAccessibilityConfiguration::kRead:
50 return ZX_VM_PERM_READ;
51 case PageAccessibilityConfiguration::kReadWrite:
52 case PageAccessibilityConfiguration::kReadWriteTagged:
53 return ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
54 case PageAccessibilityConfiguration::kReadExecuteProtected:
55 case PageAccessibilityConfiguration::kReadExecute:
56 return ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE;
57 case PageAccessibilityConfiguration::kReadWriteExecute:
58 return ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE;
59 default:
60 PA_NOTREACHED();
61 [[fallthrough]];
62 case PageAccessibilityConfiguration::kInaccessible:
63 return 0;
64 }
65 }
66
67 } // namespace
68
69 // zx_vmar_map() will fail if the VMO cannot be mapped at |vmar_offset|, i.e.
70 // |hint| is not advisory.
71 constexpr bool kHintIsAdvisory = false;
72
73 std::atomic<int32_t> s_allocPageErrorCode{0};
74
SystemAllocPagesInternal(uintptr_t hint,size_t length,PageAccessibilityConfiguration accessibility,PageTag page_tag,int file_descriptor_for_shared_alloc)75 uintptr_t SystemAllocPagesInternal(
76 uintptr_t hint,
77 size_t length,
78 PageAccessibilityConfiguration accessibility,
79 PageTag page_tag,
80 [[maybe_unused]] int file_descriptor_for_shared_alloc) {
81 zx::vmo vmo;
82 zx_status_t status = zx::vmo::create(length, 0, &vmo);
83 if (status != ZX_OK) {
84 PA_ZX_DLOG(INFO, status) << "zx_vmo_create";
85 return 0;
86 }
87
88 const char* vmo_name = PageTagToName(page_tag);
89 status = vmo.set_property(ZX_PROP_NAME, vmo_name, strlen(vmo_name));
90
91 // VMO names are used only for debugging, so failure to set a name is not
92 // fatal.
93 PA_ZX_DCHECK(status == ZX_OK, status);
94
95 if (page_tag == PageTag::kV8) {
96 // V8 uses JIT. Call zx_vmo_replace_as_executable() to allow code execution
97 // in the new VMO.
98 status = vmo.replace_as_executable(zx::resource(), &vmo);
99 if (status != ZX_OK) {
100 PA_ZX_DLOG(INFO, status) << "zx_vmo_replace_as_executable";
101 return 0;
102 }
103 }
104
105 zx_vm_option_t options = PageAccessibilityToZxVmOptions(accessibility);
106
107 uint64_t vmar_offset = 0;
108 if (hint) {
109 vmar_offset = hint;
110 options |= ZX_VM_SPECIFIC;
111 }
112
113 uint64_t address;
114 status = zx::vmar::root_self()->map(options, vmar_offset, vmo,
115 /*vmo_offset=*/0, length, &address);
116 if (status != ZX_OK) {
117 // map() is expected to fail if |hint| is set to an already-in-use location.
118 if (!hint) {
119 PA_ZX_DLOG(ERROR, status) << "zx_vmar_map";
120 }
121 return 0;
122 }
123
124 return address;
125 }
126
TrimMappingInternal(uintptr_t base_address,size_t base_length,size_t trim_length,PageAccessibilityConfiguration accessibility,size_t pre_slack,size_t post_slack)127 uintptr_t TrimMappingInternal(uintptr_t base_address,
128 size_t base_length,
129 size_t trim_length,
130 PageAccessibilityConfiguration accessibility,
131 size_t pre_slack,
132 size_t post_slack) {
133 PA_DCHECK(base_length == trim_length + pre_slack + post_slack);
134
135 // Unmap head if necessary.
136 if (pre_slack) {
137 zx_status_t status = zx::vmar::root_self()->unmap(base_address, pre_slack);
138 PA_ZX_CHECK(status == ZX_OK, status);
139 }
140
141 // Unmap tail if necessary.
142 if (post_slack) {
143 zx_status_t status = zx::vmar::root_self()->unmap(
144 base_address + pre_slack + trim_length, post_slack);
145 PA_ZX_CHECK(status == ZX_OK, status);
146 }
147
148 return base_address + pre_slack;
149 }
150
TrySetSystemPagesAccessInternal(uint64_t address,size_t length,PageAccessibilityConfiguration accessibility)151 bool TrySetSystemPagesAccessInternal(
152 uint64_t address,
153 size_t length,
154 PageAccessibilityConfiguration accessibility) {
155 zx_status_t status = zx::vmar::root_self()->protect(
156 PageAccessibilityToZxVmOptions(accessibility), address, length);
157 return status == ZX_OK;
158 }
159
SetSystemPagesAccessInternal(uint64_t address,size_t length,PageAccessibilityConfiguration accessibility)160 void SetSystemPagesAccessInternal(
161 uint64_t address,
162 size_t length,
163 PageAccessibilityConfiguration accessibility) {
164 zx_status_t status = zx::vmar::root_self()->protect(
165 PageAccessibilityToZxVmOptions(accessibility), address, length);
166 PA_ZX_CHECK(status == ZX_OK, status);
167 }
168
FreePagesInternal(uint64_t address,size_t length)169 void FreePagesInternal(uint64_t address, size_t length) {
170 zx_status_t status = zx::vmar::root_self()->unmap(address, length);
171 PA_ZX_CHECK(status == ZX_OK, status);
172 }
173
DiscardSystemPagesInternal(uint64_t address,size_t length)174 void DiscardSystemPagesInternal(uint64_t address, size_t length) {
175 zx_status_t status = zx::vmar::root_self()->op_range(
176 ZX_VMO_OP_DECOMMIT, address, length, nullptr, 0);
177 PA_ZX_CHECK(status == ZX_OK, status);
178 }
179
DecommitSystemPagesInternal(uint64_t address,size_t length,PageAccessibilityDisposition accessibility_disposition)180 void DecommitSystemPagesInternal(
181 uint64_t address,
182 size_t length,
183 PageAccessibilityDisposition accessibility_disposition) {
184 if (accessibility_disposition ==
185 PageAccessibilityDisposition::kRequireUpdate) {
186 SetSystemPagesAccess(address, length,
187 PageAccessibilityConfiguration(
188 PageAccessibilityConfiguration::kInaccessible));
189 }
190
191 DiscardSystemPagesInternal(address, length);
192 }
193
DecommitAndZeroSystemPagesInternal(uintptr_t address,size_t length)194 void DecommitAndZeroSystemPagesInternal(uintptr_t address, size_t length) {
195 SetSystemPagesAccess(address, length,
196 PageAccessibilityConfiguration(
197 PageAccessibilityConfiguration::kInaccessible));
198
199 DiscardSystemPagesInternal(address, length);
200 }
201
RecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility,PageAccessibilityDisposition accessibility_disposition)202 void RecommitSystemPagesInternal(
203 uintptr_t address,
204 size_t length,
205 PageAccessibilityConfiguration accessibility,
206 PageAccessibilityDisposition accessibility_disposition) {
207 // On Fuchsia systems, the caller needs to simply read the memory to recommit
208 // it. However, if decommit changed the permissions, recommit has to change
209 // them back.
210 if (accessibility_disposition ==
211 PageAccessibilityDisposition::kRequireUpdate) {
212 SetSystemPagesAccess(address, length, accessibility);
213 }
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 // On Fuchsia systems, the caller needs to simply read the memory to recommit
222 // it. However, if decommit changed the permissions, recommit has to change
223 // them back.
224 if (accessibility_disposition ==
225 PageAccessibilityDisposition::kRequireUpdate) {
226 return TrySetSystemPagesAccess(address, length, accessibility);
227 }
228 return true;
229 }
230
231 } // namespace partition_alloc::internal
232
233 #endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_INTERNALS_FUCHSIA_H_
234