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