| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| #pragma once | |
| #include <cdefs.h> | |
| #include <compartment-macros.h> | |
| #include <riscvreg.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <timeout.h> | |
| /** | |
| * `MALLOC_QUOTA` sets the quota for the current compartment for use with | |
| * malloc and free. This defaults to 4 KiB. | |
| */ | |
| #ifndef MALLOC_QUOTA | |
| # define MALLOC_QUOTA 4096 | |
| #endif | |
| /** | |
| * The public view of state represented by a capability that is used to | |
| * authorise heap allocations. This should be used only when creating a new | |
| * heap. | |
| */ | |
| struct AllocatorCapabilityState | |
| { | |
| /// The number of bytes that the capability will permit to be allocated. | |
| size_t quota; | |
| /// Reserved space for internal use. | |
| size_t unused; | |
| /// Reserved space for internal use. | |
| uintptr_t reserved[2]; | |
| }; | |
| struct SObjStruct; | |
| /** | |
| * Helper macro to forward declare an allocator capability. | |
| */ | |
| #define DECLARE_ALLOCATOR_CAPABILITY(name) \ | |
| DECLARE_STATIC_SEALED_VALUE( \ | |
| struct AllocatorCapabilityState, allocator, MallocKey, name); | |
| /** | |
| * Helper macro to define an allocator capability authorising the specified | |
| * quota. | |
| */ | |
| #define DEFINE_ALLOCATOR_CAPABILITY(name, quota) \ | |
| DEFINE_STATIC_SEALED_VALUE(struct AllocatorCapabilityState, \ | |
| alloc, \ | |
| MallocKey, \ | |
| name, \ | |
| (quota), \ | |
| 0, \ | |
| {0, 0}); | |
| /** | |
| * Helper macro to define an allocator capability without a separate | |
| * declaration. | |
| */ | |
| #define DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(name, quota) \ | |
| DECLARE_ALLOCATOR_CAPABILITY(name); \ | |
| DEFINE_ALLOCATOR_CAPABILITY(name, quota) | |
| #ifndef CHERIOT_NO_AMBIENT_MALLOC | |
| /** | |
| * Declare a default capability for use with malloc-style APIs. Compartments | |
| * that are not permitted to allocate memory (or which wish to use explicit | |
| * heaps) can define `CHERIOT_NO_AMBIENT_MALLOC` to disable this. | |
| */ | |
| DECLARE_ALLOCATOR_CAPABILITY(__default_malloc_capability) | |
| # ifndef CHERIOT_CUSTOM_DEFAULT_MALLOC_CAPABILITY | |
| /** | |
| * Define the capability for use with malloc-style APIs. In C code, this may be | |
| * defined in precisely on compilation unit per compartment. Others should | |
| * define `CHERIOT_CUSTOM_DEFAULT_MALLOC_CAPABILITY` to avoid the definition. | |
| */ | |
| DEFINE_ALLOCATOR_CAPABILITY(__default_malloc_capability, MALLOC_QUOTA) | |
| # endif | |
| #endif | |
| /** | |
| * Helper macro to look up the default malloc capability. | |
| */ | |
| #define MALLOC_CAPABILITY STATIC_SEALED_VALUE(__default_malloc_capability) | |
| #ifndef MALLOC_WAIT_TICKS | |
| /** | |
| * Define how long a call to `malloc` and `calloc` can block to fulfil an | |
| * allocation. Regardless of this value, `malloc` and `calloc` will only ever | |
| * block to wait for the quarantine to be processed. This means that, even with | |
| * a non-zero value of `MALLOC_WAIT_TICKS`, `malloc` would immediately return | |
| * if the heap or the quota is exhausted. | |
| */ | |
| # define MALLOC_WAIT_TICKS 30 | |
| #endif | |
| __BEGIN_DECLS | |
| static inline void __dead2 panic() | |
| { | |
| // Invalid instruction is guaranteed to trap. | |
| while (1) | |
| { | |
| __asm volatile("unimp"); | |
| } | |
| } | |
| enum [[clang::flag_enum]] AllocateWaitFlags{ | |
| /** | |
| * Non-blocking mode. This is equivalent to passing a timeout with no time | |
| * remaining. | |
| */ | |
| AllocateWaitNone = 0, | |
| /** | |
| * If there is enough memory in the quarantine to fulfil the allocation, wait | |
| * for the revoker to free objects from the quarantine. | |
| */ | |
| AllocateWaitRevocationNeeded = (1 << 0), | |
| /** | |
| * If the quota of the passed heap capability is exceeded, wait for other | |
| * threads to free allocations. | |
| */ | |
| AllocateWaitQuotaExceeded = (1 << 1), | |
| /** | |
| * If the heap memory is exhausted, wait for any other thread of the system | |
| * to free allocations. | |
| */ | |
| AllocateWaitHeapFull = (1 << 2), | |
| /** | |
| * Block on any of the above reasons. This is the default behavior. | |
| */ | |
| AllocateWaitAny = (AllocateWaitRevocationNeeded | AllocateWaitQuotaExceeded | | |
| AllocateWaitHeapFull), | |
| }; | |
| /** | |
| * Non-standard allocation API. Allocates `size` bytes. Blocking behaviour is | |
| * controlled by the `flags` and the `timeout` parameters. | |
| * | |
| * Specifically, the `flags` parameter defines on which conditions to wait, and | |
| * the `timeout` parameter how long to wait. | |
| * | |
| * The non-blocking mode (`AllocateWaitNone`, or `timeout` with no time | |
| * remaining) will return a successful allocation if one can be created | |
| * immediately, or `nullptr` otherwise. | |
| * | |
| * The blocking modes may return `nullptr` if the condition to wait is not | |
| * fulfiled, if the timeout has expired, or if the allocation cannot be | |
| * satisfied under any circumstances (for example if `size` is larger than the | |
| * total heap size). | |
| * | |
| * This means that calling this with `AllocateWaitAny` and `UnlimitedTimeout` | |
| * will only ever return `nullptr` if the allocation cannot be satisfied under | |
| * any circumstances. | |
| * | |
| * In both blocking and non-blocking cases, `-ENOTENOUGHSTACK` may be returned | |
| * if the stack is insufficiently large to safely run the function. This means | |
| * that the return value of `heap_allocate` should be checked for the validity | |
| * of the tag bit *and not* nullptr. | |
| * | |
| * Memory returned from this interface is guaranteed to be zeroed. | |
| */ | |
| void *__cheri_compartment("alloc") | |
| heap_allocate(Timeout *timeout, | |
| struct SObjStruct *heapCapability, | |
| size_t size, | |
| uint32_t flags __if_cxx(= AllocateWaitAny)); | |
| /** | |
| * Non-standard allocation API. Allocates `size` * `nmemb` bytes of memory, | |
| * checking for arithmetic overflow. Similarly to `heap_allocate`, blocking | |
| * behaviour is controlled by the `flags` and the `timeout` parameters. | |
| * | |
| * See `heap_allocate` for more information on the blocking behavior. One | |
| * difference between this and `heap_allocate` is the definition of when the | |
| * allocation cannot be satisfied under any circumstances, which is here if | |
| * `nmemb` * `size` is larger than the total heap size, or if `nmemb` * `size` | |
| * overflows. | |
| * | |
| * Similarly to `heap_allocate`, `-ENOTENOUGHSTACK` may be returned if the | |
| * stack is insufficiently large to run the function. See `heap_allocate`. | |
| * | |
| * Memory returned from this interface is guaranteed to be zeroed. | |
| */ | |
| void *__cheri_compartment("alloc") | |
| heap_allocate_array(Timeout *timeout, | |
| struct SObjStruct *heapCapability, | |
| size_t nmemb, | |
| size_t size, | |
| uint32_t flags __if_cxx(= AllocateWaitAny)); | |
| /** | |
| * Add a claim to an allocation. The object will be counted against the quota | |
| * provided by the first argument until a corresponding call to `heap_free`. | |
| * Note that this can be used with interior pointers. | |
| * | |
| * This will return the size of the allocation claimed on success, 0 on error | |
| * (if `heapCapability` or `pointer` is not valid, etc.), or `-ENOTENOUGHSTACK` | |
| * if the stack is insufficiently large to run the function. | |
| */ | |
| ssize_t __cheri_compartment("alloc") | |
| heap_claim(struct SObjStruct *heapCapability, void *pointer); | |
| /** | |
| * Interface to the fast claims mechanism. This claims two pointers using the | |
| * hazard-pointer-inspired lightweight claims mechanism. If this function | |
| * returns zero then the heap pointers are guaranteed not to become invalid | |
| * until either the next cross-compartment call or the next call to this | |
| * function. | |
| * | |
| * A null pointer can be used as a not-present value. This function will treat | |
| * operations on null pointers as unconditionally successful. It returns | |
| * `-ETIMEDOUT` if it failed to claim before the timeout expired, or `EINVAL` | |
| * if one or more of the arguments is neither null nor a valid pointer at the | |
| * end. | |
| * | |
| * In the case of failure, neither pointer will have been claimed. | |
| * | |
| * This function is provided by the compartment_helpers library, which must be | |
| * linked for it to be available. | |
| */ | |
| int __cheri_libcall heap_claim_fast(Timeout *timeout, | |
| const void *ptr, | |
| const void *ptr2 __if_cxx(= nullptr)); | |
| /** | |
| * Free a heap allocation. | |
| * | |
| * Returns 0 on success, `-EINVAL` if `ptr` is not a valid pointer to the start | |
| * of a live heap allocation, or `-ENOTENOUGHSTACK` if the stack size is | |
| * insufficiently large to safely run the function. | |
| */ | |
| int __cheri_compartment("alloc") | |
| heap_free(struct SObjStruct *heapCapability, void *ptr); | |
| /** | |
| * Free all allocations owned by this capability. | |
| * | |
| * Returns the number of bytes freed, `-EPERM` if this is not a valid heap | |
| * capability, or `-ENOTENOUGHSTACK` if the stack size is insufficiently large | |
| * to safely run the function. | |
| */ | |
| ssize_t __cheri_compartment("alloc") | |
| heap_free_all(struct SObjStruct *heapCapability); | |
| /** | |
| * Returns 0 if the allocation can be freed with the given capability, a | |
| * negated errno value otherwise. | |
| */ | |
| int __cheri_compartment("alloc") | |
| heap_can_free(struct SObjStruct *heapCapability, void *ptr); | |
| /** | |
| * Returns the space available in the given quota. This will return -1 if | |
| * `heapCapability` is not valid or if the stack is insufficient to run the | |
| * function. | |
| */ | |
| ssize_t __cheri_compartment("alloc") | |
| heap_quota_remaining(struct SObjStruct *heapCapability); | |
| /** | |
| * Block until the quarantine is empty. | |
| * | |
| * This should be used only in testing, to place the system in a quiesced | |
| * state. It can block indefinitely if another thread is allocating and | |
| * freeing memory while this runs. | |
| */ | |
| void __cheri_compartment("alloc") heap_quarantine_empty(void); | |
| /** | |
| * Returns true if `object` points to a valid heap address, false otherwise. | |
| * Note that this does *not* check that this is a valid pointer. This should | |
| * be used in conjunction with check_pointer to check validity. The principle | |
| * use of this function is checking whether an object needs to be claimed. If | |
| * this returns false but the pointer has global permission, it must be a | |
| * global and so does not need to be claimed. If the pointer lacks global | |
| * permission then it cannot be claimed, but if this function returns false | |
| * then it is guaranteed not to go away for the duration of the call. | |
| */ | |
| __if_c(static) inline _Bool heap_address_is_valid(const void *object) | |
| { | |
| ptraddr_t heap_start = LA_ABS(__export_mem_heap); | |
| ptraddr_t heap_end = LA_ABS(__export_mem_heap_end); | |
| // The heap allocator has the only capability to the heap region. Any | |
| // capability is either (transitively) derived from the heap capability or | |
| // derived from something else and so it is sufficient to check that the | |
| // base is within the range of the heap. Anything derived from a non-heap | |
| // capability must have a base outside of that range. | |
| ptraddr_t address = __builtin_cheri_base_get(object); | |
| return (address >= heap_start) && (address < heap_end); | |
| } | |
| static inline void __dead2 abort() | |
| { | |
| panic(); | |
| } | |
| #ifndef CHERIOT_NO_AMBIENT_MALLOC | |
| static inline void *malloc(size_t size) | |
| { | |
| Timeout t = {0, MALLOC_WAIT_TICKS}; | |
| void *ptr = | |
| heap_allocate(&t, MALLOC_CAPABILITY, size, AllocateWaitRevocationNeeded); | |
| if (!__builtin_cheri_tag_get(ptr)) | |
| { | |
| ptr = NULL; | |
| } | |
| return ptr; | |
| } | |
| static inline void *calloc(size_t nmemb, size_t size) | |
| { | |
| Timeout t = {0, MALLOC_WAIT_TICKS}; | |
| void *ptr = heap_allocate_array( | |
| &t, MALLOC_CAPABILITY, nmemb, size, AllocateWaitRevocationNeeded); | |
| if (!__builtin_cheri_tag_get(ptr)) | |
| { | |
| ptr = NULL; | |
| } | |
| return ptr; | |
| } | |
| static inline int free(void *ptr) | |
| { | |
| return heap_free(MALLOC_CAPABILITY, ptr); | |
| } | |
| #endif | |
| size_t __cheri_compartment("alloc") heap_available(void); | |
| static inline void yield(void) | |
| { | |
| __asm volatile("ecall"); | |
| } | |
| __END_DECLS |