xref: /freebsd/contrib/llvm-project/compiler-rt/lib/scudo/standalone/mem_map_fuchsia.cpp (revision 56b17de1e8360fe131d425de20b5e75ff3ea897c)
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