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:
committed by
Matthew Parkinson
parent
b4efc40aa6
commit
55a7ad2d58
@@ -16,31 +16,37 @@ The PAL must implement the following methods:
|
|||||||
```
|
```
|
||||||
Report a fatal error and exit.
|
Report a fatal error and exit.
|
||||||
|
|
||||||
|
The memory that snmalloc is supplied from the Pal should be in one of three
|
||||||
|
states
|
||||||
|
|
||||||
|
* `using`
|
||||||
|
* `using readonly`
|
||||||
|
* `not using`
|
||||||
|
|
||||||
|
Before accessing the memory for a read, `snmalloc` will change the state to
|
||||||
|
either `using` or `using readonly`,
|
||||||
|
and before a write by it will change the state to `using`.
|
||||||
|
If memory is not required any more, then `snmalloc` will change the state to
|
||||||
|
`not using`, and will ensure that it notifies the `Pal` again
|
||||||
|
before it every accesses that memory again.
|
||||||
|
The `not using` state allows the `Pal` to recycle the memory for other purposes.
|
||||||
|
If `PalEnforceAccess` is set to true, then accessing that has not been notified
|
||||||
|
correctly should trigger an exception/segfault.
|
||||||
|
|
||||||
|
The state for a particular region of memory is set with
|
||||||
```c++
|
```c++
|
||||||
static void notify_not_using(void* p, size_t size) noexcept;
|
static void notify_not_using(void* p, size_t size) noexcept;
|
||||||
```
|
|
||||||
Notify the system that the range of memory from `p` to `p` + `size` is no
|
|
||||||
longer in use, allowing the underlying physical pages to recycled for other
|
|
||||||
purposes.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
template<ZeroMem zero_mem>
|
template<ZeroMem zero_mem>
|
||||||
static void notify_using(void* p, size_t size) noexcept;
|
static void notify_using(void* p, size_t size) noexcept;
|
||||||
```
|
|
||||||
Notify the system that the range of memory from `p` to `p` + `size` is now in use.
|
|
||||||
On systems that lazily provide physical memory to virtual mappings, this
|
|
||||||
function may not be required to do anything.
|
|
||||||
If the template parameter is set to `YesZero` then this function is also
|
|
||||||
responsible for ensuring that the newly requested memory is full of zeros.
|
|
||||||
|
|
||||||
```c++
|
|
||||||
static void notify_using_readonly(void* p, size_t size) noexcept;
|
static void notify_using_readonly(void* p, size_t size) noexcept;
|
||||||
```
|
```
|
||||||
Notify the system that the range of memory from `p` to `p` + `size` is now in use
|
These functions notify the system that the range of memory from `p` to `p` +
|
||||||
for read-only access. This is currently only requried on platforms that support
|
`size` is in the relevant state.
|
||||||
`LazyCommit`.
|
|
||||||
On systems that lazily provide physical memory to virtual mappings, this
|
If the template parameter is set to `YesZero` then `notify_using` must ensure
|
||||||
function may not be required to do anything.
|
the range is full of zeros.
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
template<bool page_aligned = false>
|
template<bool page_aligned = false>
|
||||||
@@ -54,18 +60,17 @@ efficient to request that the operating system provides background-zeroed
|
|||||||
pages, rather than zeroing them synchronously in this call
|
pages, rather than zeroing them synchronously in this call
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
template<bool committed>
|
template<bool state_using>
|
||||||
static void* reserve_aligned(size_t size) noexcept;
|
static void* reserve_aligned(size_t size) noexcept;
|
||||||
static void* reserve(size_t size) noexcept;
|
static void* reserve(size_t size) noexcept;
|
||||||
```
|
```
|
||||||
All platforms should provide `reserve` and can optionally provide
|
All platforms should provide `reserve` and can optionally provide
|
||||||
`reserve_aligned` if the underlying system can provide strongly aligned
|
`reserve_aligned` if the underlying system can provide strongly aligned
|
||||||
memory regions.
|
memory regions.
|
||||||
If the system guarantees only page alignment, implement only the second. The Pal is
|
If the system guarantees only page alignment, implement only the second. `snmalloc` will
|
||||||
free to overallocate based on the platform's desire and snmalloc
|
overallocate to ensure it can find suitably aligned blocks inside the region.
|
||||||
will find suitably aligned blocks inside the region. `reserve` should
|
`reserve` should consider memory initially as `not_using`, and `snmalloc` will notify when it
|
||||||
not commit memory as snmalloc will commit the range of memory it requires of what
|
needs the range of memory.
|
||||||
is returned.
|
|
||||||
|
|
||||||
If the system provides strong alignment, implement the first to return memory
|
If the system provides strong alignment, implement the first to return memory
|
||||||
at the desired alignment. If providing the first, then the `Pal` should also
|
at the desired alignment. If providing the first, then the `Pal` should also
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ namespace snmalloc
|
|||||||
{
|
{
|
||||||
SNMALLOC_ASSERT(is_aligned_block<page_size>(p, size));
|
SNMALLOC_ASSERT(is_aligned_block<page_size>(p, size));
|
||||||
|
|
||||||
# if defined(SNMALLOC_CHECK_CLIENT) && !defined(NDEBUG)
|
# if !defined(NDEBUG)
|
||||||
memset(p, 0x5a, size);
|
memset(p, 0x5a, size);
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
@@ -126,12 +126,13 @@ namespace snmalloc
|
|||||||
while (madvise(p, size, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN)
|
while (madvise(p, size, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN)
|
||||||
;
|
;
|
||||||
|
|
||||||
# ifdef SNMALLOC_CHECK_CLIENT
|
if constexpr (PalEnforceAccess)
|
||||||
// This must occur after `MADV_FREE_REUSABLE`.
|
{
|
||||||
//
|
// This must occur after `MADV_FREE_REUSABLE`.
|
||||||
// `mach_vm_protect` is observably slower in benchmarks.
|
//
|
||||||
mprotect(p, size, PROT_NONE);
|
// `mach_vm_protect` is observably slower in benchmarks.
|
||||||
# endif
|
mprotect(p, size, PROT_NONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,12 +181,13 @@ namespace snmalloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifdef SNMALLOC_CHECK_CLIENT
|
if constexpr (PalEnforceAccess)
|
||||||
// Mark pages as writable for `madvise` below.
|
{
|
||||||
//
|
// Mark pages as writable for `madvise` below.
|
||||||
// `mach_vm_protect` is observably slower in benchmarks.
|
//
|
||||||
mprotect(p, size, PROT_READ | PROT_WRITE);
|
// `mach_vm_protect` is observably slower in benchmarks.
|
||||||
# endif
|
mprotect(p, size, PROT_READ | PROT_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
// `MADV_FREE_REUSE` can only be applied to writable pages,
|
// `MADV_FREE_REUSE` can only be applied to writable pages,
|
||||||
// otherwise it's an error.
|
// otherwise it's an error.
|
||||||
@@ -205,7 +207,7 @@ namespace snmalloc
|
|||||||
// Apple's `mmap` doesn't support user-specified alignment and only
|
// Apple's `mmap` doesn't support user-specified alignment and only
|
||||||
// guarantees mappings are aligned to the system page size, so we use
|
// guarantees mappings are aligned to the system page size, so we use
|
||||||
// `mach_vm_map` instead.
|
// `mach_vm_map` instead.
|
||||||
template<bool committed>
|
template<bool state_using>
|
||||||
static void* reserve_aligned(size_t size) noexcept
|
static void* reserve_aligned(size_t size) noexcept
|
||||||
{
|
{
|
||||||
SNMALLOC_ASSERT(bits::is_pow2(size));
|
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.
|
// must be initialized to 0 or addr is interepreted as a lower-bound.
|
||||||
mach_vm_address_t addr = 0;
|
mach_vm_address_t addr = 0;
|
||||||
|
|
||||||
# ifdef SNMALLOC_CHECK_CLIENT
|
vm_prot_t prot = (state_using || !PalEnforceAccess) ?
|
||||||
vm_prot_t prot = committed ? VM_PROT_READ | VM_PROT_WRITE : VM_PROT_NONE;
|
VM_PROT_READ | VM_PROT_WRITE :
|
||||||
# else
|
VM_PROT_NONE;
|
||||||
vm_prot_t prot = VM_PROT_READ | VM_PROT_WRITE;
|
|
||||||
# endif
|
|
||||||
|
|
||||||
kern_return_t kr = mach_vm_map(
|
kern_return_t kr = mach_vm_map(
|
||||||
mach_task_self(),
|
mach_task_self(),
|
||||||
|
|||||||
@@ -34,14 +34,16 @@ namespace snmalloc
|
|||||||
static void notify_not_using(void* p, size_t size) noexcept
|
static void notify_not_using(void* p, size_t size) noexcept
|
||||||
{
|
{
|
||||||
SNMALLOC_ASSERT(is_aligned_block<OS::page_size>(p, size));
|
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);
|
memset(p, 0x5a, size);
|
||||||
#endif
|
#endif
|
||||||
madvise(p, size, MADV_FREE);
|
madvise(p, size, MADV_FREE);
|
||||||
#ifdef SNMALLOC_CHECK_CLIENT
|
|
||||||
mprotect(p, size, PROT_NONE);
|
if constexpr (PalEnforceAccess)
|
||||||
#endif
|
{
|
||||||
|
mprotect(p, size, PROT_NONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace snmalloc
|
} // namespace snmalloc
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace snmalloc
|
|||||||
/**
|
/**
|
||||||
* Reserve memory at a specific alignment.
|
* Reserve memory at a specific alignment.
|
||||||
*/
|
*/
|
||||||
template<bool committed>
|
template<bool state_using>
|
||||||
static void* reserve_aligned(size_t size) noexcept
|
static void* reserve_aligned(size_t size) noexcept
|
||||||
{
|
{
|
||||||
// Alignment must be a power of 2.
|
// Alignment must be a power of 2.
|
||||||
@@ -37,11 +37,8 @@ namespace snmalloc
|
|||||||
|
|
||||||
int log2align = static_cast<int>(bits::next_pow2_bits(size));
|
int log2align = static_cast<int>(bits::next_pow2_bits(size));
|
||||||
|
|
||||||
#ifdef SNMALLOC_CHECK_CLIENT
|
auto prot =
|
||||||
auto prot = committed ? PROT_READ | PROT_WRITE : PROT_NONE;
|
state_using || !PalEnforceAccess ? PROT_READ | PROT_WRITE : PROT_NONE;
|
||||||
#else
|
|
||||||
auto prot = PROT_READ | PROT_WRITE;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void* p = mmap(
|
void* p = mmap(
|
||||||
nullptr,
|
nullptr,
|
||||||
|
|||||||
@@ -6,6 +6,21 @@
|
|||||||
|
|
||||||
namespace snmalloc
|
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
|
* 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.
|
* should be set in the PAL's `pal_features` static constexpr field.
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace snmalloc
|
|||||||
::bzero(p, size);
|
::bzero(p, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<bool committed>
|
template<bool state_using>
|
||||||
static void* reserve_aligned(size_t size) noexcept
|
static void* reserve_aligned(size_t size) noexcept
|
||||||
{
|
{
|
||||||
SNMALLOC_ASSERT(bits::is_pow2(size));
|
SNMALLOC_ASSERT(bits::is_pow2(size));
|
||||||
@@ -80,7 +80,7 @@ namespace snmalloc
|
|||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (committed)
|
if (state_using)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
kmem_back(kernel_object, addr, size, M_ZERO | M_WAITOK) !=
|
kmem_back(kernel_object, addr, size, M_ZERO | M_WAITOK) !=
|
||||||
|
|||||||
@@ -178,17 +178,21 @@ namespace snmalloc
|
|||||||
static void notify_not_using(void* p, size_t size) noexcept
|
static void notify_not_using(void* p, size_t size) noexcept
|
||||||
{
|
{
|
||||||
SNMALLOC_ASSERT(is_aligned_block<OS::page_size>(p, size));
|
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
|
if constexpr (PalEnforceAccess)
|
||||||
// assumptions on the content.
|
{
|
||||||
# if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
memset(p, 0x5a, size);
|
// Fill memory so that when we switch the pages back on we don't make
|
||||||
# endif
|
// assumptions on the content.
|
||||||
mprotect(p, size, PROT_NONE);
|
memset(p, 0x5a, size);
|
||||||
#else
|
|
||||||
UNUSED(p);
|
|
||||||
UNUSED(size);
|
|
||||||
#endif
|
#endif
|
||||||
|
mprotect(p, size, PROT_NONE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UNUSED(p);
|
||||||
|
UNUSED(size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,12 +208,13 @@ namespace snmalloc
|
|||||||
SNMALLOC_ASSERT(
|
SNMALLOC_ASSERT(
|
||||||
is_aligned_block<OS::page_size>(p, size) || (zero_mem == NoZero));
|
is_aligned_block<OS::page_size>(p, size) || (zero_mem == NoZero));
|
||||||
|
|
||||||
#ifdef SNMALLOC_CHECK_CLIENT
|
if constexpr (PalEnforceAccess)
|
||||||
mprotect(p, size, PROT_READ | PROT_WRITE);
|
mprotect(p, size, PROT_READ | PROT_WRITE);
|
||||||
#else
|
else
|
||||||
UNUSED(p);
|
{
|
||||||
UNUSED(size);
|
UNUSED(p);
|
||||||
#endif
|
UNUSED(size);
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (zero_mem == YesZero)
|
if constexpr (zero_mem == YesZero)
|
||||||
zero<true>(p, size);
|
zero<true>(p, size);
|
||||||
@@ -225,12 +230,13 @@ namespace snmalloc
|
|||||||
{
|
{
|
||||||
SNMALLOC_ASSERT(is_aligned_block<OS::page_size>(p, size));
|
SNMALLOC_ASSERT(is_aligned_block<OS::page_size>(p, size));
|
||||||
|
|
||||||
#ifdef SNMALLOC_CHECK_CLIENT
|
if constexpr (PalEnforceAccess)
|
||||||
mprotect(p, size, PROT_READ);
|
mprotect(p, size, PROT_READ);
|
||||||
#else
|
else
|
||||||
UNUSED(p);
|
{
|
||||||
UNUSED(size);
|
UNUSED(p);
|
||||||
#endif
|
UNUSED(size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -286,11 +292,10 @@ namespace snmalloc
|
|||||||
*/
|
*/
|
||||||
static void* reserve(size_t size) noexcept
|
static void* reserve(size_t size) noexcept
|
||||||
{
|
{
|
||||||
#ifdef SNMALLOC_CHECK_CLIENT
|
// If enforcing access, map pages initially as None, and then
|
||||||
auto prot = PROT_NONE;
|
// add permissions as required. Otherwise, immediately give all
|
||||||
#else
|
// access as this is the most efficient to implement.
|
||||||
auto prot = PROT_READ | PROT_WRITE;
|
auto prot = PalEnforceAccess ? PROT_NONE : PROT_READ | PROT_WRITE;
|
||||||
#endif
|
|
||||||
|
|
||||||
void* p = mmap(
|
void* p = mmap(
|
||||||
nullptr,
|
nullptr,
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ namespace snmalloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ifdef PLATFORM_HAS_VIRTUALALLOC2
|
# ifdef PLATFORM_HAS_VIRTUALALLOC2
|
||||||
template<bool committed>
|
template<bool state_using>
|
||||||
static void* reserve_aligned(size_t size) noexcept
|
static void* reserve_aligned(size_t size) noexcept
|
||||||
{
|
{
|
||||||
SNMALLOC_ASSERT(bits::is_pow2(size));
|
SNMALLOC_ASSERT(bits::is_pow2(size));
|
||||||
@@ -166,7 +166,7 @@ namespace snmalloc
|
|||||||
|
|
||||||
DWORD flags = MEM_RESERVE;
|
DWORD flags = MEM_RESERVE;
|
||||||
|
|
||||||
if (committed)
|
if (state_using)
|
||||||
flags |= MEM_COMMIT;
|
flags |= MEM_COMMIT;
|
||||||
|
|
||||||
// If we're on Windows 10 or newer, we can use the VirtualAlloc2
|
// If we're on Windows 10 or newer, we can use the VirtualAlloc2
|
||||||
|
|||||||
Reference in New Issue
Block a user