Files
snmalloc/src/pal/pal_apple.h
Matthew Parkinson 55a7ad2d58 Introduce PalEnforceAccess
The various Pals were given different meanings in CHECK_CLIENT and
non-CHECK_CLIENT builds.  This was because it is essential
that in the CHECK_CLIENT builds access is prevented, when not requested.

This PR separates the CHECK_CLIENT concept from how the Pal should be
implemented.
2021-09-28 09:23:52 +01:00

288 lines
9.6 KiB
C++

#pragma once
#ifdef __APPLE__
# include "pal_bsd.h"
# include <CommonCrypto/CommonRandom.h>
# include <errno.h>
# include <mach/mach_init.h>
# include <mach/mach_vm.h>
# include <mach/vm_statistics.h>
# include <mach/vm_types.h>
# include <stdio.h>
# include <stdlib.h>
# include <sys/mman.h>
# include <unistd.h>
namespace snmalloc
{
/**
* PAL implementation for Apple systems (macOS, iOS, watchOS, tvOS...).
*/
template<uint8_t PALAnonID = PALAnonDefaultID>
class PALApple : public PALBSD<PALApple<>>
{
public:
/**
* The features exported by this PAL.
*/
static constexpr uint64_t pal_features =
AlignedAllocation | LazyCommit | Entropy;
/*
* `page_size`
*
* On 64-bit ARM platforms, the page size (for user-space) is 16KiB.
* Otherwise (e.g. x86_64) it is 4KiB.
*
* macOS on Apple Silicon ARM does support 4KiB pages, but they are reserved
* for "exotic" processes (i.e. Rosetta 2) and kernel-space. Using 4KiB
* pages from user-space in "native" (non-translated) processes is incorrect
* and will cause bugs.
*
* However, Apple's 64-bit embedded ARM-based platforms (phones, pads, tvs)
* do not support 4KiB pages.
*
*/
static constexpr size_t page_size = Aal::aal_name == ARM ? 0x4000 : 0x1000;
static constexpr size_t minimum_alloc_size = page_size;
/*
* Memory Tag
*
* A memory tag is an 8-bit value that denotes auxillary "type information"
* of a vm region. This tag can be used for marking memory for profiling and
* debugging, or instructing the kernel to perform tag-specific behavior.
* (E.g. VM_MEMORY_MALLOC{_*} is reused by default, unless it is no longer
* in its "original state". See `vm_map_entry_is_reusable` in
* `osfmk/vm/vm_map.c` for more details of this behavior.)
*
* Memory tags are encoded using `VM_MAKE_TAG(tag_value)`, and can be passed
* to the kernel by either `mmap` or `mach_vm_map`:
* 1. `fd` argument of `mmap`.
* 2. `flags` argument of `mach_vm_map`.
*
* There are currently 4 categories of memory tags:
*
* 1. Reserved: [0, 39]. Typically used for Apple libraries and services.
* Use may trigger undocumented kernel-based behavior.
*
* 2. Defined "placeholders": [39, 98]. Typically used for Apple libraries
* and services.
*
* 3. Undefined "placeholders": [99, 239]. Unallocated by Apple. Typically
* used for libraries. (E.g. LLVM sanitizers use 99.)
*
* 4. Application specific: [240, 255]
*
* See <mach/vm_statistics.h> for more details about memory tags and their
* uses.
*
* In the future, we may switch our default memory tag from "category 4" to
* "category 3", thereby affording us a "well-known" memory tag that can be
* easily identified in tools such as vmmap(1) or Instruments.
*
*/
// Encoded memory tag passed to `mmap`.
static constexpr int anonymous_memory_fd =
int(VM_MAKE_TAG(uint32_t(PALAnonID)));
// Encoded memory tag passed to `mach_vm_map`.
static constexpr int default_mach_vm_map_flags =
int(VM_MAKE_TAG(uint32_t(PALAnonID)));
/**
* Notify platform that we will not be using these pages.
*
* We deviate from `PALBSD::notify_not_using` b/c `MADV_FREE` does not
* behave as expected on Apple platforms. The pages are never marked as
* "reusable" by the kernel and this can be observed through profiling. E.g.
* at least ~75% to ~90% less dirty memory is used by `func-malloc-16`
* (observed on x86_64).
*
* Apple's own malloc implementation as well as many ports for Apple
* Operating Systems use MADV_FREE_REUS{E, ABLE} instead of MADV_FREE. See:
* https://opensource.apple.com/source/libmalloc/libmalloc-53.1.1/src/magazine_malloc.c.auto.html
* https://bugs.chromium.org/p/chromium/issues/detail?id=713892
*
*/
static void notify_not_using(void* p, size_t size) noexcept
{
SNMALLOC_ASSERT(is_aligned_block<page_size>(p, size));
# if !defined(NDEBUG)
memset(p, 0x5a, size);
# endif
// `MADV_FREE_REUSABLE` can only be applied to writable pages,
// otherwise it's an error.
//
// `mach_vm_behavior_set` is observably slower in benchmarks.
//
// macOS 11 Big Sur may behave in an undocumented manner.
while (madvise(p, size, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN)
;
if constexpr (PalEnforceAccess)
{
// This must occur after `MADV_FREE_REUSABLE`.
//
// `mach_vm_protect` is observably slower in benchmarks.
mprotect(p, size, PROT_NONE);
}
}
/**
* Notify platform that we will be using these pages.
*
* We deviate from `PALPOSIX::notify_using` for three reasons:
* 1. `MADV_FREE_REUSABLE` must be paired with `MADV_FREE_REUSE`.
* 2. `MADV_FREE_REUSE` must only be applied to writable pages, otherwise it
* is an error.
* 3. `PALPOSIX::notify_using` will apply mprotect(PROT_READ|PROT_WRITE) to
* the pages, and then call `PALPOSIX::zero<true>` (overwrite pages with
* mmap, and if mmap fails call bzero on the pages). This is very wasteful;
* if mmap succeeds we do not need to change the permissions of the pages
* since it is done during mmap. Instead `PALApple::notify_using` will try
* to overwrite with mmap, and if mmap fails, apply
* mprotect(PROT_READ|PROT_WRITE), madvise(MADV_REUSE), and finally call
* `bzero` to clear the pages.
*
* Currently, `PALPOSIX::zero` will call `bzero` on the pages that `mmap`
* failed to overwrite. In the future, we should duplicate the behavior of
* `PALWindows` and abort the process if a `mmap` call fails. But for now we
* are going to be consistent with the behavior of the other POSIX PAL
* implementations.
*
*/
template<ZeroMem zero_mem>
static void notify_using(void* p, size_t size) noexcept
{
KeepErrno e;
SNMALLOC_ASSERT(
is_aligned_block<page_size>(p, size) || (zero_mem == NoZero));
if constexpr (zero_mem == YesZero)
{
void* r = mmap(
p,
size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
anonymous_memory_fd,
0);
if (likely(r != MAP_FAILED))
{
return;
}
}
if constexpr (PalEnforceAccess)
{
// Mark pages as writable for `madvise` below.
//
// `mach_vm_protect` is observably slower in benchmarks.
mprotect(p, size, PROT_READ | PROT_WRITE);
}
// `MADV_FREE_REUSE` can only be applied to writable pages,
// otherwise it's an error.
//
// `mach_vm_behavior_set` is observably slower in benchmarks.
//
// macOS 11 Big Sur may behave in an undocumented manner.
while (madvise(p, size, MADV_FREE_REUSE) == -1 && errno == EAGAIN)
;
if constexpr (zero_mem == YesZero)
{
::bzero(p, size);
}
}
// Apple's `mmap` doesn't support user-specified alignment and only
// guarantees mappings are aligned to the system page size, so we use
// `mach_vm_map` instead.
template<bool state_using>
static void* reserve_aligned(size_t size) noexcept
{
SNMALLOC_ASSERT(bits::is_pow2(size));
SNMALLOC_ASSERT(size >= minimum_alloc_size);
// mask has least-significant bits set
mach_vm_offset_t mask = size - 1;
int flags = VM_FLAGS_ANYWHERE | default_mach_vm_map_flags;
// must be initialized to 0 or addr is interepreted as a lower-bound.
mach_vm_address_t addr = 0;
vm_prot_t prot = (state_using || !PalEnforceAccess) ?
VM_PROT_READ | VM_PROT_WRITE :
VM_PROT_NONE;
kern_return_t kr = mach_vm_map(
mach_task_self(),
&addr,
size,
mask,
flags,
MEMORY_OBJECT_NULL,
0,
TRUE,
prot,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_COPY);
if (unlikely(kr != KERN_SUCCESS))
{
return nullptr;
}
return reinterpret_cast<void*>(addr);
}
/**
* Source of Entropy
*
* Apple platforms have a working `getentropy(2)` implementation.
* However, it is not allowed on the App Store and Apple actively
* discourages its use. The substitutes `arc4random_buf(3)`,
* `CCRandomGenerateBytes`, and `SecRandomCopyBytes` are recommended
* instead.
*
* `CCRandomGenerateBytes` was selected because:
*
* 1. The implementation of `arc4random_buf(3)` differs from its
* documentation. It is documented to never fail, yet its'
* implementation can fail silently: it calls the function
* `ccrng_generate`, but ignores the error case.
* `CCRandomGenerateBytes` is built on the same function, but can return an
* error code in case of failure. See:
* https://opensource.apple.com/source/Libc/Libc-1439.40.11/gen/FreeBSD/arc4random.c.auto.html
* https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60061/include/CommonRandom.h.auto.html
*
* 2. `SecRandomCopyBytes` introduces a dependency on `Security.framework`.
* `CCRandomGenerateBytes` introduces no new dependencies.
*
*/
static uint64_t get_entropy64()
{
uint64_t result;
if (
CCRandomGenerateBytes(
reinterpret_cast<void*>(&result), sizeof(result)) != kCCSuccess)
{
error("Failed to get system randomness");
}
return result;
}
};
} // namespace snmalloc
#endif