blob: dc16e720065f491cad469dd602473e165effb4f1 [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
/**
* This file contains the interface for a simple message queue. This is split
* into two layers. The core functionality is implemented as a shared library.
* This allows message queues to be used for inter-thread communication without
* requiring cross-compartment calls except in the blocking cases (reading from
* an empty queue, writing to a full queue).
*
* These library interfaces are then wrapped in a compartment, which provides
* sealed capabilities that authorise sending or receiving messages via a
* queue. The compartment interface can be used between mutually distrusting
* compartments. Neither endpoint can corrupt the queue state, though there is
* of course no guarantee that the sender will send valid data.
*
* Both sets of queues support multiple senders and multiple receivers. This
* does *not* guarantee priority propagation and so a high-priority thread
* sending a message may be starved by a low-priority thread that attempts to
* send a message over the same queue and is preempted by a medium-priority
* thread.
*/
#pragma once
#include "cdefs.h"
#include <multiwaiter.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <timeout.h>
/**
* A handle to a queue endpoint.
*
* Dropping permissions can make this a receive-only or a send-only handle.
*/
struct QueueHandle
{
/**
* The size of one element in this queue.
*/
size_t elementSize;
/**
* The size of the queue.
*/
size_t queueSize;
/**
* The buffer used for storing queue elements.
*/
void *buffer;
/**
* The producer counter.
*/
_Atomic(uint32_t) *producer;
/**
* The consumer counter.
*/
_Atomic(uint32_t) *consumer;
};
__BEGIN_DECLS
/**
* Allocates space for a queue using `heapCapability` and stores a handle to it
* via `outQueue`. The underlying allocation (which is necessary to free the
* queue) is returned via `outAllocation`.
*
* The queue is has space for `elementCount` entries. Each entry is a fixed
* size, `elementSize` bytes.
*/
int __cheri_libcall queue_create(Timeout *timeout,
struct SObjStruct *heapCapability,
struct QueueHandle *outQueue,
void **outAllocation,
size_t elementSize,
size_t elementCount);
/**
* Destroys a queue. This wakes up all threads waiting to produce or consume,
* and makes them fail to acquire the lock, before deallocating the underlying
* allocation.
*
* This must be called on an unrestricted queue handle (*not* one returned by
* `queue_make_receive_handle` or `queue_make_send_handle`).
*
* Returns 0 on success. On failure, returns `-EPERM` if the queue handle is
* restricted (see comment above).
*/
int __cheri_libcall queue_destroy(struct SObjStruct *heapCapability,
struct QueueHandle *handle);
/**
* Convert a queue handle returned from `queue_create` into one that can be
* used *only* for receiving.
*
* Note: This is primarily defence in depth. A malicious holder of this queue
* handle can still set the consumer counter to invalid values.
*/
struct QueueHandle __cheri_libcall
queue_make_receive_handle(struct QueueHandle handle);
/**
* Convert a queue handle returned from `queue_create` into one that can be
* used *only* for sending.
*
* Note: This is primarily defence in depth. A malicious holder of this queue
* handle can still set the producer counter to invalid values and overwrite
* arbitrary queue locations.
*/
struct QueueHandle __cheri_libcall
queue_make_send_handle(struct QueueHandle handle);
/**
* Send a message to the queue specified by `handle`. This expects to be able
* to copy the number of bytes specified by `elementSize` when the queue was
* created from `src`.
*
* Returns 0 on success. On failure, returns `-ETIMEOUT` if the timeout was
* exhausted, `-EINVAL` on invalid arguments.
*
* This expected to be called with a valid queue handle. It does not validate
* that this is correct. It uses `safe_memcpy` and so will check the buffer.
*/
int __cheri_libcall queue_send(Timeout *timeout,
struct QueueHandle *handle,
const void *src);
/**
* Receive a message over a queue specified by `handle`. This expects to be
* able to copy the number of bytes specified by `elementSize`. The message is
* copied to `dst`, which must have sufficient permissions and space to hold
* the message.
*
* Returns 0 on success, `-ETIMEOUT` if the timeout was exhausted, `-EINVAL` on
* invalid arguments.
*/
int __cheri_libcall queue_receive(Timeout *timeout,
struct QueueHandle *handle,
void *dst);
/**
* Returns the number of items in the queue specified by `handle` via `items`.
*
* Returns 0 on success. This has no failure mechanisms, but is intended to
* have the same interface as the version that operates on a sealed queue
* handle.
*
* Note: This interface is inherently racy. The number of items in the queue
* may change in between the return of this function and the caller acting on
* the result.
*/
int __cheri_libcall queue_items_remaining(struct QueueHandle *handle,
size_t *items);
/**
* Allocate a new message queue that is managed by the message queue
* compartment. This is returned as two sealed pointers to send and receive
* ends of the queue.
*/
int __cheri_compartment("message_queue")
queue_create_sealed(Timeout *timeout,
struct SObjStruct *heapCapability,
struct SObjStruct **outQueueSend,
struct SObjStruct **outQueReceive,
size_t elementSize,
size_t elementCount);
/**
* Destroy a queue using a sealed queue endpoint handle. The queue is not
* actually freed until *both* endpoints are destroyed, which means that you
* can safely call this from the sending end without the receiving end losing
* access to messages held in the queue.
*/
int __cheri_compartment("message_queue")
queue_destroy_sealed(Timeout *timeout,
struct SObjStruct *heapCapability,
struct SObjStruct *queueHandle);
/**
* Send a message via a sealed queue endpoint. This behaves in the same way as
* `queue_send`, except that it will return `-EINVAL` if the endpoint is not a
* valid sending endpoint and may return `-ECOMPARTMENTFAIL` if the queue is
* destroyed during the call.
*/
int __cheri_compartment("message_queue")
queue_send_sealed(Timeout *timeout,
struct SObjStruct *handle,
const void *src);
/**
* Receive a message via a sealed queue endpoint. This behaves in the same way
* as `queue_receive`, except that it will return `-EINVAL` if the endpoint is
* not a valid receiving endpoint and may return `-ECOMPARTMENTFAIL` if the
* queue is destroyed during the call.
*/
int __cheri_compartment("message_queue")
queue_receive_sealed(Timeout *timeout, struct SObjStruct *handle, void *dst);
/**
* Returns, via `items`, the number of items in the queue specified by `handle`.
* Returns 0 on success.
*
* This call is intended to be fast and so does minimal checking of arguments.
* It does not mutate state or acquire any locks and so may return
* `-ECOMPARTMENTFAIL` for any failure case.
*/
int __cheri_compartment("message_queue")
queue_items_remaining_sealed(struct SObjStruct *handle, size_t *items);
/**
* Initialise an event waiter source so that it will wait for the queue to be
* ready to receive. Note that this is inherently racy because another consumer
* may drain the queue before this consumer wakes up.
*/
void __cheri_libcall
multiwaiter_queue_receive_init(struct EventWaiterSource *source,
struct QueueHandle *handle);
/**
* Initialise an event waiter source so that it will wait for the queue to be
* ready to send. Note that this is inherently racy because another producer
* may fill the queue before this consumer wakes up.
*/
void __cheri_libcall
multiwaiter_queue_send_init(struct EventWaiterSource *source,
struct QueueHandle *handle);
/**
* Initialise an event waiter source as in `multiwaiter_queue_receive_init`,
* using a sealed queue endpoint. The `source` argument must be a receive
* endpoint.
*
* Returns 0 on success, `-EINVAL` on invalid arguments. May return
* `-ECOMPARTMENTFAIL` if the queue is deallocated in the middle of this call.
*/
int __cheri_compartment("message_queue")
multiwaiter_queue_receive_init_sealed(struct EventWaiterSource *source,
struct SObjStruct *handle);
/**
* Initialise an event waiter source as in `multiwaiter_queue_send_init`,
* using a sealed queue endpoint. The `source` argument must be a send
* endpoint.
*
* Returns 0 on success, `-EINVAL` on invalid arguments. May return
* `-ECOMPARTMENTFAIL` if the queue is deallocated in the middle of this call.
*/
int __cheri_compartment("message_queue")
multiwaiter_queue_send_init_sealed(struct EventWaiterSource *source,
struct SObjStruct *handle);
__END_DECLS