1 //===-- mem_map_fuchsia.cpp -------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "mem_map_fuchsia.h"
10
11 #include "atomic_helpers.h"
12 #include "common.h"
13 #include "string_utils.h"
14
15 #if SCUDO_FUCHSIA
16
17 #include <zircon/process.h>
18 #include <zircon/status.h>
19 #include <zircon/syscalls.h>
20
21 namespace scudo {
22
dieOnError(zx_status_t Status,const char * FnName,uptr Size)23 static void NORETURN dieOnError(zx_status_t Status, const char *FnName,
24 uptr Size) {
25 ScopedString Error;
26 Error.append("SCUDO ERROR: %s failed with size %zuKB (%s)", FnName,
27 Size >> 10, _zx_status_get_string(Status));
28 outputRaw(Error.data());
29 die();
30 }
31
setVmoName(zx_handle_t Vmo,const char * Name)32 static void setVmoName(zx_handle_t Vmo, const char *Name) {
33 size_t Len = strlen(Name);
34 DCHECK_LT(Len, ZX_MAX_NAME_LEN);
35 zx_status_t Status = _zx_object_set_property(Vmo, ZX_PROP_NAME, Name, Len);
36 CHECK_EQ(Status, ZX_OK);
37 }
38
39 // Returns the (cached) base address of the root VMAR.
getRootVmarBase()40 static uptr getRootVmarBase() {
41 static atomic_uptr CachedResult = {0};
42
43 uptr Result = atomic_load(&CachedResult, memory_order_acquire);
44 if (UNLIKELY(!Result)) {
45 zx_info_vmar_t VmarInfo;
46 zx_status_t Status =
47 _zx_object_get_info(_zx_vmar_root_self(), ZX_INFO_VMAR, &VmarInfo,
48 sizeof(VmarInfo), nullptr, nullptr);
49 CHECK_EQ(Status, ZX_OK);
50 CHECK_NE(VmarInfo.base, 0);
51
52 atomic_store(&CachedResult, VmarInfo.base, memory_order_release);
53 Result = VmarInfo.base;
54 }
55
56 return Result;
57 }
58
59 // Lazily creates and then always returns the same zero-sized VMO.
getPlaceholderVmo()60 static zx_handle_t getPlaceholderVmo() {
61 static atomic_u32 StoredVmo = {ZX_HANDLE_INVALID};
62
63 zx_handle_t Vmo = atomic_load(&StoredVmo, memory_order_acquire);
64 if (UNLIKELY(Vmo == ZX_HANDLE_INVALID)) {
65 // Create a zero-sized placeholder VMO.
66 zx_status_t Status = _zx_vmo_create(0, 0, &Vmo);
67 if (UNLIKELY(Status != ZX_OK))
68 dieOnError(Status, "zx_vmo_create", 0);
69
70 setVmoName(Vmo, "scudo:reserved");
71
72 // Atomically store its handle. If some other thread wins the race, use its
73 // handle and discard ours.
74 zx_handle_t OldValue = atomic_compare_exchange_strong(
75 &StoredVmo, ZX_HANDLE_INVALID, Vmo, memory_order_acq_rel);
76 if (UNLIKELY(OldValue != ZX_HANDLE_INVALID)) {
77 Status = _zx_handle_close(Vmo);
78 CHECK_EQ(Status, ZX_OK);
79
80 Vmo = OldValue;
81 }
82 }
83
84 return Vmo;
85 }
86
87 // Checks if MAP_ALLOWNOMEM allows the given error code.
IsNoMemError(zx_status_t Status)88 static bool IsNoMemError(zx_status_t Status) {
89 // Note: _zx_vmar_map returns ZX_ERR_NO_RESOURCES if the VMAR does not contain
90 // a suitable free spot.
91 return Status == ZX_ERR_NO_MEMORY || Status == ZX_ERR_NO_RESOURCES;
92 }
93
94 // Note: this constructor is only called by ReservedMemoryFuchsia::dispatch.
MemMapFuchsia(uptr Base,uptr Capacity)95 MemMapFuchsia::MemMapFuchsia(uptr Base, uptr Capacity)
96 : MapAddr(Base), WindowBase(Base), WindowSize(Capacity) {
97 // Create the VMO.
98 zx_status_t Status = _zx_vmo_create(Capacity, 0, &Vmo);
99 if (UNLIKELY(Status != ZX_OK))
100 dieOnError(Status, "zx_vmo_create", Capacity);
101
102 setVmoName(Vmo, "scudo:dispatched");
103 }
104
mapImpl(UNUSED uptr Addr,uptr Size,const char * Name,uptr Flags)105 bool MemMapFuchsia::mapImpl(UNUSED uptr Addr, uptr Size, const char *Name,
106 uptr Flags) {
107 const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
108 const bool PreCommit = !!(Flags & MAP_PRECOMMIT);
109 const bool NoAccess = !!(Flags & MAP_NOACCESS);
110
111 // Create the VMO.
112 zx_status_t Status = _zx_vmo_create(Size, 0, &Vmo);
113 if (UNLIKELY(Status != ZX_OK)) {
114 if (AllowNoMem && IsNoMemError(Status))
115 return false;
116 dieOnError(Status, "zx_vmo_create", Size);
117 }
118
119 if (Name != nullptr)
120 setVmoName(Vmo, Name);
121
122 // Map it.
123 zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS;
124 if (!NoAccess)
125 MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
126 Status =
127 _zx_vmar_map(_zx_vmar_root_self(), MapFlags, 0, Vmo, 0, Size, &MapAddr);
128 if (UNLIKELY(Status != ZX_OK)) {
129 if (AllowNoMem && IsNoMemError(Status)) {
130 Status = _zx_handle_close(Vmo);
131 CHECK_EQ(Status, ZX_OK);
132
133 MapAddr = 0;
134 Vmo = ZX_HANDLE_INVALID;
135 return false;
136 }
137 dieOnError(Status, "zx_vmar_map", Size);
138 }
139
140 if (PreCommit) {
141 Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr,
142 Size, nullptr, 0);
143 CHECK_EQ(Status, ZX_OK);
144 }
145
146 WindowBase = MapAddr;
147 WindowSize = Size;
148 return true;
149 }
150
unmapImpl(uptr Addr,uptr Size)151 void MemMapFuchsia::unmapImpl(uptr Addr, uptr Size) {
152 zx_status_t Status;
153
154 if (Size == WindowSize) {
155 // NOTE: Closing first and then unmapping seems slightly faster than doing
156 // the same operations in the opposite order.
157 Status = _zx_handle_close(Vmo);
158 CHECK_EQ(Status, ZX_OK);
159 Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size);
160 CHECK_EQ(Status, ZX_OK);
161
162 MapAddr = WindowBase = WindowSize = 0;
163 Vmo = ZX_HANDLE_INVALID;
164 } else {
165 // Unmap the subrange.
166 Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size);
167 CHECK_EQ(Status, ZX_OK);
168
169 // Decommit the pages that we just unmapped.
170 Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, Addr - MapAddr, Size,
171 nullptr, 0);
172 CHECK_EQ(Status, ZX_OK);
173
174 if (Addr == WindowBase)
175 WindowBase += Size;
176 WindowSize -= Size;
177 }
178 }
179
remapImpl(uptr Addr,uptr Size,const char * Name,uptr Flags)180 bool MemMapFuchsia::remapImpl(uptr Addr, uptr Size, const char *Name,
181 uptr Flags) {
182 const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
183 const bool PreCommit = !!(Flags & MAP_PRECOMMIT);
184 const bool NoAccess = !!(Flags & MAP_NOACCESS);
185
186 // NOTE: This will rename the *whole* VMO, not only the requested portion of
187 // it. But we cannot do better than this given the MemMap API. In practice,
188 // the upper layers of Scudo always pass the same Name for a given MemMap.
189 if (Name != nullptr)
190 setVmoName(Vmo, Name);
191
192 uptr MappedAddr;
193 zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS | ZX_VM_SPECIFIC_OVERWRITE;
194 if (!NoAccess)
195 MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
196 zx_status_t Status =
197 _zx_vmar_map(_zx_vmar_root_self(), MapFlags, Addr - getRootVmarBase(),
198 Vmo, Addr - MapAddr, Size, &MappedAddr);
199 if (UNLIKELY(Status != ZX_OK)) {
200 if (AllowNoMem && IsNoMemError(Status))
201 return false;
202 dieOnError(Status, "zx_vmar_map", Size);
203 }
204 DCHECK_EQ(Addr, MappedAddr);
205
206 if (PreCommit) {
207 Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr,
208 Size, nullptr, 0);
209 CHECK_EQ(Status, ZX_OK);
210 }
211
212 return true;
213 }
214
releaseAndZeroPagesToOSImpl(uptr From,uptr Size)215 void MemMapFuchsia::releaseAndZeroPagesToOSImpl(uptr From, uptr Size) {
216 zx_status_t Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, From - MapAddr,
217 Size, nullptr, 0);
218 CHECK_EQ(Status, ZX_OK);
219 }
220
setMemoryPermissionImpl(uptr Addr,uptr Size,uptr Flags)221 void MemMapFuchsia::setMemoryPermissionImpl(uptr Addr, uptr Size, uptr Flags) {
222 const bool NoAccess = !!(Flags & MAP_NOACCESS);
223
224 zx_vm_option_t MapFlags = 0;
225 if (!NoAccess)
226 MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
227 zx_status_t Status =
228 _zx_vmar_protect(_zx_vmar_root_self(), MapFlags, Addr, Size);
229 CHECK_EQ(Status, ZX_OK);
230 }
231
createImpl(UNUSED uptr Addr,uptr Size,UNUSED const char * Name,uptr Flags)232 bool ReservedMemoryFuchsia::createImpl(UNUSED uptr Addr, uptr Size,
233 UNUSED const char *Name, uptr Flags) {
234 const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
235
236 // Reserve memory by mapping the placeholder VMO without any permission.
237 zx_status_t Status = _zx_vmar_map(_zx_vmar_root_self(), ZX_VM_ALLOW_FAULTS, 0,
238 getPlaceholderVmo(), 0, Size, &Base);
239 if (UNLIKELY(Status != ZX_OK)) {
240 if (AllowNoMem && IsNoMemError(Status))
241 return false;
242 dieOnError(Status, "zx_vmar_map", Size);
243 }
244
245 Capacity = Size;
246 return true;
247 }
248
releaseImpl()249 void ReservedMemoryFuchsia::releaseImpl() {
250 zx_status_t Status = _zx_vmar_unmap(_zx_vmar_root_self(), Base, Capacity);
251 CHECK_EQ(Status, ZX_OK);
252 }
253
dispatchImpl(uptr Addr,uptr Size)254 ReservedMemoryFuchsia::MemMapT ReservedMemoryFuchsia::dispatchImpl(uptr Addr,
255 uptr Size) {
256 return ReservedMemoryFuchsia::MemMapT(Addr, Size);
257 }
258
259 } // namespace scudo
260
261 #endif // SCUDO_FUCHSIA
262