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