• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MemMapFuchsia(uptr Base,uptr Capacity)94 MemMapFuchsia::MemMapFuchsia(uptr Base, uptr Capacity)
95     : MapAddr(Base), WindowBase(Base), WindowSize(Capacity) {
96   // Create the VMO.
97   zx_status_t Status = _zx_vmo_create(Capacity, 0, &Vmo);
98   if (UNLIKELY(Status != ZX_OK))
99     dieOnError(Status, "zx_vmo_create", Capacity);
100 }
101 
mapImpl(UNUSED uptr Addr,uptr Size,const char * Name,uptr Flags)102 bool MemMapFuchsia::mapImpl(UNUSED uptr Addr, uptr Size, const char *Name,
103                             uptr Flags) {
104   const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
105   const bool PreCommit = !!(Flags & MAP_PRECOMMIT);
106   const bool NoAccess = !!(Flags & MAP_NOACCESS);
107 
108   // Create the VMO.
109   zx_status_t Status = _zx_vmo_create(Size, 0, &Vmo);
110   if (UNLIKELY(Status != ZX_OK)) {
111     if (AllowNoMem && IsNoMemError(Status))
112       return false;
113     dieOnError(Status, "zx_vmo_create", Size);
114   }
115 
116   if (Name != nullptr)
117     setVmoName(Vmo, Name);
118 
119   // Map it.
120   zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS;
121   if (!NoAccess)
122     MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
123   Status =
124       _zx_vmar_map(_zx_vmar_root_self(), MapFlags, 0, Vmo, 0, Size, &MapAddr);
125   if (UNLIKELY(Status != ZX_OK)) {
126     if (AllowNoMem && IsNoMemError(Status)) {
127       Status = _zx_handle_close(Vmo);
128       CHECK_EQ(Status, ZX_OK);
129 
130       MapAddr = 0;
131       Vmo = ZX_HANDLE_INVALID;
132       return false;
133     }
134     dieOnError(Status, "zx_vmar_map", Size);
135   }
136 
137   if (PreCommit) {
138     Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr,
139                                Size, nullptr, 0);
140     CHECK_EQ(Status, ZX_OK);
141   }
142 
143   WindowBase = MapAddr;
144   WindowSize = Size;
145   return true;
146 }
147 
unmapImpl(uptr Addr,uptr Size)148 void MemMapFuchsia::unmapImpl(uptr Addr, uptr Size) {
149   zx_status_t Status;
150 
151   if (Size == WindowSize) {
152     // NOTE: Closing first and then unmapping seems slightly faster than doing
153     // the same operations in the opposite order.
154     Status = _zx_handle_close(Vmo);
155     CHECK_EQ(Status, ZX_OK);
156     Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size);
157     CHECK_EQ(Status, ZX_OK);
158 
159     MapAddr = WindowBase = WindowSize = 0;
160     Vmo = ZX_HANDLE_INVALID;
161   } else {
162     // Unmap the subrange.
163     Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size);
164     CHECK_EQ(Status, ZX_OK);
165 
166     // Decommit the pages that we just unmapped.
167     Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, Addr - MapAddr, Size,
168                               nullptr, 0);
169     CHECK_EQ(Status, ZX_OK);
170 
171     if (Addr == WindowBase)
172       WindowBase += Size;
173     WindowSize -= Size;
174   }
175 }
176 
remapImpl(uptr Addr,uptr Size,const char * Name,uptr Flags)177 bool MemMapFuchsia::remapImpl(uptr Addr, uptr Size, const char *Name,
178                               uptr Flags) {
179   const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
180   const bool PreCommit = !!(Flags & MAP_PRECOMMIT);
181   const bool NoAccess = !!(Flags & MAP_NOACCESS);
182 
183   // NOTE: This will rename the *whole* VMO, not only the requested portion of
184   // it. But we cannot do better than this given the MemMap API. In practice,
185   // the upper layers of Scudo always pass the same Name for a given MemMap.
186   if (Name != nullptr)
187     setVmoName(Vmo, Name);
188 
189   uptr MappedAddr;
190   zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS | ZX_VM_SPECIFIC_OVERWRITE;
191   if (!NoAccess)
192     MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
193   zx_status_t Status =
194       _zx_vmar_map(_zx_vmar_root_self(), MapFlags, Addr - getRootVmarBase(),
195                    Vmo, Addr - MapAddr, Size, &MappedAddr);
196   if (UNLIKELY(Status != ZX_OK)) {
197     if (AllowNoMem && IsNoMemError(Status))
198       return false;
199     dieOnError(Status, "zx_vmar_map", Size);
200   }
201   DCHECK_EQ(Addr, MappedAddr);
202 
203   if (PreCommit) {
204     Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr,
205                                Size, nullptr, 0);
206     CHECK_EQ(Status, ZX_OK);
207   }
208 
209   return true;
210 }
211 
releaseAndZeroPagesToOSImpl(uptr From,uptr Size)212 void MemMapFuchsia::releaseAndZeroPagesToOSImpl(uptr From, uptr Size) {
213   zx_status_t Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, From - MapAddr,
214                                         Size, nullptr, 0);
215   CHECK_EQ(Status, ZX_OK);
216 }
217 
setMemoryPermissionImpl(uptr Addr,uptr Size,uptr Flags)218 void MemMapFuchsia::setMemoryPermissionImpl(uptr Addr, uptr Size, uptr Flags) {
219   const bool NoAccess = !!(Flags & MAP_NOACCESS);
220 
221   zx_vm_option_t MapFlags = 0;
222   if (!NoAccess)
223     MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
224   zx_status_t Status =
225       _zx_vmar_protect(_zx_vmar_root_self(), MapFlags, Addr, Size);
226   CHECK_EQ(Status, ZX_OK);
227 }
228 
createImpl(UNUSED uptr Addr,uptr Size,UNUSED const char * Name,uptr Flags)229 bool ReservedMemoryFuchsia::createImpl(UNUSED uptr Addr, uptr Size,
230                                        UNUSED const char *Name, uptr Flags) {
231   const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
232 
233   // Reserve memory by mapping the placeholder VMO without any permission.
234   zx_status_t Status = _zx_vmar_map(_zx_vmar_root_self(), ZX_VM_ALLOW_FAULTS, 0,
235                                     getPlaceholderVmo(), 0, Size, &Base);
236   if (UNLIKELY(Status != ZX_OK)) {
237     if (AllowNoMem && IsNoMemError(Status))
238       return false;
239     dieOnError(Status, "zx_vmar_map", Size);
240   }
241 
242   Capacity = Size;
243   return true;
244 }
245 
releaseImpl()246 void ReservedMemoryFuchsia::releaseImpl() {
247   zx_status_t Status = _zx_vmar_unmap(_zx_vmar_root_self(), Base, Capacity);
248   CHECK_EQ(Status, ZX_OK);
249 }
250 
dispatchImpl(uptr Addr,uptr Size)251 ReservedMemoryFuchsia::MemMapT ReservedMemoryFuchsia::dispatchImpl(uptr Addr,
252                                                                    uptr Size) {
253   return ReservedMemoryFuchsia::MemMapT(Addr, Size);
254 }
255 
256 } // namespace scudo
257 
258 #endif // SCUDO_FUCHSIA
259