Do not abort if a vector cannot increase its own capacity. In that case, push_back calls silently fail. Modify the ScopedString implementation so that it no longer requires two passes to do the format. Move the helper functions to be private member functions so that they can use push_back directly. This allows the capacity to be increased under the hood and/or silently discards data if the capacity is exceeded and cannot be increased. Add new tests for the Vector and ScopedString for capacity increase failures. Doing this so that if a map call fails, and we are attempting to write an error string, we can still get some of the message dumped. This also avoids crashing in Scudo code, and makes the caller handle any failures.
252 lines
7.8 KiB
C++
252 lines
7.8 KiB
C++
//===-- 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 <zircon/process.h>
|
|
#include <zircon/status.h>
|
|
#include <zircon/syscalls.h>
|
|
|
|
namespace scudo {
|
|
|
|
static void NORETURN dieOnError(zx_status_t Status, const char *FnName,
|
|
uptr Size) {
|
|
ScopedString Error;
|
|
Error.append("SCUDO ERROR: %s failed with size %zuKB (%s)", FnName,
|
|
Size >> 10, _zx_status_get_string(Status));
|
|
outputRaw(Error.data());
|
|
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
|