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.
This commit is contained in:
Matthew Parkinson
2021-09-27 13:32:46 +01:00
committed by Matthew Parkinson
parent b4efc40aa6
commit 55a7ad2d58
8 changed files with 108 additions and 84 deletions

View File

@@ -113,7 +113,7 @@ namespace snmalloc
{
SNMALLOC_ASSERT(is_aligned_block<page_size>(p, size));
# if defined(SNMALLOC_CHECK_CLIENT) && !defined(NDEBUG)
# if !defined(NDEBUG)
memset(p, 0x5a, size);
# endif
@@ -126,12 +126,13 @@ namespace snmalloc
while (madvise(p, size, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN)
;
# ifdef SNMALLOC_CHECK_CLIENT
// This must occur after `MADV_FREE_REUSABLE`.
//
// `mach_vm_protect` is observably slower in benchmarks.
mprotect(p, size, PROT_NONE);
# endif
if constexpr (PalEnforceAccess)
{
// This must occur after `MADV_FREE_REUSABLE`.
//
// `mach_vm_protect` is observably slower in benchmarks.
mprotect(p, size, PROT_NONE);
}
}
/**
@@ -180,12 +181,13 @@ namespace snmalloc
}
}
# ifdef SNMALLOC_CHECK_CLIENT
// Mark pages as writable for `madvise` below.
//
// `mach_vm_protect` is observably slower in benchmarks.
mprotect(p, size, PROT_READ | PROT_WRITE);
# endif
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.
@@ -205,7 +207,7 @@ namespace snmalloc
// 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 committed>
template<bool state_using>
static void* reserve_aligned(size_t size) noexcept
{
SNMALLOC_ASSERT(bits::is_pow2(size));
@@ -219,11 +221,9 @@ namespace snmalloc
// must be initialized to 0 or addr is interepreted as a lower-bound.
mach_vm_address_t addr = 0;
# ifdef SNMALLOC_CHECK_CLIENT
vm_prot_t prot = committed ? VM_PROT_READ | VM_PROT_WRITE : VM_PROT_NONE;
# else
vm_prot_t prot = VM_PROT_READ | VM_PROT_WRITE;
# endif
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(),

View File

@@ -34,14 +34,16 @@ namespace snmalloc
static void notify_not_using(void* p, size_t size) noexcept
{
SNMALLOC_ASSERT(is_aligned_block<OS::page_size>(p, size));
// Call this Pal to simulate the Windows decommit in CI.
#if defined(SNMALLOC_CHECK_CLIENT) && !defined(NDEBUG)
#if !defined(NDEBUG)
memset(p, 0x5a, size);
#endif
madvise(p, size, MADV_FREE);
#ifdef SNMALLOC_CHECK_CLIENT
mprotect(p, size, PROT_NONE);
#endif
if constexpr (PalEnforceAccess)
{
mprotect(p, size, PROT_NONE);
}
}
};
} // namespace snmalloc

View File

@@ -28,7 +28,7 @@ namespace snmalloc
/**
* Reserve memory at a specific alignment.
*/
template<bool committed>
template<bool state_using>
static void* reserve_aligned(size_t size) noexcept
{
// Alignment must be a power of 2.
@@ -37,11 +37,8 @@ namespace snmalloc
int log2align = static_cast<int>(bits::next_pow2_bits(size));
#ifdef SNMALLOC_CHECK_CLIENT
auto prot = committed ? PROT_READ | PROT_WRITE : PROT_NONE;
#else
auto prot = PROT_READ | PROT_WRITE;
#endif
auto prot =
state_using || !PalEnforceAccess ? PROT_READ | PROT_WRITE : PROT_NONE;
void* p = mmap(
nullptr,

View File

@@ -6,6 +6,21 @@
namespace snmalloc
{
/**
* Pal implementations should query this flag to see whether they
* are allowed to optimise memory access, or that they must provide
* exceptions/segfaults if accesses do not obey the
* - using
* - using_readonly
* - not_using
* model.
*/
#ifdef SNMALLOC_CHECK_CLIENT
static constexpr bool PalEnforceAccess = true;
#else
static constexpr bool PalEnforceAccess = false;
#endif
/**
* Flags in a bitfield of optional features that a PAL may support. These
* should be set in the PAL's `pal_features` static constexpr field.

View File

@@ -59,7 +59,7 @@ namespace snmalloc
::bzero(p, size);
}
template<bool committed>
template<bool state_using>
static void* reserve_aligned(size_t size) noexcept
{
SNMALLOC_ASSERT(bits::is_pow2(size));
@@ -80,7 +80,7 @@ namespace snmalloc
{
return nullptr;
}
if (committed)
if (state_using)
{
if (
kmem_back(kernel_object, addr, size, M_ZERO | M_WAITOK) !=

View File

@@ -178,17 +178,21 @@ namespace snmalloc
static void notify_not_using(void* p, size_t size) noexcept
{
SNMALLOC_ASSERT(is_aligned_block<OS::page_size>(p, size));
#ifdef SNMALLOC_CHECK_CLIENT
// Fill memory so that when we switch the pages back on we don't make
// assumptions on the content.
# if !defined(NDEBUG)
memset(p, 0x5a, size);
# endif
mprotect(p, size, PROT_NONE);
#else
UNUSED(p);
UNUSED(size);
if constexpr (PalEnforceAccess)
{
#if !defined(NDEBUG)
// Fill memory so that when we switch the pages back on we don't make
// assumptions on the content.
memset(p, 0x5a, size);
#endif
mprotect(p, size, PROT_NONE);
}
else
{
UNUSED(p);
UNUSED(size);
}
}
/**
@@ -204,12 +208,13 @@ namespace snmalloc
SNMALLOC_ASSERT(
is_aligned_block<OS::page_size>(p, size) || (zero_mem == NoZero));
#ifdef SNMALLOC_CHECK_CLIENT
mprotect(p, size, PROT_READ | PROT_WRITE);
#else
UNUSED(p);
UNUSED(size);
#endif
if constexpr (PalEnforceAccess)
mprotect(p, size, PROT_READ | PROT_WRITE);
else
{
UNUSED(p);
UNUSED(size);
}
if constexpr (zero_mem == YesZero)
zero<true>(p, size);
@@ -225,12 +230,13 @@ namespace snmalloc
{
SNMALLOC_ASSERT(is_aligned_block<OS::page_size>(p, size));
#ifdef SNMALLOC_CHECK_CLIENT
mprotect(p, size, PROT_READ);
#else
UNUSED(p);
UNUSED(size);
#endif
if constexpr (PalEnforceAccess)
mprotect(p, size, PROT_READ);
else
{
UNUSED(p);
UNUSED(size);
}
}
/**
@@ -286,11 +292,10 @@ namespace snmalloc
*/
static void* reserve(size_t size) noexcept
{
#ifdef SNMALLOC_CHECK_CLIENT
auto prot = PROT_NONE;
#else
auto prot = PROT_READ | PROT_WRITE;
#endif
// If enforcing access, map pages initially as None, and then
// add permissions as required. Otherwise, immediately give all
// access as this is the most efficient to implement.
auto prot = PalEnforceAccess ? PROT_NONE : PROT_READ | PROT_WRITE;
void* p = mmap(
nullptr,

View File

@@ -158,7 +158,7 @@ namespace snmalloc
}
# ifdef PLATFORM_HAS_VIRTUALALLOC2
template<bool committed>
template<bool state_using>
static void* reserve_aligned(size_t size) noexcept
{
SNMALLOC_ASSERT(bits::is_pow2(size));
@@ -166,7 +166,7 @@ namespace snmalloc
DWORD flags = MEM_RESERVE;
if (committed)
if (state_using)
flags |= MEM_COMMIT;
// If we're on Windows 10 or newer, we can use the VirtualAlloc2