Optionally consider RemoteAllocator heads Tame

This commit is contained in:
Nathaniel Wesley Filardo
2021-10-12 02:11:57 +01:00
committed by Nathaniel Wesley Filardo
parent 96155db640
commit 97950a9fca
4 changed files with 81 additions and 10 deletions

View File

@@ -77,6 +77,15 @@ namespace snmalloc
* for allocating core allocators.
*/
bool LocalAllocSupportsLazyInit = true;
/**
* Are the front and back pointers to the message queue in a RemoteAllocator
* considered to be capptr_bounds::Wildness::Tame (as opposed to Wild)?
* That is, is it presumed that clients or other potentialadversaries cannot
* access the front and back pointers themselves, even if they can access
* the queue nodes themselves (which are always considered Wild)?
*/
bool QueueHeadsAreTame = true;
};
/**

View File

@@ -424,7 +424,24 @@ namespace snmalloc
auto& entry = SharedStateHandle::Pagemap::get_metaentry(
local_state, snmalloc::address_cast(p));
auto r = message_queue().dequeue(key_global, domesticate);
std::pair<freelist::HeadPtr, bool> r;
if constexpr (SharedStateHandle::Options.QueueHeadsAreTame)
{
/*
* The front of the queue has already been validated; just change the
* annotating type.
*/
auto domesticate_first = [](freelist::QueuePtr p)
SNMALLOC_FAST_PATH_LAMBDA {
return freelist::HeadPtr(p.unsafe_ptr());
};
r =
message_queue().dequeue(key_global, domesticate_first, domesticate);
}
else
{
r = message_queue().dequeue(key_global, domesticate, domesticate);
}
if (unlikely(!r.second))
break;

View File

@@ -29,6 +29,33 @@ namespace snmalloc
*/
inline static FreeListKey key_global(0xdeadbeef, 0xbeefdead, 0xdeadbeef);
/**
*
* A RemoteAllocator is the message queue of freed objects. It exposes a MPSC
* append-only atomic queue that uses one xchg per append.
*
* The internal pointers are considered QueuePtr-s to support deployment
* scenarios in which the RemoteAllocator itself is exposed to the client.
* This is excessively paranoid in the common case that the RemoteAllocator-s
* are as "hard" for the client to reach as the Pagemap, which we trust to
* store not just Tame CapPtr<>s but raw C++ pointers.
*
* While we could try to condition the types used here on a flag in the
* backend's `struct Flags Options` value, we instead expose two domesticator
* callbacks at the interface and are careful to use one for the front and
* back values and the other for pointers read from the queue itself. That's
* not ideal, but it lets the client condition its behavior appropriately and
* prevents us from accidentally following either of these pointers in generic
* code.
*
* `domesticate_head` is used for the pointer used to reach the of the queue,
* while `domesticate_queue` is used to traverse the first link in the queue
* itself. In the case that the RemoteAllocator is not easily accessible to
* the client, `domesticate_head` can just be a type coersion, and
* `domesticate_queue` should perform actual validation. If the
* RemoteAllocator is exposed to the client, both Domesticators should perform
* validation.
*/
struct alignas(REMOTE_MIN_ALIGN) RemoteAllocator
{
using alloc_id_t = address_t;
@@ -74,13 +101,16 @@ namespace snmalloc
/**
* Pushes a list of messages to the queue. Each message from first to
* last should be linked together through their next pointers.
*
* The Domesticator here is used only on pointers read from the head. See
* the commentary on the class.
*/
template<typename Domesticator>
template<typename Domesticator_head>
void enqueue(
freelist::HeadPtr first,
freelist::HeadPtr last,
const FreeListKey& key,
Domesticator domesticate)
Domesticator_head domesticate_head)
{
invariant();
freelist::Object::atomic_store_null(last, key);
@@ -89,7 +119,7 @@ namespace snmalloc
freelist::QueuePtr prev =
back.exchange(capptr_rewild(last), std::memory_order_release);
freelist::Object::atomic_store_next(domesticate(prev), first, key);
freelist::Object::atomic_store_next(domesticate_head(prev), first, key);
}
freelist::QueuePtr peek()
@@ -99,14 +129,19 @@ namespace snmalloc
/**
* Returns the front message, or null if not possible to return a message.
*
* Takes a domestication callback for each of "pointers read from head" and
* "pointers read from queue". See the commentary on the class.
*/
template<typename Domesticator>
std::pair<freelist::HeadPtr, bool>
dequeue(const FreeListKey& key, Domesticator domesticate)
template<typename Domesticator_head, typename Domesticator_queue>
std::pair<freelist::HeadPtr, bool> dequeue(
const FreeListKey& key,
Domesticator_head domesticate_head,
Domesticator_queue domesticate_queue)
{
invariant();
freelist::HeadPtr first = domesticate(front);
freelist::HeadPtr next = first->atomic_read_next(key, domesticate);
freelist::HeadPtr first = domesticate_head(front);
freelist::HeadPtr next = first->atomic_read_next(key, domesticate_queue);
if (next != nullptr)
{

View File

@@ -103,7 +103,17 @@ namespace snmalloc
auto [first, last] = list[i].extract_segment(key);
MetaEntry entry = SharedStateHandle::Pagemap::get_metaentry(
local_state, address_cast(first));
entry.get_remote()->enqueue(first, last, key, domesticate);
if constexpr (SharedStateHandle::Options.QueueHeadsAreTame)
{
auto domesticate_nop = [](freelist::QueuePtr p) {
return freelist::HeadPtr(p.unsafe_ptr());
};
entry.get_remote()->enqueue(first, last, key, domesticate_nop);
}
else
{
entry.get_remote()->enqueue(first, last, key, domesticate);
}
sent_something = true;
}
}