-
Notifications
You must be signed in to change notification settings - Fork 6
DRAFT: Start words on CHERI-enabled allocator caller and callee guarantees #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
93d759e
9f0552a
edccc24
8a02a0f
767e794
e74dc54
2b0b739
b0c7263
906356c
7e38743
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Memory allocators and CHERI C/C++ | ||
|
|
||
| This chapter considers two closely related topics: | ||
|
|
||
| * Guarantees that may be relied on by memory-allocator consumers programmed | ||
| in CHERI C/C++ | ||
| * Guidance for memory-allocator developers targeting CHERI C/C++ execution | ||
| environments | ||
|
|
||
| While the focus of this section is on class C-language APIs such as | ||
| `malloc()`, `calloc()`, `free()`, and `realloc()`, aspects of these guidelines | ||
| will also apply to many other allocators including, to varying extents, | ||
| bespoke allocators in OS kernels, language runtimes, and scalable | ||
| applications. | ||
|
|
||
| The most fundamental behaviors of current allocators are not changed with | ||
| CHERI: Allocators are responsible for returning pointers to memory storage | ||
| that is, under its invariants, stable and unique for the lifetime of the | ||
| allocation. | ||
| However, allocator implementations must be adapted to CHERI C/C++: To achieve | ||
| memory protection for its callers, it must set CHERI capability properties | ||
| (such as bounds), taking into account properties such as capability alignment | ||
| and bounds compression, as well as (if required) integrate support for | ||
| revocation. | ||
| CHERI will then ensure that memory accesses to allocations made via pointers | ||
| are safe with respect to memory-safety properties such as spatial safety, | ||
| temporal safety, and so on. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| ## Recommendation for allocator implementations | ||
|
|
||
| ### Allocating memory | ||
|
|
||
| In addition to implementing the conventational invariants ensuring the | ||
| mutually exclusive allocation of memory, CHERI-aware implementations of | ||
| `malloc()` and `calloc()` must return a capability that has the following | ||
| properties: | ||
|
|
||
| * Is valid (i.e., with its tag bit set) | ||
| * Is unsealed | ||
| * Has bounds that permit access to the full requested range of the allocation | ||
| * Has bounds that do not permit access to any other current allocation, nor | ||
| allocator metadata, implementing non-aliasing spatial safety | ||
| * Has permissions that allow data load, data store, capability load, and | ||
| capability store | ||
| * Be sufficiently aligned to allow capability loads and stores at relative | ||
| offset 0 from the returned pointer | ||
|
|
||
| The allocator should: | ||
|
|
||
| * Fill reachable memory within bounds with zeroes before returning a pointer | ||
| to it | ||
|
|
||
| The allocator may: | ||
|
|
||
| * Provide precise bounds -- i.e., in which the lower bound is the lowest | ||
| address of the returned allocation, and the upper bound is the highest | ||
| address of the returned allocation plus one | ||
|
|
||
| ### Freeing memory | ||
|
|
||
| CHERI-aware allocators must ignore, or trap on, calls to `free()` if the | ||
| passed capability: | ||
|
|
||
| * Is invalid (i.e, with its tag bit unset); or | ||
| * Is sealed; or | ||
| * Has bounds that have been changed from those returned by the allocator | ||
| when the allocation was first made; or | ||
| * Has permissions that disallow any of data load, data store, capability | ||
| load, or capability store | ||
|
|
||
| The allocator may: | ||
|
|
||
| * Implement fail-stop semantics if the call fails for one or more of the | ||
| above reasons. | ||
|
|
||
| ### Reallocating memory | ||
|
|
||
| The allocator must: | ||
|
|
||
| * Fail a call to `realloc()` if the passed capability violates any of the | ||
| properties checked for in `free()`. | ||
| * Return a capability with the same properties as those defined for | ||
| `malloc()`. | ||
|
|
||
| The allocator must not: | ||
|
|
||
| * Return a new pointer from `realloc()` that has an identical address to the | ||
| passed argument but differs in its bounds or other metadata. | ||
|
|
||
| The allocator should: | ||
|
|
||
| * Zero any newly accessible memory before returning a pointer to it, | ||
| including any allocator metadata. | ||
|
|
||
| The allocator may: | ||
|
|
||
| * Implement fail-stop semantics if the call fails for the above reasons. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| ## Guarantees to the allocator consumer | ||
|
|
||
| This section describes properties that consumers of memory allocators may | ||
| rely on from all CHERI-enabled allocators. | ||
|
|
||
| ### Allocating memory | ||
|
|
||
| Calls to `malloc()` and `calloc() must return capabilities that: | ||
|
|
||
| * Are valid (i.e., with its tag bit set) | ||
| * Are unsealed | ||
| * Have bounds that permit access to the full requested memory range of the | ||
| allocation | ||
| * Have bounds that do not permit access to any other current allocation, nor | ||
| allocator metadata, implementing non-aliasing spatial safety | ||
| * Have permissions that allow data load, data store, capability load, and | ||
| capability store | ||
| * Are sufficiently aligned to allow capability loads and stores at relative | ||
| offset 0 from the returned pointer | ||
|
|
||
| The allocator may: | ||
|
|
||
| * Fill reachable memory within bounds with zeroes before returning a pointer | ||
| to it | ||
| * Provide precise bounds, with the lower bound being the bottom address of | ||
| the allocation, and the upper bound being one byte above the top address of | ||
| the allocation | ||
|
|
||
| ### Freeing memory | ||
|
|
||
| The caller must not pass as an argument to `free()` a capability that: | ||
|
|
||
| * Is invalid (i.e., without its tag bit set) | ||
| * Is unsealed | ||
| * Has bounds other than those on the original capability returned by | ||
| `malloc()`, `calloc()`, or `realloc()` | ||
| * Has permissions that differ from those on the original capability returned | ||
| by `malloc()`, `calloc()`, or `realloc()` | ||
|
|
||
| The allocator must not: | ||
|
|
||
| * Reuse storage associated with the allocation until there are no outstanding | ||
| valid capabilities that authorize access to the memory | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit surprised to see this separately from the revocation point below?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My thought was that I wanted to avoid an assumption of revocation in the 'must' / 'must not' text, and simply state the invariants, leaving open the possibility of other techniques than revocation -- perhaps GC-related ones. I guess this is in the |
||
|
|
||
| The allocator may: | ||
|
|
||
| * Fill reachable memory within the bounds of the allocation with zeroes after | ||
| it has been freed | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, if revocation is not prompt (CHERIoT, MTE), these zeros are advisory (and the allocator should re-zero memory before returning it from malloc).
rwatson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * On virtual-memory-enabled systems, unmap reachable memory within the bounds | ||
| of the allocation after it has been freed. | ||
| * Revoke capabilities to the storage immediately upon free | ||
|
|
||
| If utilizing revocation, the allocator must: | ||
|
|
||
| * Ensure that any outstanding capabilities to the allocation become | ||
| non-dereferenceable | ||
|
|
||
| On revocation, the allocator may: | ||
|
|
||
| * Clear the tag of revoked capabilities | ||
|
|
||
| ### Reallocating memory | ||
|
|
||
| The caller must not: | ||
|
|
||
| * Pass a capability to `realloc()` that violates any of the requirements for | ||
| a call to `free()`. | ||
|
|
||
| The allocator must: | ||
|
|
||
| * Conform to the guarantees associated with calls to `malloc()` and | ||
| `calloc()` when allocating memory in `realloc()`. | ||
|
|
||
| The allocator must not: | ||
|
|
||
| * Return a new pointer from `realloc()` that has an identical address to the | ||
| passed argument but differs in its bounds or other metadata. | ||
|
|
||
| The allocator may: | ||
|
|
||
| * Zero any newly accessible memory before returning a pointer to it. | ||
| * Always reallocate, returning a new pointer, on every call to `realloc()`. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is probably safe to leave up to the allocator whether a nonzero offset between address and base is acceptable.
The CHERIoT allocator overloads
free()(well,heap_free()) to permit dropping claims on an object, and in that case it accepts interior, subset, &| attenuated pointers. That is, claims may be (taken and) dropped on subobjects (or, really, arbitrary slices of objects) but deallocation requires the original pointer (a la C).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the former: Are there use cases for non-zero offsets in heap allocators we are aware of beyond, perhaps, "deterministically trap on overflow rather than on underflow when imprecise bounds require padding"?