CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
stdlib.h
1// Copyright Microsoft and CHERIoT Contributors.
2// SPDX-License-Identifier: MIT
3
4#pragma once
5
6#include <__cheri_sealed.h>
7#include <cdefs.h>
9#include <riscvreg.h>
10#include <stddef.h>
11#include <stdint.h>
12#include <timeout.h>
13
14/**
15 * The SDK allocator implementation works in terms of "chunks" and has minimum
16 * size requirements for these. This is occasionally visible to its clients,
17 * as documented on interface functions below.
18 */
19static const size_t CHERIOTHeapMinChunkSize = 16;
20
21/**
22 * `MALLOC_QUOTA` sets the quota for the current compartment for use with
23 * malloc and free. This defaults to 4 KiB.
24 */
25#ifndef MALLOC_QUOTA
26# define MALLOC_QUOTA 4096
27#endif
28
29/**
30 * The public view of state represented by a capability that is used to
31 * authorise heap allocations. This should be used only when creating a new
32 * heap.
33 */
35{
36 /// The number of bytes that the capability will permit to be allocated.
37 size_t quota;
38 /// Reserved space for internal use.
39 size_t unused;
40 /// Reserved space for internal use.
41 uintptr_t reserved[2];
42};
43
44/**
45 * Type for allocator capabilities.
46 */
47typedef CHERI_SEALED(struct AllocatorCapabilityState *) AllocatorCapability;
48
49/**
50 * Helper macro to forward declare an allocator capability.
51 */
52#define DECLARE_ALLOCATOR_CAPABILITY(name) \
53 DECLARE_STATIC_SEALED_VALUE( \
54 struct AllocatorCapabilityState, allocator, MallocKey, name);
55
56/**
57 * Helper macro to define an allocator capability authorising the specified
58 * quota.
59 */
60#define DEFINE_ALLOCATOR_CAPABILITY(name, quota) \
61 DEFINE_STATIC_SEALED_VALUE(struct AllocatorCapabilityState, \
62 allocator, \
63 MallocKey, \
64 name, \
65 (quota), \
66 0, \
67 {0, 0});
68
69/**
70 * Helper macro to define an allocator capability without a separate
71 * declaration.
72 */
73#define DECLARE_AND_DEFINE_ALLOCATOR_CAPABILITY(name, quota) \
74 DECLARE_ALLOCATOR_CAPABILITY(name); \
75 DEFINE_ALLOCATOR_CAPABILITY(name, quota)
76
77#ifndef CHERIOT_NO_AMBIENT_MALLOC
78/**
79 * Declare a default capability for use with malloc-style APIs. Compartments
80 * that are not permitted to allocate memory (or which wish to use explicit
81 * heaps) can define `CHERIOT_NO_AMBIENT_MALLOC` to disable this.
82 */
83DECLARE_ALLOCATOR_CAPABILITY(__default_malloc_capability)
84# ifndef CHERIOT_CUSTOM_DEFAULT_MALLOC_CAPABILITY
85/**
86 * Define the capability for use with malloc-style APIs. In C code, this may be
87 * defined in precisely on compilation unit per compartment. Others should
88 * define `CHERIOT_CUSTOM_DEFAULT_MALLOC_CAPABILITY` to avoid the definition.
89 */
90DEFINE_ALLOCATOR_CAPABILITY(__default_malloc_capability, MALLOC_QUOTA)
91# endif
92
93/**
94 * Helper macro to look up the default malloc capability.
95 */
96# define MALLOC_CAPABILITY STATIC_SEALED_VALUE(__default_malloc_capability)
97#endif
98
99#ifndef MALLOC_WAIT_TICKS
100/**
101 * Define how long a call to `malloc` and `calloc` can block to fulfil an
102 * allocation. Regardless of this value, `malloc` and `calloc` will only ever
103 * block to wait for the quarantine to be processed. This means that, even with
104 * a non-zero value of `MALLOC_WAIT_TICKS`, `malloc` would immediately return
105 * if the heap or the quota is exhausted.
106 */
107# define MALLOC_WAIT_TICKS 30
108#endif
109
110__BEGIN_DECLS
111static inline void __dead2 panic()
112{
113 // Invalid instruction is guaranteed to trap.
114 while (1)
115 {
116 __asm volatile("unimp");
117 }
118}
119
120enum [[clang::flag_enum]] AllocateWaitFlags
121{
122 /**
123 * Non-blocking mode. This is equivalent to passing a timeout with no time
124 * remaining.
125 */
126 AllocateWaitNone = 0,
127 /**
128 * If there is enough memory in the quarantine to fulfil the allocation,
129 * wait for the revoker to free objects from the quarantine.
130 */
131 AllocateWaitRevocationNeeded = (1 << 0),
132 /**
133 * If the quota of the passed heap capability is exceeded, wait for other
134 * threads to free allocations.
135 */
136 AllocateWaitQuotaExceeded = (1 << 1),
137 /**
138 * If the heap memory is exhausted, wait for any other thread of the system
139 * to free allocations.
140 */
141 AllocateWaitHeapFull = (1 << 2),
142 /**
143 * Block on any of the above reasons. This is the default behavior.
144 */
145 AllocateWaitAny = (AllocateWaitRevocationNeeded |
146 AllocateWaitQuotaExceeded | AllocateWaitHeapFull),
147};
148
149/**
150 * Non-standard allocation API. Allocates `size` bytes.
151 *
152 * The `heapCapability` quota object must have remaining capacity sufficient
153 * for the requested `size` as well as any padding required by the CHERIoT
154 * capability encoding (see its ISA document for details) and any additional
155 * space required by the allocator's internal layout, which may be up to
156 * `CHERIOTHeapMinChunkSize` bytes. Not all of these padding bytes may be
157 * available for use via the returned capability.
158 *
159 * Blocking behaviour is controlled by the `flags` and the `timeout` parameters.
160 * Specifically, the `flags` parameter defines on which conditions to wait, and
161 * the `timeout` parameter how long to wait.
162 *
163 * Returns `-EINVAL` if the provided timeout is invalid, and `-EPERM` if the
164 * heap capability does not have permission to perform this allocation.
165 *
166 * The non-blocking mode (`AllocateWaitNone`, or `timeout` with no time
167 * remaining) will return a successful allocation if one can be created
168 * immediately, `-ENOMEM` for heap full or out-of-quota, `-EAGAIN` if
169 * there is enough memory to satisfy the allocation in quaratine, indicating
170 * that the operation could succeed if retried, or `-EINVAL` if the
171 * allocation cannot succeed under any circumstances.
172 *
173 * The blocking modes may return error codes if:
174 * - `-EAGAIN`: Revocation needed and flags specify no wait on this condition.
175 * - `-ENOMEM`: The heap is full or provided quota is exceeded, and flags
176 * specify no wait on this condition
177 * - `-ETIMEDOUT`: The timeout has expired while waiting to acquire the
178 * allocator lock or while retrying the allocation.
179 * - `-EINVAL`: The allocation cannot be satisfied under any circumstances,
180 * for example if `size` is larger than the total heap size.
181 *
182 * In both blocking and non-blocking cases, `-ENOTENOUGHSTACK` may be returned
183 * if the stack is insufficiently large to safely run the function.
184 *
185 * The return value of `heap_allocate` should be checked for the validity
186 * of the tag bit *and not* simply compared against `nullptr`.
187 *
188 * Memory returned from this interface is guaranteed to be zeroed.
189 */
190void *__cheri_compartment("allocator")
191 heap_allocate(TimeoutArgument timeout,
192 AllocatorCapability heapCapability,
193 size_t size,
194 uint32_t flags __if_cxx(= AllocateWaitAny));
195
196/**
197 * Non-standard allocation API. Allocates `size` * `nmemb` bytes of memory,
198 * checking for arithmetic overflow. Similarly to `heap_allocate`, blocking
199 * behaviour is controlled by the `flags` and the `timeout` parameters.
200 *
201 * See `heap_allocate` for more information on the padding and blocking
202 * behavior. One difference between this and `heap_allocate` is the definition
203 * of when the allocation cannot be satisfied under any circumstances, which is
204 * here if `nmemb` * `size` is larger than the total heap size, or if `nmemb` *
205 * `size` overflows.
206 *
207 * Returns `-EINVAL` on such an overflow, or when the provided timeout pointer
208 * is invalid. Returns `-EPERM` if `heapCapability` does not have permission to
209 * perform this allocation.
210 *
211 * Similarly to `heap_allocate`, `-ENOTENOUGHSTACK` may be returned if the
212 * stack is insufficiently large to run the function. See `heap_allocate` for
213 * other potential return values.
214 *
215 * Memory returned from this interface is guaranteed to be zeroed.
216 */
217void *__cheri_compartment("allocator")
218 heap_allocate_array(TimeoutArgument timeout,
219 AllocatorCapability heapCapability,
220 size_t nmemb,
221 size_t size,
222 uint32_t flags __if_cxx(= AllocateWaitAny));
223
224/**
225 * Free a heap allocation or release a claim made by `heap_claim`.
226 *
227 * To free an allocation `ptr` must be a capability to the entire allocation
228 * with the same bounds as returned by `heap_allocate`. Claims, however, may be
229 * released using a capability with reduced bounds, just as we allow
230 * `heap_claim` with such a capability. Note that we actually free / release the
231 * allocation associated with the *base* of `ptr`, not the address. This is
232 * because the base is guaranteed to be within the bounds of the original
233 * allocation due to capability monotonicity, unlike the address (which is
234 * ignored)[1].
235 *
236 * Returns:
237 * - 0 on success
238 * - `-EINVAL` if `heapCapability` is invalid or if `ptr` is untagged, is
239 * sealed, or is not a heap pointer.
240 * - `-EPERM` if `ptr` is not owned or claimed by `heapCapability`, or if the
241 * bounds do not correspond to the whole allocation and there is no
242 * outstanding claim to release.
243 * - `-ENOTENOUGHSTACK` if the stack size is insufficiently large to safely run
244 * the function.
245 *
246 * [1]: Technically it is possible to derive a tagged, zero-length capability
247 * with the base equal to the top of an allocation (i.e. just past the end),
248 * but such capabilities are liable to have their tag bit cleared if stored
249 * to memory due to the allocator always setting the revocation bit on the
250 * header of the next allocation. Attempting to free such a pointer will
251 * result in -EINVAL.
252 */
253int __cheri_compartment("allocator")
254 heap_free(AllocatorCapability heapCapability, void *ptr);
255
256/**
257 * Returns true if `object` points to a valid heap address, false otherwise.
258 * Note that this does *not* check that this is a valid pointer. This should
259 * be used in conjunction with `check_pointer` to check validity. The
260 * principle use of this function is checking whether an object needs to be
261 * claimed. If this returns false but the pointer has global permission, it
262 * must be a global and so does not need to be claimed. If the pointer lacks
263 * global permission then it cannot be claimed, but if this function returns
264 * false then it is guaranteed not to go away for the duration of the call.
265 */
266__if_c(static) inline _Bool heap_address_is_valid(const void *object)
267{
268 ptraddr_t heap_start = LA_ABS(__export_mem_heap);
269 ptraddr_t heap_end = LA_ABS(__export_mem_heap_end);
270 // The heap allocator has the only capability to the heap region. Any
271 // capability is either (transitively) derived from the heap capability or
272 // derived from something else and so it is sufficient to check that the
273 // base is within the range of the heap. Anything derived from a non-heap
274 // capability must have a base outside of that range.
275 ptraddr_t address = __builtin_cheri_base_get(object);
276 return (address >= heap_start) && (address < heap_end);
277}
278
279static inline void __dead2 abort()
280{
281 panic();
282}
283
284#ifndef CHERIOT_NO_AMBIENT_MALLOC
285static inline void *malloc(size_t size)
286{
287 Timeout t = {0, MALLOC_WAIT_TICKS};
288 void *ptr =
289 heap_allocate(&t, MALLOC_CAPABILITY, size, AllocateWaitRevocationNeeded);
290 if (!__builtin_cheri_tag_get(ptr))
291 {
292 ptr = NULL;
293 }
294 return ptr;
295}
296static inline void *calloc(size_t nmemb, size_t size)
297{
298 Timeout t = {0, MALLOC_WAIT_TICKS};
299 void *ptr = heap_allocate_array(
300 &t, MALLOC_CAPABILITY, nmemb, size, AllocateWaitRevocationNeeded);
301 if (!__builtin_cheri_tag_get(ptr))
302 {
303 ptr = NULL;
304 }
305 return ptr;
306}
307static inline int free(void *ptr)
308{
309 return heap_free(MALLOC_CAPABILITY, ptr);
310}
311#endif
312
313static inline void yield(void)
314{
315 __asm volatile("ecall" ::: "memory");
316}
317
318/**
319 * Convert an ASCII string into a signed long.
320 *
321 * This function, and indeed the CHERIoT-RTOS in general, has no notion of
322 * locale.
323 *
324 * While prototyped here, it is available as part of a dedicated 'strtol'
325 * library, which may be omitted from firmware builds if the implementation is
326 * not required.
327 */
328long __cheri_libcall strtol(const char *nptr, char **endptr, int base);
329
330/**
331 * Convert an ASCII string into an unsigned long.
332 *
333 * This function, and indeed the CHERIoT-RTOS in general, has no notion of
334 * locale.
335 *
336 * While prototyped here, it is available as part of a dedicated 'strtol'
337 * library, which may be omitted from firmware builds if the implementation is
338 * not required.
339 */
340unsigned long __cheri_libcall strtoul(const char *nptr,
341 char **endptr,
342 int base);
343
344__END_DECLS
Macros for interacting with the compartmentalisation model in CHERIoT.
The public view of state represented by a capability that is used to authorise heap allocations.
Definition stdlib.h:35
uintptr_t reserved[2]
Reserved space for internal use.
Definition stdlib.h:41
size_t unused
Reserved space for internal use.
Definition stdlib.h:39
size_t quota
The number of bytes that the capability will permit to be allocated.
Definition stdlib.h:37
Structure representing a timeout.
Definition timeout.h:47
This file contains the types used for timeouts across scheduler APIs.