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 PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_FUCHSIA_H_
13 #define 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::kReadWriteExecuteProtected:
88 case PageAccessibilityConfiguration::kReadWriteExecute:
89 return ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_PERM_EXECUTE;
90 case PageAccessibilityConfiguration::kInaccessible:
91 case PageAccessibilityConfiguration::kInaccessibleWillJitLater:
92 return 0;
93 };
94 PA_NOTREACHED();
95 }
96
97 } // namespace
98
99 // zx_vmar_map() will fail if the VMO cannot be mapped at |vmar_offset|, i.e.
100 // |hint| is not advisory.
101 constexpr bool kHintIsAdvisory = false;
102
103 std::atomic<int32_t> s_allocPageErrorCode{0};
104
SystemAllocPagesInternal(uintptr_t hint,size_t length,PageAccessibilityConfiguration accessibility,PageTag page_tag,int file_descriptor_for_shared_alloc)105 uintptr_t SystemAllocPagesInternal(
106 uintptr_t hint,
107 size_t length,
108 PageAccessibilityConfiguration accessibility,
109 PageTag page_tag,
110 [[maybe_unused]] int file_descriptor_for_shared_alloc) {
111 zx::vmo vmo;
112 zx_status_t status = zx::vmo::create(length, 0, &vmo);
113 if (status != ZX_OK) {
114 PA_ZX_DLOG(INFO, status) << "zx_vmo_create";
115 return 0;
116 }
117
118 const char* vmo_name = PageTagToName(page_tag);
119 status = vmo.set_property(ZX_PROP_NAME, vmo_name, strlen(vmo_name));
120
121 // VMO names are used only for debugging, so failure to set a name is not
122 // fatal.
123 PA_ZX_DCHECK(status == ZX_OK, status);
124
125 if (accessibility.permissions ==
126 PageAccessibilityConfiguration::kInaccessibleWillJitLater ||
127 accessibility.permissions ==
128 PageAccessibilityConfiguration::kReadWriteExecute) {
129 // V8 uses JIT. Call zx_vmo_replace_as_executable() to allow code execution
130 // in the new VMO.
131 status = vmo.replace_as_executable(VmexResource(), &vmo);
132 if (status != ZX_OK) {
133 PA_ZX_DLOG(INFO, status) << "zx_vmo_replace_as_executable";
134 return 0;
135 }
136 }
137
138 zx_vm_option_t options = PageAccessibilityToZxVmOptions(accessibility);
139
140 uint64_t vmar_offset = 0;
141 if (hint) {
142 vmar_offset = hint;
143 options |= ZX_VM_SPECIFIC;
144 }
145
146 uint64_t address;
147 status = zx::vmar::root_self()->map(options, vmar_offset, vmo,
148 /*vmo_offset=*/0, length, &address);
149 if (status != ZX_OK) {
150 // map() is expected to fail if |hint| is set to an already-in-use location.
151 if (!hint) {
152 PA_ZX_DLOG(ERROR, status) << "zx_vmar_map";
153 }
154 return 0;
155 }
156
157 return address;
158 }
159
TrimMappingInternal(uintptr_t base_address,size_t base_length,size_t trim_length,PageAccessibilityConfiguration accessibility,size_t pre_slack,size_t post_slack)160 uintptr_t TrimMappingInternal(uintptr_t base_address,
161 size_t base_length,
162 size_t trim_length,
163 PageAccessibilityConfiguration accessibility,
164 size_t pre_slack,
165 size_t post_slack) {
166 PA_DCHECK(base_length == trim_length + pre_slack + post_slack);
167
168 // Unmap head if necessary.
169 if (pre_slack) {
170 zx_status_t status = zx::vmar::root_self()->unmap(base_address, pre_slack);
171 PA_ZX_CHECK(status == ZX_OK, status);
172 }
173
174 // Unmap tail if necessary.
175 if (post_slack) {
176 zx_status_t status = zx::vmar::root_self()->unmap(
177 base_address + pre_slack + trim_length, post_slack);
178 PA_ZX_CHECK(status == ZX_OK, status);
179 }
180
181 return base_address + pre_slack;
182 }
183
TrySetSystemPagesAccessInternal(uint64_t address,size_t length,PageAccessibilityConfiguration accessibility)184 bool TrySetSystemPagesAccessInternal(
185 uint64_t address,
186 size_t length,
187 PageAccessibilityConfiguration accessibility) {
188 zx_status_t status = zx::vmar::root_self()->protect(
189 PageAccessibilityToZxVmOptions(accessibility), address, length);
190 return status == ZX_OK;
191 }
192
SetSystemPagesAccessInternal(uint64_t address,size_t length,PageAccessibilityConfiguration accessibility)193 void SetSystemPagesAccessInternal(
194 uint64_t address,
195 size_t length,
196 PageAccessibilityConfiguration accessibility) {
197 zx_status_t status = zx::vmar::root_self()->protect(
198 PageAccessibilityToZxVmOptions(accessibility), address, length);
199 PA_ZX_CHECK(status == ZX_OK, status);
200 }
201
FreePagesInternal(uint64_t address,size_t length)202 void FreePagesInternal(uint64_t address, size_t length) {
203 zx_status_t status = zx::vmar::root_self()->unmap(address, length);
204 PA_ZX_CHECK(status == ZX_OK, status);
205 }
206
DiscardSystemPagesInternal(uint64_t address,size_t length)207 void DiscardSystemPagesInternal(uint64_t address, size_t length) {
208 zx_status_t status = zx::vmar::root_self()->op_range(
209 ZX_VMO_OP_DECOMMIT, address, length, nullptr, 0);
210 PA_ZX_CHECK(status == ZX_OK, status);
211 }
212
DecommitSystemPagesInternal(uint64_t address,size_t length,PageAccessibilityDisposition accessibility_disposition)213 void DecommitSystemPagesInternal(
214 uint64_t address,
215 size_t length,
216 PageAccessibilityDisposition accessibility_disposition) {
217 if (accessibility_disposition ==
218 PageAccessibilityDisposition::kRequireUpdate) {
219 SetSystemPagesAccess(address, length,
220 PageAccessibilityConfiguration(
221 PageAccessibilityConfiguration::kInaccessible));
222 }
223
224 DiscardSystemPagesInternal(address, length);
225 }
226
DecommitAndZeroSystemPagesInternal(uintptr_t address,size_t length,PageTag page_tag)227 bool DecommitAndZeroSystemPagesInternal(uintptr_t address,
228 size_t length,
229 PageTag page_tag) {
230 SetSystemPagesAccess(address, length,
231 PageAccessibilityConfiguration(
232 PageAccessibilityConfiguration::kInaccessible));
233
234 DiscardSystemPagesInternal(address, length);
235 return true;
236 }
237
RecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility,PageAccessibilityDisposition accessibility_disposition)238 void RecommitSystemPagesInternal(
239 uintptr_t address,
240 size_t length,
241 PageAccessibilityConfiguration accessibility,
242 PageAccessibilityDisposition accessibility_disposition) {
243 // On Fuchsia systems, the caller needs to simply read the memory to recommit
244 // it. However, if decommit changed the permissions, recommit has to change
245 // them back.
246 if (accessibility_disposition ==
247 PageAccessibilityDisposition::kRequireUpdate) {
248 SetSystemPagesAccess(address, length, accessibility);
249 }
250 }
251
TryRecommitSystemPagesInternal(uintptr_t address,size_t length,PageAccessibilityConfiguration accessibility,PageAccessibilityDisposition accessibility_disposition)252 bool TryRecommitSystemPagesInternal(
253 uintptr_t address,
254 size_t length,
255 PageAccessibilityConfiguration accessibility,
256 PageAccessibilityDisposition accessibility_disposition) {
257 // On Fuchsia systems, the caller needs to simply read the memory to recommit
258 // it. However, if decommit changed the permissions, recommit has to change
259 // them back.
260 if (accessibility_disposition ==
261 PageAccessibilityDisposition::kRequireUpdate) {
262 return TrySetSystemPagesAccess(address, length, accessibility);
263 }
264 return true;
265 }
266
267 } // namespace partition_alloc::internal
268
269 #endif // PARTITION_ALLOC_PAGE_ALLOCATOR_INTERNALS_FUCHSIA_H_
270