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 | |
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 | |
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. |
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. |
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. |
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. |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |