xref: /freebsd-src/contrib/llvm-project/compiler-rt/lib/scudo/standalone/mem_map_fuchsia.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
106c3fb27SDimitry Andric //===-- mem_map_fuchsia.cpp -------------------------------------*- C++ -*-===//
206c3fb27SDimitry Andric //
306c3fb27SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
406c3fb27SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
506c3fb27SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
606c3fb27SDimitry Andric //
706c3fb27SDimitry Andric //===----------------------------------------------------------------------===//
806c3fb27SDimitry Andric 
906c3fb27SDimitry Andric #include "mem_map_fuchsia.h"
1006c3fb27SDimitry Andric 
1106c3fb27SDimitry Andric #include "atomic_helpers.h"
1206c3fb27SDimitry Andric #include "common.h"
1306c3fb27SDimitry Andric #include "string_utils.h"
1406c3fb27SDimitry Andric 
1506c3fb27SDimitry Andric #if SCUDO_FUCHSIA
1606c3fb27SDimitry Andric 
1706c3fb27SDimitry Andric #include <zircon/process.h>
1806c3fb27SDimitry Andric #include <zircon/status.h>
1906c3fb27SDimitry Andric #include <zircon/syscalls.h>
2006c3fb27SDimitry Andric 
2106c3fb27SDimitry Andric namespace scudo {
2206c3fb27SDimitry Andric 
2306c3fb27SDimitry Andric static void NORETURN dieOnError(zx_status_t Status, const char *FnName,
2406c3fb27SDimitry Andric                                 uptr Size) {
25*0fca6ea1SDimitry Andric   ScopedString Error;
26*0fca6ea1SDimitry Andric   Error.append("SCUDO ERROR: %s failed with size %zuKB (%s)", FnName,
2706c3fb27SDimitry Andric                Size >> 10, _zx_status_get_string(Status));
28*0fca6ea1SDimitry Andric   outputRaw(Error.data());
2906c3fb27SDimitry Andric   die();
3006c3fb27SDimitry Andric }
3106c3fb27SDimitry Andric 
3206c3fb27SDimitry Andric static void setVmoName(zx_handle_t Vmo, const char *Name) {
3306c3fb27SDimitry Andric   size_t Len = strlen(Name);
3406c3fb27SDimitry Andric   DCHECK_LT(Len, ZX_MAX_NAME_LEN);
3506c3fb27SDimitry Andric   zx_status_t Status = _zx_object_set_property(Vmo, ZX_PROP_NAME, Name, Len);
3606c3fb27SDimitry Andric   CHECK_EQ(Status, ZX_OK);
3706c3fb27SDimitry Andric }
3806c3fb27SDimitry Andric 
3906c3fb27SDimitry Andric // Returns the (cached) base address of the root VMAR.
4006c3fb27SDimitry Andric static uptr getRootVmarBase() {
4106c3fb27SDimitry Andric   static atomic_uptr CachedResult = {0};
4206c3fb27SDimitry Andric 
435f757f3fSDimitry Andric   uptr Result = atomic_load(&CachedResult, memory_order_acquire);
4406c3fb27SDimitry Andric   if (UNLIKELY(!Result)) {
4506c3fb27SDimitry Andric     zx_info_vmar_t VmarInfo;
4606c3fb27SDimitry Andric     zx_status_t Status =
4706c3fb27SDimitry Andric         _zx_object_get_info(_zx_vmar_root_self(), ZX_INFO_VMAR, &VmarInfo,
4806c3fb27SDimitry Andric                             sizeof(VmarInfo), nullptr, nullptr);
4906c3fb27SDimitry Andric     CHECK_EQ(Status, ZX_OK);
5006c3fb27SDimitry Andric     CHECK_NE(VmarInfo.base, 0);
5106c3fb27SDimitry Andric 
525f757f3fSDimitry Andric     atomic_store(&CachedResult, VmarInfo.base, memory_order_release);
5306c3fb27SDimitry Andric     Result = VmarInfo.base;
5406c3fb27SDimitry Andric   }
5506c3fb27SDimitry Andric 
5606c3fb27SDimitry Andric   return Result;
5706c3fb27SDimitry Andric }
5806c3fb27SDimitry Andric 
5906c3fb27SDimitry Andric // Lazily creates and then always returns the same zero-sized VMO.
6006c3fb27SDimitry Andric static zx_handle_t getPlaceholderVmo() {
6106c3fb27SDimitry Andric   static atomic_u32 StoredVmo = {ZX_HANDLE_INVALID};
6206c3fb27SDimitry Andric 
635f757f3fSDimitry Andric   zx_handle_t Vmo = atomic_load(&StoredVmo, memory_order_acquire);
6406c3fb27SDimitry Andric   if (UNLIKELY(Vmo == ZX_HANDLE_INVALID)) {
6506c3fb27SDimitry Andric     // Create a zero-sized placeholder VMO.
6606c3fb27SDimitry Andric     zx_status_t Status = _zx_vmo_create(0, 0, &Vmo);
6706c3fb27SDimitry Andric     if (UNLIKELY(Status != ZX_OK))
6806c3fb27SDimitry Andric       dieOnError(Status, "zx_vmo_create", 0);
6906c3fb27SDimitry Andric 
7006c3fb27SDimitry Andric     setVmoName(Vmo, "scudo:reserved");
7106c3fb27SDimitry Andric 
7206c3fb27SDimitry Andric     // Atomically store its handle. If some other thread wins the race, use its
7306c3fb27SDimitry Andric     // handle and discard ours.
745f757f3fSDimitry Andric     zx_handle_t OldValue = atomic_compare_exchange_strong(
755f757f3fSDimitry Andric         &StoredVmo, ZX_HANDLE_INVALID, Vmo, memory_order_acq_rel);
765f757f3fSDimitry Andric     if (UNLIKELY(OldValue != ZX_HANDLE_INVALID)) {
7706c3fb27SDimitry Andric       Status = _zx_handle_close(Vmo);
7806c3fb27SDimitry Andric       CHECK_EQ(Status, ZX_OK);
7906c3fb27SDimitry Andric 
8006c3fb27SDimitry Andric       Vmo = OldValue;
8106c3fb27SDimitry Andric     }
8206c3fb27SDimitry Andric   }
8306c3fb27SDimitry Andric 
8406c3fb27SDimitry Andric   return Vmo;
8506c3fb27SDimitry Andric }
8606c3fb27SDimitry Andric 
87*0fca6ea1SDimitry Andric // Checks if MAP_ALLOWNOMEM allows the given error code.
88*0fca6ea1SDimitry Andric static bool IsNoMemError(zx_status_t Status) {
89*0fca6ea1SDimitry Andric   // Note: _zx_vmar_map returns ZX_ERR_NO_RESOURCES if the VMAR does not contain
90*0fca6ea1SDimitry Andric   // a suitable free spot.
91*0fca6ea1SDimitry Andric   return Status == ZX_ERR_NO_MEMORY || Status == ZX_ERR_NO_RESOURCES;
92*0fca6ea1SDimitry Andric }
93*0fca6ea1SDimitry Andric 
94*0fca6ea1SDimitry Andric // Note: this constructor is only called by ReservedMemoryFuchsia::dispatch.
9506c3fb27SDimitry Andric MemMapFuchsia::MemMapFuchsia(uptr Base, uptr Capacity)
9606c3fb27SDimitry Andric     : MapAddr(Base), WindowBase(Base), WindowSize(Capacity) {
9706c3fb27SDimitry Andric   // Create the VMO.
9806c3fb27SDimitry Andric   zx_status_t Status = _zx_vmo_create(Capacity, 0, &Vmo);
9906c3fb27SDimitry Andric   if (UNLIKELY(Status != ZX_OK))
10006c3fb27SDimitry Andric     dieOnError(Status, "zx_vmo_create", Capacity);
101*0fca6ea1SDimitry Andric 
102*0fca6ea1SDimitry Andric   setVmoName(Vmo, "scudo:dispatched");
10306c3fb27SDimitry Andric }
10406c3fb27SDimitry Andric 
10506c3fb27SDimitry Andric bool MemMapFuchsia::mapImpl(UNUSED uptr Addr, uptr Size, const char *Name,
10606c3fb27SDimitry Andric                             uptr Flags) {
10706c3fb27SDimitry Andric   const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
10806c3fb27SDimitry Andric   const bool PreCommit = !!(Flags & MAP_PRECOMMIT);
10906c3fb27SDimitry Andric   const bool NoAccess = !!(Flags & MAP_NOACCESS);
11006c3fb27SDimitry Andric 
11106c3fb27SDimitry Andric   // Create the VMO.
11206c3fb27SDimitry Andric   zx_status_t Status = _zx_vmo_create(Size, 0, &Vmo);
11306c3fb27SDimitry Andric   if (UNLIKELY(Status != ZX_OK)) {
114*0fca6ea1SDimitry Andric     if (AllowNoMem && IsNoMemError(Status))
11506c3fb27SDimitry Andric       return false;
116*0fca6ea1SDimitry Andric     dieOnError(Status, "zx_vmo_create", Size);
11706c3fb27SDimitry Andric   }
11806c3fb27SDimitry Andric 
11906c3fb27SDimitry Andric   if (Name != nullptr)
12006c3fb27SDimitry Andric     setVmoName(Vmo, Name);
12106c3fb27SDimitry Andric 
12206c3fb27SDimitry Andric   // Map it.
12306c3fb27SDimitry Andric   zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS;
12406c3fb27SDimitry Andric   if (!NoAccess)
12506c3fb27SDimitry Andric     MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
12606c3fb27SDimitry Andric   Status =
12706c3fb27SDimitry Andric       _zx_vmar_map(_zx_vmar_root_self(), MapFlags, 0, Vmo, 0, Size, &MapAddr);
12806c3fb27SDimitry Andric   if (UNLIKELY(Status != ZX_OK)) {
129*0fca6ea1SDimitry Andric     if (AllowNoMem && IsNoMemError(Status)) {
13006c3fb27SDimitry Andric       Status = _zx_handle_close(Vmo);
13106c3fb27SDimitry Andric       CHECK_EQ(Status, ZX_OK);
13206c3fb27SDimitry Andric 
13306c3fb27SDimitry Andric       MapAddr = 0;
13406c3fb27SDimitry Andric       Vmo = ZX_HANDLE_INVALID;
13506c3fb27SDimitry Andric       return false;
13606c3fb27SDimitry Andric     }
137*0fca6ea1SDimitry Andric     dieOnError(Status, "zx_vmar_map", Size);
138*0fca6ea1SDimitry Andric   }
13906c3fb27SDimitry Andric 
14006c3fb27SDimitry Andric   if (PreCommit) {
14106c3fb27SDimitry Andric     Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr,
14206c3fb27SDimitry Andric                                Size, nullptr, 0);
14306c3fb27SDimitry Andric     CHECK_EQ(Status, ZX_OK);
14406c3fb27SDimitry Andric   }
14506c3fb27SDimitry Andric 
14606c3fb27SDimitry Andric   WindowBase = MapAddr;
14706c3fb27SDimitry Andric   WindowSize = Size;
14806c3fb27SDimitry Andric   return true;
14906c3fb27SDimitry Andric }
15006c3fb27SDimitry Andric 
15106c3fb27SDimitry Andric void MemMapFuchsia::unmapImpl(uptr Addr, uptr Size) {
15206c3fb27SDimitry Andric   zx_status_t Status;
15306c3fb27SDimitry Andric 
15406c3fb27SDimitry Andric   if (Size == WindowSize) {
15506c3fb27SDimitry Andric     // NOTE: Closing first and then unmapping seems slightly faster than doing
15606c3fb27SDimitry Andric     // the same operations in the opposite order.
15706c3fb27SDimitry Andric     Status = _zx_handle_close(Vmo);
15806c3fb27SDimitry Andric     CHECK_EQ(Status, ZX_OK);
15906c3fb27SDimitry Andric     Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size);
16006c3fb27SDimitry Andric     CHECK_EQ(Status, ZX_OK);
16106c3fb27SDimitry Andric 
16206c3fb27SDimitry Andric     MapAddr = WindowBase = WindowSize = 0;
16306c3fb27SDimitry Andric     Vmo = ZX_HANDLE_INVALID;
16406c3fb27SDimitry Andric   } else {
16506c3fb27SDimitry Andric     // Unmap the subrange.
16606c3fb27SDimitry Andric     Status = _zx_vmar_unmap(_zx_vmar_root_self(), Addr, Size);
16706c3fb27SDimitry Andric     CHECK_EQ(Status, ZX_OK);
16806c3fb27SDimitry Andric 
16906c3fb27SDimitry Andric     // Decommit the pages that we just unmapped.
17006c3fb27SDimitry Andric     Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, Addr - MapAddr, Size,
17106c3fb27SDimitry Andric                               nullptr, 0);
17206c3fb27SDimitry Andric     CHECK_EQ(Status, ZX_OK);
17306c3fb27SDimitry Andric 
17406c3fb27SDimitry Andric     if (Addr == WindowBase)
17506c3fb27SDimitry Andric       WindowBase += Size;
17606c3fb27SDimitry Andric     WindowSize -= Size;
17706c3fb27SDimitry Andric   }
17806c3fb27SDimitry Andric }
17906c3fb27SDimitry Andric 
18006c3fb27SDimitry Andric bool MemMapFuchsia::remapImpl(uptr Addr, uptr Size, const char *Name,
18106c3fb27SDimitry Andric                               uptr Flags) {
18206c3fb27SDimitry Andric   const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
18306c3fb27SDimitry Andric   const bool PreCommit = !!(Flags & MAP_PRECOMMIT);
18406c3fb27SDimitry Andric   const bool NoAccess = !!(Flags & MAP_NOACCESS);
18506c3fb27SDimitry Andric 
18606c3fb27SDimitry Andric   // NOTE: This will rename the *whole* VMO, not only the requested portion of
18706c3fb27SDimitry Andric   // it. But we cannot do better than this given the MemMap API. In practice,
18806c3fb27SDimitry Andric   // the upper layers of Scudo always pass the same Name for a given MemMap.
18906c3fb27SDimitry Andric   if (Name != nullptr)
19006c3fb27SDimitry Andric     setVmoName(Vmo, Name);
19106c3fb27SDimitry Andric 
19206c3fb27SDimitry Andric   uptr MappedAddr;
19306c3fb27SDimitry Andric   zx_vm_option_t MapFlags = ZX_VM_ALLOW_FAULTS | ZX_VM_SPECIFIC_OVERWRITE;
19406c3fb27SDimitry Andric   if (!NoAccess)
19506c3fb27SDimitry Andric     MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
19606c3fb27SDimitry Andric   zx_status_t Status =
19706c3fb27SDimitry Andric       _zx_vmar_map(_zx_vmar_root_self(), MapFlags, Addr - getRootVmarBase(),
19806c3fb27SDimitry Andric                    Vmo, Addr - MapAddr, Size, &MappedAddr);
19906c3fb27SDimitry Andric   if (UNLIKELY(Status != ZX_OK)) {
200*0fca6ea1SDimitry Andric     if (AllowNoMem && IsNoMemError(Status))
20106c3fb27SDimitry Andric       return false;
202*0fca6ea1SDimitry Andric     dieOnError(Status, "zx_vmar_map", Size);
20306c3fb27SDimitry Andric   }
20406c3fb27SDimitry Andric   DCHECK_EQ(Addr, MappedAddr);
20506c3fb27SDimitry Andric 
20606c3fb27SDimitry Andric   if (PreCommit) {
20706c3fb27SDimitry Andric     Status = _zx_vmar_op_range(_zx_vmar_root_self(), ZX_VMAR_OP_COMMIT, MapAddr,
20806c3fb27SDimitry Andric                                Size, nullptr, 0);
20906c3fb27SDimitry Andric     CHECK_EQ(Status, ZX_OK);
21006c3fb27SDimitry Andric   }
21106c3fb27SDimitry Andric 
21206c3fb27SDimitry Andric   return true;
21306c3fb27SDimitry Andric }
21406c3fb27SDimitry Andric 
21506c3fb27SDimitry Andric void MemMapFuchsia::releaseAndZeroPagesToOSImpl(uptr From, uptr Size) {
21606c3fb27SDimitry Andric   zx_status_t Status = _zx_vmo_op_range(Vmo, ZX_VMO_OP_DECOMMIT, From - MapAddr,
21706c3fb27SDimitry Andric                                         Size, nullptr, 0);
21806c3fb27SDimitry Andric   CHECK_EQ(Status, ZX_OK);
21906c3fb27SDimitry Andric }
22006c3fb27SDimitry Andric 
22106c3fb27SDimitry Andric void MemMapFuchsia::setMemoryPermissionImpl(uptr Addr, uptr Size, uptr Flags) {
22206c3fb27SDimitry Andric   const bool NoAccess = !!(Flags & MAP_NOACCESS);
22306c3fb27SDimitry Andric 
22406c3fb27SDimitry Andric   zx_vm_option_t MapFlags = 0;
22506c3fb27SDimitry Andric   if (!NoAccess)
22606c3fb27SDimitry Andric     MapFlags |= ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
22706c3fb27SDimitry Andric   zx_status_t Status =
22806c3fb27SDimitry Andric       _zx_vmar_protect(_zx_vmar_root_self(), MapFlags, Addr, Size);
22906c3fb27SDimitry Andric   CHECK_EQ(Status, ZX_OK);
23006c3fb27SDimitry Andric }
23106c3fb27SDimitry Andric 
23206c3fb27SDimitry Andric bool ReservedMemoryFuchsia::createImpl(UNUSED uptr Addr, uptr Size,
23306c3fb27SDimitry Andric                                        UNUSED const char *Name, uptr Flags) {
23406c3fb27SDimitry Andric   const bool AllowNoMem = !!(Flags & MAP_ALLOWNOMEM);
23506c3fb27SDimitry Andric 
23606c3fb27SDimitry Andric   // Reserve memory by mapping the placeholder VMO without any permission.
23706c3fb27SDimitry Andric   zx_status_t Status = _zx_vmar_map(_zx_vmar_root_self(), ZX_VM_ALLOW_FAULTS, 0,
23806c3fb27SDimitry Andric                                     getPlaceholderVmo(), 0, Size, &Base);
23906c3fb27SDimitry Andric   if (UNLIKELY(Status != ZX_OK)) {
240*0fca6ea1SDimitry Andric     if (AllowNoMem && IsNoMemError(Status))
24106c3fb27SDimitry Andric       return false;
242*0fca6ea1SDimitry Andric     dieOnError(Status, "zx_vmar_map", Size);
24306c3fb27SDimitry Andric   }
24406c3fb27SDimitry Andric 
24506c3fb27SDimitry Andric   Capacity = Size;
24606c3fb27SDimitry Andric   return true;
24706c3fb27SDimitry Andric }
24806c3fb27SDimitry Andric 
24906c3fb27SDimitry Andric void ReservedMemoryFuchsia::releaseImpl() {
25006c3fb27SDimitry Andric   zx_status_t Status = _zx_vmar_unmap(_zx_vmar_root_self(), Base, Capacity);
25106c3fb27SDimitry Andric   CHECK_EQ(Status, ZX_OK);
25206c3fb27SDimitry Andric }
25306c3fb27SDimitry Andric 
25406c3fb27SDimitry Andric ReservedMemoryFuchsia::MemMapT ReservedMemoryFuchsia::dispatchImpl(uptr Addr,
25506c3fb27SDimitry Andric                                                                    uptr Size) {
25606c3fb27SDimitry Andric   return ReservedMemoryFuchsia::MemMapT(Addr, Size);
25706c3fb27SDimitry Andric }
25806c3fb27SDimitry Andric 
25906c3fb27SDimitry Andric } // namespace scudo
26006c3fb27SDimitry Andric 
26106c3fb27SDimitry Andric #endif // SCUDO_FUCHSIA
262