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_SRC_PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_FUCHSIA_H_
13 #define BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_FUCHSIA_H_
14
15 #include <fidl/fuchsia.kernel/cpp/fidl.h>
16 #include <lib/component/incoming/cpp/protocol.h>
17 #include <lib/zx/resource.h>
18 #include <lib/zx/vmar.h>
19 #include <lib/zx/vmo.h>
20
21 #include <cstdint>
22
23 #include "partition_alloc/page_allocator.h"
24 #include "partition_alloc/partition_alloc_base/fuchsia/fuchsia_logging.h"
25 #include "partition_alloc/partition_alloc_base/no_destructor.h"
26 #include "partition_alloc/partition_alloc_base/notreached.h"
27 #include "partition_alloc/partition_alloc_check.h"
28
29 namespace partition_alloc::internal {
30
31 namespace {
32
GetVmexResource()33 zx::resource GetVmexResource() {
34 auto vmex_resource_client =
35 component::Connect<fuchsia_kernel::VmexResource>();
36 if (vmex_resource_client.is_error()) {
37 PA_LOG(ERROR) << "Connect(VmexResource):"
38 << vmex_resource_client.status_string();
39 return {};
40 }
41
42 fidl::SyncClient sync_vmex_resource_client(
43 std::move(vmex_resource_client.value()));
44 auto result = sync_vmex_resource_client->Get();
45 if (result.is_error()) {
46 PA_LOG(ERROR) << "VmexResource.Get():"
47 << result.error_value().FormatDescription().c_str();
48 return {};
49 }
50
51 return std::move(result->resource());
52 }
53
VmexResource()54 const zx::resource& VmexResource() {
55 static base::NoDestructor<zx::resource> vmex_resource(GetVmexResource());
56 return *vmex_resource;
57 }
58
59 // Returns VMO name for a PageTag.
PageTagToName(PageTag tag)60 const char* PageTagToName(PageTag tag) {
61 switch (tag) {
62 case PageTag::kBlinkGC:
63 return "cr_blink_gc";
64 case PageTag::kPartitionAlloc:
65 return "cr_partition_alloc";
66 case PageTag::kChromium:
67 return "cr_chromium";
68 case PageTag::kV8:
69 return "cr_v8";
70 case PageTag::kSimulation:
71 PA_NOTREACHED();
72 }
73 PA_NOTREACHED();
74 }
75
PageAccessibilityToZxVmOptions(PageAccessibilityConfiguration accessibility)76 zx_vm_option_t PageAccessibilityToZxVmOptions(
77 PageAccessibilityConfiguration accessibility) {
78 switch (accessibility.permissions) {
79 case PageAccessibilityConfiguration::kRead:
80 return ZX_VM_PERM_READ;
81 case PageAccessibilityConfiguration::kReadWrite:
82 case PageAccessibilityConfiguration::kReadWriteTagged:
83 return ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
84 case PageAccessibilityConfiguration::kReadExecuteProtected:
85 case PageAccessibilityConfiguration::kReadExecute:
86 return ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE;
87 case PageAccessibilityConfiguration::kReadWriteExecute:
88 return ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE;
89 case PageAccessibilityConfiguration::kInaccessible:
90 case PageAccessibilityConfiguration::kInaccessibleWillJitLater:
91 return 0;
92 };
93 PA_NOTREACHED();
94 }
95
96 } // namespace
97
98 // zx_vmar_map() will fail if the VMO cannot be mapped at |vmar_offset|, i.e.
99 // |hint| is not advisory.
100 constexpr bool kHintIsAdvisory = false;
101
102 std::atomic<int32_t> s_allocPageErrorCode{0};
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 zx::vmo vmo;
111 zx_status_t status = zx::vmo::create(length, 0, &vmo);
112 if (status != ZX_OK) {
113 PA_ZX_DLOG(INFO, status) << "zx_vmo_create";
114 return 0;
115 }
116
117 const char* vmo_name = PageTagToName(page_tag);
118 status = vmo.set_property(ZX_PROP_NAME, vmo_name, strlen(vmo_name));
119
120 // VMO names are used only for debugging, so failure to set a name is not
121 // fatal.
122 PA_ZX_DCHECK(status == ZX_OK, status);
123
124 if (accessibility.permissions ==
125 PageAccessibilityConfiguration::kInaccessibleWillJitLater ||
126 accessibility.permissions ==
127 PageAccessibilityConfiguration::kReadWriteExecute) {
128 // V8 uses JIT. Call zx_vmo_replace_as_executable() to allow code execution
129 // in the new VMO.
130 status = vmo.replace_as_executable(VmexResource(), &vmo);
131 if (status != ZX_OK) {
132 PA_ZX_DLOG(INFO, status) << "zx_vmo_replace_as_executable";
133 return 0;
134 }
135 }
136
137 zx_vm_option_t options = PageAccessibilityToZxVmOptions(accessibility);
138
139 uint64_t vmar_offset = 0;
140 if (hint) {
141 vmar_offset = hint;
142 options |= ZX_VM_SPECIFIC;
143 }
144
145 uint64_t address;
146 status = zx::vmar::root_self()->map(options, vmar_offset, vmo,
147 /*vmo_offset=*/0, length, &address);
148 if (status != ZX_OK) {
149 // map() is expected to fail if |hint| is set to an already-in-use location.
150 if (!hint) {
151 PA_ZX_DLOG(ERROR, status) << "zx_vmar_map";
152 }
153 return 0;
154 }
155
156 return address;
157 }
158
TrimMappingInternal(uintptr_t base_address,size_t base_length,size_t trim_length,PageAccessibilityConfiguration accessibility,size_t pre_slack,size_t post_slack)159 uintptr_t TrimMappingInternal(uintptr_t base_address,
160 size_t base_length,
161 size_t trim_length,
162 PageAccessibilityConfiguration accessibility,
163 size_t pre_slack,
164 size_t post_slack) {
165 PA_DCHECK(base_length == trim_length + pre_slack + post_slack);
166
167 // Unmap head if necessary.
168 if (pre_slack) {
169 zx_status_t status = zx::vmar::root_self()->unmap(base_address, pre_slack);
170 PA_ZX_CHECK(status == ZX_OK, status);
171 }
172
173 // Unmap tail if necessary.
174 if (post_slack) {
175 zx_status_t status = zx::vmar::root_self()->unmap(
176 base_address + pre_slack + trim_length, post_slack);
177 PA_ZX_CHECK(status == ZX_OK, status);
178 }
179
180 return base_address + pre_slack;
181 }
182
TrySetSystemPagesAccessInternal(uint64_t address,size_t length,PageAccessibilityConfiguration accessibility)183 bool TrySetSystemPagesAccessInternal(
184 uint64_t address,
185 size_t length,
186 PageAccessibilityConfiguration accessibility) {
187 zx_status_t status = zx::vmar::root_self()->protect(
188 PageAccessibilityToZxVmOptions(accessibility), address, length);
189 return status == ZX_OK;
190 }
191
SetSystemPagesAccessInternal(uint64_t address,size_t length,PageAccessibilityConfiguration accessibility)192 void SetSystemPagesAccessInternal(
193 uint64_t address,
194 size_t length,
195 PageAccessibilityConfiguration accessibility) {
196 zx_status_t status = zx::vmar::root_self()->protect(
197 PageAccessibilityToZxVmOptions(accessibility), address, length);
198 PA_ZX_CHECK(status == ZX_OK, status);
199 }
200
FreePagesInternal(uint64_t address,size_t length)201 void FreePagesInternal(uint64_t address, size_t length) {
202 zx_status_t status = zx::vmar::root_self()->unmap(address, length);
203 PA_ZX_CHECK(status == ZX_OK, status);
204 }
205
DiscardSystemPagesInternal(uint64_t address,size_t length)206 void DiscardSystemPagesInternal(uint64_t address, size_t length) {
207 zx_status_t status = zx::vmar::root_self()->op_range(
208 ZX_VMO_OP_DECOMMIT, address, length, nullptr, 0);
209 PA_ZX_CHECK(status == ZX_OK, status);
210 }
211
DecommitSystemPagesInternal(uint64_t address,size_t length,PageAccessibilityDisposition accessibility_disposition)212 void DecommitSystemPagesInternal(
213 uint64_t address,
214 size_t length,
215 PageAccessibilityDisposition accessibility_disposition) {
216 if (accessibility_disposition ==
217 PageAccessibilityDisposition::kRequireUpdate) {
218 SetSystemPagesAccess(address, length,
219 PageAccessibilityConfiguration(
220 PageAccessibilityConfiguration::kInaccessible));
221 }
222
223 DiscardSystemPagesInternal(address, length);
224 }
225
DecommitAndZeroSystemPagesInternal(uintptr_t address,size_t length,PageTag page_tag)226 void DecommitAndZeroSystemPagesInternal(uintptr_t address,
227 size_t length,
228 PageTag page_tag) {
229 SetSystemPagesAccess(address, length,
230 PageAccessibilityConfiguration(
231 PageAccessibilityConfiguration::kInaccessible));
232
233 DiscardSystemPagesInternal(address, length);
234 }
235
RecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility,PageAccessibilityDisposition accessibility_disposition)236 void RecommitSystemPagesInternal(
237 uintptr_t address,
238 size_t length,
239 PageAccessibilityConfiguration accessibility,
240 PageAccessibilityDisposition accessibility_disposition) {
241 // On Fuchsia systems, the caller needs to simply read the memory to recommit
242 // it. However, if decommit changed the permissions, recommit has to change
243 // them back.
244 if (accessibility_disposition ==
245 PageAccessibilityDisposition::kRequireUpdate) {
246 SetSystemPagesAccess(address, length, accessibility);
247 }
248 }
249
TryRecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility,PageAccessibilityDisposition accessibility_disposition)250 bool TryRecommitSystemPagesInternal(
251 uintptr_t address,
252 size_t length,
253 PageAccessibilityConfiguration accessibility,
254 PageAccessibilityDisposition accessibility_disposition) {
255 // On Fuchsia systems, the caller needs to simply read the memory to recommit
256 // it. However, if decommit changed the permissions, recommit has to change
257 // them back.
258 if (accessibility_disposition ==
259 PageAccessibilityDisposition::kRequireUpdate) {
260 return TrySetSystemPagesAccess(address, length, accessibility);
261 }
262 return true;
263 }
264
265 } // namespace partition_alloc::internal
266
267 #endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_SRC_PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_FUCHSIA_H_
268