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