blob: 96f484539ae819c874e7cd6b3aca4766c2fc7b83 [file] [log] [blame]
#include <cheri.hh>
#include <compartment.h>
#include <cstdlib>
#include <errno.h>
#include <locks.hh>
#include <queue.h>
#include <token.h>
using namespace CHERI;
using Debug = ConditionalDebug<false, "Queue compartment">;
namespace
{
__always_inline SKey receive_key()
{
return STATIC_SEALING_TYPE(ReceiveHandle);
}
__always_inline SKey send_key()
{
return STATIC_SEALING_TYPE(SendHandle);
}
struct QueueEndpoint
{
QueueHandle handle;
void *allocation;
// Lock that protects against double free.
FlagLockPriorityInherited lock;
};
} // namespace
int queue_create_sealed(Timeout *timeout,
struct SObjStruct *heapCapability,
struct SObjStruct **outQueueSend,
struct SObjStruct **outQueueReceive,
size_t elementSize,
size_t elementCount)
{
if (!check_timeout_pointer(timeout))
{
return -EPERM;
}
// Allocate the queue endpoints
auto [send, sendSealed] =
token_allocate<QueueEndpoint>(timeout, heapCapability, send_key());
if (!send)
{
return timeout->may_block() ? -ENOMEM : -ETIMEDOUT;
}
auto [receive, receiveSealed] =
token_allocate<QueueEndpoint>(timeout, heapCapability, receive_key());
if (!receive)
{
token_obj_destroy(heapCapability, send_key(), sendSealed);
return timeout->may_block() ? -ENOMEM : -ETIMEDOUT;
}
// Bidirectional queue handle
QueueHandle handle;
// The pointer to the queue that is used when freeing
void *freeBuffer;
// Allocate the queue object
int ret = queue_create(
timeout, heapCapability, &handle, &freeBuffer, elementSize, elementCount);
if (ret != 0)
{
token_obj_destroy(heapCapability, send_key(), sendSealed);
token_obj_destroy(heapCapability, receive_key(), receiveSealed);
return ret;
}
send->handle = queue_make_send_handle(handle);
send->allocation = freeBuffer;
receive->handle = queue_make_receive_handle(handle);
receive->allocation = freeBuffer;
// Add a second claim on the buffer so that we can free the queue by freeing
// it twice, once in each endpoint.
// TODO we should check the return value of `heap_claim`
heap_claim(heapCapability, freeBuffer);
if (int claimed = heap_claim_fast(timeout, outQueueSend, outQueueReceive);
claimed != 0)
{
return claimed;
}
if (!check_pointer<PermissionSet{Permission::Load,
Permission::Store,
Permission::LoadStoreCapability}>(
outQueueReceive, sizeof(void *)) ||
!check_pointer<PermissionSet{Permission::Load,
Permission::Store,
Permission::LoadStoreCapability}>(
outQueueSend, sizeof(void *)))
{
// Free twice because we claimed it once in addition to the original
// allocation.
heap_free(heapCapability, freeBuffer);
heap_free(heapCapability, freeBuffer);
return -EPERM;
}
*outQueueSend = sendSealed;
*outQueueReceive = receiveSealed;
return 0;
}
int queue_destroy_sealed(Timeout *timeout,
struct SObjStruct *heapCapability,
struct SObjStruct *queueHandle)
{
if (!check_timeout_pointer(timeout))
{
return -EPERM;
}
Debug::log("Destroying queue {}", queueHandle);
auto token = receive_key();
auto *end = token_unseal(token, Sealed<QueueEndpoint>{queueHandle});
// This function takes either endpoint, so we need to try unsealing with
// both keys.
if (!end)
{
token = send_key();
end = token_unseal(token, Sealed<QueueEndpoint>{queueHandle});
}
if (!end)
{
return -EINVAL;
}
// Don't bother with a lock guard: we will destroy this lock if we reach the
// end. If we lose a race here, this will trap and we will implicitly return
// `-ECOMPARTMENTFAIL`.
if (!end->lock.try_lock(timeout))
{
return -ETIMEDOUT;
}
if (heap_free(heapCapability, end->allocation) != 0)
{
end->lock.unlock();
return -EPERM;
}
token_obj_destroy(heapCapability, token, queueHandle);
return 0;
}
int queue_send_sealed(Timeout *timeout,
struct SObjStruct *handle,
const void *src)
{
auto *end = token_unseal(send_key(), Sealed<QueueEndpoint>{handle});
// If we failed to unseal, or if the timeout pointer is invalid, invalid
// argument error.
if (!end || !check_timeout_pointer(timeout))
{
return -EINVAL;
}
return queue_send(timeout, &end->handle, src);
}
int queue_receive_sealed(Timeout *timeout, struct SObjStruct *handle, void *dst)
{
auto *end = token_unseal(receive_key(), Sealed<QueueEndpoint>{handle});
// If we failed to unseal, or if the timeout is on the heap, invalid
// argument error.
if (!end || !check_timeout_pointer(timeout))
{
return -EINVAL;
}
return queue_receive(timeout, &end->handle, dst);
}
int multiwaiter_queue_receive_init_sealed(struct EventWaiterSource *source,
struct SObjStruct *handle)
{
auto *end = token_unseal(receive_key(), Sealed<QueueEndpoint>{handle});
if (!end)
{
return -EINVAL;
}
multiwaiter_queue_receive_init(source, &end->handle);
return 0;
}
int multiwaiter_queue_send_init_sealed(struct EventWaiterSource *source,
struct SObjStruct *handle)
{
auto *end = token_unseal(send_key(), Sealed<QueueEndpoint>{handle});
if (!end)
{
return -EINVAL;
}
multiwaiter_queue_send_init(source, &end->handle);
return 0;
}
int queue_items_remaining_sealed(struct SObjStruct *queueHandle, size_t *items)
{
auto *end = token_unseal(receive_key(), Sealed<QueueEndpoint>{queueHandle});
// This function takes either endpoint, so we need to try unsealing with
// both keys.
if (!end)
{
end = token_unseal(send_key(), Sealed<QueueEndpoint>{queueHandle});
}
queue_items_remaining(&end->handle, items);
return 0;
}