• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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