blob: 6955d233e30824a6bd199293d00a284fd43fb025 [file] [log] [blame]
// 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