//===-- mem_map_fuchsia.cpp -------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "mem_map_fuchsia.h" #include "atomic_helpers.h" #include "common.h" #include "string_utils.h" #if SCUDO_FUCHSIA #include #include #include namespace scudo { static void NORETURN dieOnError(zx_status_t Status, const char *FnName, uptr Size) { char Error[128]; formatString(Error, sizeof(Error), "SCUDO ERROR: %s failed with size %zuKB (%s)", FnName, Size >> 10, _zx_status_get_string(Status)); outputRaw(Error); die(); } static void setVmoName(zx_handle_t Vmo, const char *Name) { size_t Len = strlen(Name); DCHECK_LT(Len, ZX_MAX_NAME_LEN); zx_status_t Status = _zx_object_set_property(Vmo, ZX_PROP_NAME, Name, Len); CHECK_EQ(Status, ZX_OK); } // Returns the (cached) base address of the root VMAR. static uptr getRootVmarBase() { static atomic_uptr CachedResult = {0}; uptr Result = atomic_load(&CachedResult, memory_order_acquire); if (UNLIKELY(!Result)) { zx_info_vmar_t VmarInfo; zx_status_t Status = _zx_object_get_info(_zx_vmar_root_self(), ZX_INFO_VMAR, &VmarInfo, sizeof(VmarInfo), nullptr, nullptr); CHECK_EQ(Status, ZX_OK); CHECK_NE(VmarInfo.base, 0); atomic_store(&CachedResult, VmarInfo.base, memory_order_release); Result = VmarInfo.base; } return Result; } // Lazily creates and then always returns the same zero-sized VMO. static zx_handle_t getPlaceholderVmo() { static atomic_u32 StoredVmo = {ZX_HANDLE_INVALID}; zx_handle_t Vmo = atomic_load(&StoredVmo, memory_order_acquire); if (UNLIKELY(Vmo == ZX_HANDLE_INVALID)) { // Create a zero-sized placeholder VMO. zx_status_t Status = _zx_vmo_create(0, 0, &Vmo); if (UNLIKELY(Status != ZX_OK)) dieOnError(Status, "zx_vmo_create", 0); setVmoName(Vmo, "scudo:reserved"); // Atomically store its handle. If some other thread wins the race, use its // handle and discard ours. zx_handle_t OldValue = atomic_compare_exchange_strong( &StoredVmo, ZX_HANDLE_INVALID, Vmo, memory_order_acq_rel); if (UNLIKELY(OldValue != ZX_HANDLE_INVALID)) { Status = _zx_handle_close(Vmo); CHECK_EQ(Status, ZX_OK); Vmo = OldValue; } } return Vmo; } MemMapFuchsia::MemMapFuchsia(uptr Base, uptr Capacity) : MapAddr(Base), WindowBase(Base), WindowSize(Capacity) { // Create the VMO. zx_status_t Status = _zx_vmo_create(Capacity, 0, &Vmo); if (UNLIKELY(Status != ZX_OK)) dieOnError(Status, "zx_vmo_create", Capacity); } bool MemMapFuchsia::mapImpl(UNUSED uptr Addr, uptr Size, const char *Name, uptr Flags) { const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM); const bool PreCommit = !!(Flags & MAP_PRECOMMIT); const bool NoAccess = !!(Flags & MAP_NOACCESS); // Create the VMO. zx_status_t Status = _zx_vmo_create(Size, 0, &Vmo); if (UNLIKELY(Status != ZX_OK)) { if (Status != ZX_ERR_NO_MEMORY || !AllowNoMem) dieOnError(Status, "zx_vmo_create", Size); return false; } if (Name != nullptr) setVmoName(Vmo, Name); // Map it. zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS; if (!NoAccess) MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE; Status = _zx_vmar_map(_zx_vmar_root_self(), MapFlags, 0, Vmo, 0, Size, &MapAddr); if (UNLIKELY(Status != ZX_OK)) { if (Status != ZX_ERR_NO_MEMORY || !AllowNoMem) dieOnError(Status, "zx_vmar_map", Size); Status = _zx_handle_close(Vmo); CHECK_EQ(Status, ZX_OK); MapAddr = 0; Vmo = ZX_HANDLE_INVALID; return false; } if (PreCommit) { Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr, Size, nullptr, 0); CHECK_EQ(Status, ZX_OK); } WindowBase = MapAddr; WindowSize = Size; return true; } void MemMapFuchsia::unmapImpl(uptr Addr, uptr Size) { zx_status_t Status; if (Size == WindowSize) { // NOTE: Closing first and then unmapping seems slightly faster than doing // the same operations in the opposite order. Status = _zx_handle_close(Vmo); CHECK_EQ(Status, ZX_OK); Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size); CHECK_EQ(Status, ZX_OK); MapAddr = WindowBase = WindowSize = 0; Vmo = ZX_HANDLE_INVALID; } else { // Unmap the subrange. Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size); CHECK_EQ(Status, ZX_OK); // Decommit the pages that we just unmapped. Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, Addr - MapAddr, Size, nullptr, 0); CHECK_EQ(Status, ZX_OK); if (Addr == WindowBase) WindowBase += Size; WindowSize -= Size; } } bool MemMapFuchsia::remapImpl(uptr Addr, uptr Size, const char *Name, uptr Flags) { const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM); const bool PreCommit = !!(Flags & MAP_PRECOMMIT); const bool NoAccess = !!(Flags & MAP_NOACCESS); // NOTE: This will rename the *whole* VMO, not only the requested portion of // it. But we cannot do better than this given the MemMap API. In practice, // the upper layers of Scudo always pass the same Name for a given MemMap. if (Name != nullptr) setVmoName(Vmo, Name); uptr MappedAddr; zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS | ZX_VM_SPECIFIC_OVERWRITE; if (!NoAccess) MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE; zx_status_t Status = _zx_vmar_map(_zx_vmar_root_self(), MapFlags, Addr - getRootVmarBase(), Vmo, Addr - MapAddr, Size, &MappedAddr); if (UNLIKELY(Status != ZX_OK)) { if (Status != ZX_ERR_NO_MEMORY || !AllowNoMem) dieOnError(Status, "zx_vmar_map", Size); return false; } DCHECK_EQ(Addr, MappedAddr); if (PreCommit) { Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr, Size, nullptr, 0); CHECK_EQ(Status, ZX_OK); } return true; } void MemMapFuchsia::releaseAndZeroPagesToOSImpl(uptr From, uptr Size) { zx_status_t Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, From - MapAddr, Size, nullptr, 0); CHECK_EQ(Status, ZX_OK); } void MemMapFuchsia::setMemoryPermissionImpl(uptr Addr, uptr Size, uptr Flags) { const bool NoAccess = !!(Flags & MAP_NOACCESS); zx_vm_option_t MapFlags = 0; if (!NoAccess) MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE; zx_status_t Status = _zx_vmar_protect(_zx_vmar_root_self(), MapFlags, Addr, Size); CHECK_EQ(Status, ZX_OK); } bool ReservedMemoryFuchsia::createImpl(UNUSED uptr Addr, uptr Size, UNUSED const char *Name, uptr Flags) { const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM); // Reserve memory by mapping the placeholder VMO without any permission. zx_status_t Status = _zx_vmar_map(_zx_vmar_root_self(), ZX_VM_ALLOW_FAULTS, 0, getPlaceholderVmo(), 0, Size, &Base); if (UNLIKELY(Status != ZX_OK)) { if (Status != ZX_ERR_NO_MEMORY || !AllowNoMem) dieOnError(Status, "zx_vmar_map", Size); return false; } Capacity = Size; return true; } void ReservedMemoryFuchsia::releaseImpl() { zx_status_t Status = _zx_vmar_unmap(_zx_vmar_root_self(), Base, Capacity); CHECK_EQ(Status, ZX_OK); } ReservedMemoryFuchsia::MemMapT ReservedMemoryFuchsia::dispatchImpl(uptr Addr, uptr Size) { return ReservedMemoryFuchsia::MemMapT(Addr, Size); } } // namespace scudo #endif // SCUDO_FUCHSIA