| #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. |
| 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; |
| } |