-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Compare
cheri_cap_build
& cheri_address_set
Adds an example comparing `cheri_cap_build` and `cheri_address_set`. `cheri_cap_build` and `cheri_address_set` are very similar, but not quite the same. We were previously unsure of what the difference between the two was; this example uses both and explains the differences between the two. Briefly put, `cheri_address_set` takes two capabilities as arguments and returns a new capability with the metadata of the first arg and the address of the second. `cheri_cap_build` takes a capability and a `uintcap_t` — some bits to interpret as a capability, but not actually a capability — and returns a new capability constructed from the bits of the second (`uintcap_t`) argument, which is valid if and only if it is no more permissive than the first (cap) argument. There's a little more detail, but that's a summary. For more, there are a couple of useful comments on github: - #92 (comment) - #39 (comment)
- Loading branch information
1 parent
d3d04b0
commit b87ebee
Showing
8 changed files
with
446 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// We've found little documentation of `cheri_cap_build`, and on the surface, | ||
// it seems similar to `cheri_address_set`. These functions do similar things, | ||
// but they're different. This example tries to demonstrate what those | ||
// differences are. Briefly summarised…: | ||
// | ||
// - `cheri_address_set()` takes a cap and a ptr, and returns a new cap with | ||
// the address set to the ptr passed in, derived from the cap passed in. | ||
// It's basically taking an address and popping it into the appropriate | ||
// place in an existing capability to create a new one. | ||
// - `cheri_cap_build()` takes a cap and a uintcap_t (a uintptr_t equivalent | ||
// for capabilities) and returns a new cap with the bits of the uintcap_t, | ||
// which is valid if and only if it would be no more permissive than the cap | ||
// passed as the first argument. This is taking all of the data in the | ||
// `uintcap_t` and creating a cap from it, rather than deriving any of it | ||
// from the first argument --- which differentiates `cheri_address_set` and | ||
// `cheri_cap_build`. | ||
|
||
#include "cheriintrin.h" | ||
#include "include/common.h" | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
int main() | ||
{ | ||
// We create two capabilities: x, and y, which is derived from x. We note | ||
// that, though we use `cheri_address_set()`, it doesn't actually set (i.e. | ||
// _change_) the address of its arguments, but construct a new capability | ||
// with the address we pass. | ||
// | ||
// cheri_address_set(void* __capability, uintptr_t) -> void *__capability | ||
void *__capability x = cheri_ddc_get(); | ||
uintptr_t y_ptr = (uintptr_t) malloc(20); | ||
void *__capability y = cheri_address_set(x, y_ptr); | ||
|
||
// The second capability printed below is created from the first using | ||
// `cheri_address_set()`. Notice that `cheri_address_set()` created a new | ||
// capability rather than setting the address on the first. | ||
pp_cap(x); | ||
pp_cap(y); | ||
printf("\n\n"); | ||
|
||
// We create an (invalid!) capability by writing it directly. | ||
void *my_cap_mem = malloc(16); | ||
long cap; | ||
cap = 0xdc5f400000030005; | ||
memcpy(my_cap_mem + 8, &cap, 8); | ||
cap = 0x00000000deadbeef; | ||
memcpy(my_cap_mem + 0, &cap, 8); | ||
void *__capability my_cap = (void *__capability) *(void *__capability *) my_cap_mem; | ||
|
||
// We build a new capability using `cheri_cap_build()`. Note that this | ||
// capability is _valid_, because it's derived from `x`, which is valid. | ||
// The address comes from `my_cap`, which isn't valid, but we can still | ||
// create a valid capability beecause `cheri_cap_build` just reads it's | ||
// address bits and constructs a capability. | ||
|
||
// To date we haven't found the specific type signature for this, but at | ||
// https://www.morello-project.org/resources/morello-linux-morelloie/ | ||
// we can see: | ||
// void *__builtin_cheri_cap_build(const void *key, unsigned __intcap bits); | ||
// After experimenting, we've found it wants to be treated as…: | ||
// cheri_cap_build(void *__capability, uintcap_t) -> void *__capability | ||
void *__capability valid_cap = cheri_cap_build(x, (uintcap_t) my_cap); | ||
|
||
// The first capability printed below is created by writing its structure | ||
// into memory; it's not valid, it's just a bunch of bits. The second is an | ||
// equivalent capability, which is constructed using `cheri_cap_build(). | ||
// Notice that the second one is valid; that's because it's derived using a | ||
// valid capability (the variable `x`, which contains a copy of the DDC). | ||
pp_cap(my_cap); | ||
pp_cap(valid_cap); | ||
printf("\n\n"); | ||
|
||
|
||
// It might appear that `cheri_cap_build` could be used to create | ||
// capabilities with elevated permissions, by writing specific permission | ||
// bits that we want to elevate before recreating a capability. That's not | ||
// possible. To demonstrate, the rest of this example uses | ||
// `cheri_cap_build` to rebuild capabilities from raw bits stored in the | ||
// heap. | ||
|
||
// Write a copy of the DDC to heap, so we can play with it later | ||
char *ddc_copy_space = malloc(16); | ||
void *__capability ddc_copy_val = cheri_ddc_get(); | ||
void **ddc_copy_raw_ptr = (void **) &ddc_copy_val; | ||
memcpy(ddc_copy_space, ddc_copy_raw_ptr, 16); | ||
|
||
// Switch DDC, removing the mutable load permission, so we can attempt to | ||
// restore permissions we _shouldn't_ have later on. | ||
void *__capability newDDC = cheri_perms_and(cheri_ddc_get(), ~ARM_CAP_PERMISSION_MUTABLE_LOAD); | ||
asm("MSR DDC, %[cap]\n\t" : : [cap] "C"(newDDC) : "memory"); | ||
|
||
// Create a capability and copy it into heap; use this to construct two new | ||
// capabilities, one unmodified and one with restored permissions, both | ||
// using cheri_cap_build We remove some permissions from the DDC and try to | ||
// construct a capability that has them again. | ||
int val = 5; | ||
char *modspace = malloc(16); | ||
void *__capability val_cap = (void *__capability) &val; | ||
void **cap_bytes_raw_ptr = (void **) &val_cap; // a pointer to the cap's bytes | ||
memcpy(modspace, cap_bytes_raw_ptr, 16); | ||
|
||
// Re-read the capability from heap and attempt to build it again. | ||
// This works — so long as cheri_cap_build's first argument is a capability | ||
// as permissive as the second argument, we can create a valid capability | ||
// using the bits of the second argument. | ||
// (a `uintcap_t` is the length of a capability) | ||
void *__capability rebuilt_as_stored = cheri_cap_build( | ||
cheri_ddc_get(), (uintcap_t) (void *__capability) *(void *__capability *) modspace); | ||
pp_cap(rebuilt_as_stored); | ||
|
||
// Try to reconsitute the capability in memory with elevated permissions. | ||
// First, restore all permissions to cap in heap using old DDC | ||
modspace[14] = ddc_copy_space[14]; | ||
modspace[15] = ddc_copy_space[15]; | ||
|
||
// Then use the copy of the DDC in heap to try to reconstruct a capability | ||
// pointing to `val` with the additional permissions restored. We can't: | ||
// when casting `ddc_copy_space` back to a capability, we create a | ||
// capability using our currently installed DDC, which has been altered. | ||
// That means the first argument doesn't have the permission we're trying | ||
// to restore, so the second argument is more permissive, and we receive an | ||
// invalid capability. | ||
void *__capability rebuilt_constructed_from_ddc_in_heap = | ||
cheri_cap_build((void *__capability) *(void *__capability *) ddc_copy_space, | ||
(uintcap_t) (void *__capability) *(void *__capability *) modspace); | ||
pp_cap(rebuilt_constructed_from_ddc_in_heap); | ||
printf("\n"); | ||
|
||
// To demonstrate the difference between cheri_cap_build and | ||
// cheri_address_set, here we remove all permissions on our heap-stored | ||
// capability and restore it using `cheri_cap_build` and the DDC. Notice | ||
// that the DDC has many permissions, but the capability we create still | ||
// has none, because its value is determined by the second argument; _only | ||
// its validity bit_ is determined by a comparison with the first. | ||
// Afterwards, we recreate it using `cheri_address_set`, which takes | ||
// metadata such as permissions from the first argument. | ||
modspace[14] = 0x00; | ||
modspace[15] = 0x00; | ||
void *__capability rebuilt_with_no_perms = cheri_cap_build( | ||
cheri_ddc_get(), (uintcap_t) (void *__capability) *(void *__capability *) modspace); | ||
pp_cap(rebuilt_with_no_perms); | ||
|
||
void *__capability rebuilt_using_set_addr = cheri_address_set( | ||
cheri_ddc_get(), (uintcap_t) (void *__capability) *(void *__capability *) modspace); | ||
pp_cap(rebuilt_using_set_addr); | ||
|
||
free(ddc_copy_space); | ||
free(modspace); | ||
free((void *) y_ptr); | ||
free(my_cap_mem); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors. | ||
# SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
include ../../build/Makefile.vars.morello-hybrid | ||
include ../../build/Makefile.vars.common | ||
include ../../build/Makefile.simple |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# Compartmentalising Allocator | ||
|
||
A modification of the "bump allocator" in this repo. This allocator creates | ||
compartments on the heap, and returns capabilities to allocations within the | ||
compartment. | ||
|
||
Because allocations are made against a compartment's (previously `mmap`'d) | ||
buffer, we need to identify the compartment being used when we call `malloc`. | ||
`init_compartment` returns a capability used to identify the compartment it | ||
creates; calls to `malloc_compartment` accept this identifier as a parameter. | ||
|
||
`main.c` shows a simple use, with allocations against two different | ||
compartments. | ||
|
||
Example is run using `make` in the typical fashion for the repo (i.e. along the | ||
lines of `make -f Makefile.morello-hybrid run-main`). | ||
|
||
## What are "compartments" here? | ||
|
||
A "compartment" as created by this allocator is different conceptually to the | ||
compartments created by the other examples in this repo. It's worth explaining | ||
what "compartment" means in this context. | ||
|
||
Here, the "compartment" is some heap-allocated space that only capabilities are | ||
used to access. There are obviously ways to abuse "just some space on the heap" | ||
if the DDC isn't also restricted. The easiest way to explain this is to draw it | ||
out. | ||
|
||
Assuming only the DDC is being used for compartmentalisation (so we're | ||
constraining, say, dereferencing of addresses for data but not jump points etc) | ||
and it's being used to bound which addresses can be legally dereferenced, we | ||
could draw memory out as a "number line" of the address space and mark points | ||
where a compartment's bounds begin and end. So we could diagram compartments | ||
like so: | ||
|
||
``` | ||
|--------------------| | ||
0 g f F G ♾️ | ||
``` | ||
|
||
…where a compartment's lower bound is a lowercase letter and its upper bound | ||
the corresponding upper case letter (here ♾️ is the maximum address, and the `G` | ||
compartment's lower bound is marked by `g`, and the upper case marked by `G`; | ||
there's also a compartment, `F`, which is more restricted than `G` and is a | ||
strict subset of `G`). Clearly, this just defines compartment bounds relative | ||
to each other, and not in any kind of detail, but all we're interested in is | ||
the relative positioning of compartment boundaries here. | ||
|
||
The compartments this example constructs are intended to sit _outwith_ all | ||
other compartments, so that any access performed against them _must_ be done | ||
through the capabilities returned by `malloc_compartment`. For this to work, | ||
the DDC needs to be restricted when a program first runs, so that no future | ||
compartment's bounds will overlap with the ones we construct on the heap. Then, | ||
the ordinary compartments we create (derived from the now-restricted initial | ||
DDC) can't overlap with these separated heap-y compartments. | ||
|
||
I've taken to thinking of them as _"side compartments"_ to differentiate them | ||
from ordinary compartments. | ||
|
||
To illustrate what I mean, we can create a special space adjacent to our | ||
ordinary compartments and allocations like this: | ||
|
||
``` | ||
|---------------------| | ||
0 >< ♾️ | ||
``` | ||
|
||
…where `>` marks the upper bound of a DDC we install right when a program | ||
launches, and `<` marks the lower bound of possible "side" compartments. To | ||
extend the first diagram, we might make a side compartment that looks like…: | ||
|
||
``` | ||
|------------------------------| | ||
0 g f F G >< p P ♾️ | ||
``` | ||
|
||
…and the "side" compartment `P` is what `compartment` means in this instance. | ||
Those compartments are similar to the buffers created in the `bump-allocator` | ||
example in their structure. | ||
|
||
The intended use of these compartments is to create an isolated area of memory | ||
which is only accessible via capabilities, so that a process can't just | ||
calculate an address within the isolated area and dereference it (because the | ||
limited DDC prevents this). |
128 changes: 128 additions & 0 deletions
128
example_allocators/compartment_alloc/compartment_alloc.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
#include "compartment_alloc.h" | ||
#include "utils.h" | ||
#include <cheriintrin.h> | ||
#include <errno.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <sys/mman.h> | ||
|
||
heap_compartment compartments[maxCompartments]; | ||
int numCompartments = 0; | ||
|
||
/* | ||
* Initialise a compartment with `size_in_bytes` space, where all future | ||
* allocations will be constrained by `dc` as if it was the DDC when allocation | ||
* occurred. | ||
*/ | ||
void *__capability init_compartment(size_t size_in_bytes, void *__capability dc) | ||
{ | ||
void *buf; | ||
void *__capability compartment_id; | ||
|
||
if (numCompartments + 1 == maxCompartments) | ||
{ | ||
perror("Too many compartments requested."); | ||
exit(1); | ||
} | ||
|
||
// Allocate memory for this compartment | ||
buf = mmap(NULL, size_in_bytes, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); | ||
if (buf == MAP_FAILED) | ||
{ | ||
perror("error in initial mem allocation"); | ||
exit(-1); | ||
} | ||
|
||
// Create the new compartment | ||
compartment_id = sealed_reference(buf); | ||
compartments[numCompartments] = (heap_compartment){ | ||
buf, // Space on the heap | ||
0, // bytes allocated (currently none!) | ||
size_in_bytes, // Our maximum size, to check when allocating | ||
dc, // The data capability to allocate against (like a DDC) | ||
compartment_id // An identifier which allows a holder to allocate in this component | ||
}; | ||
numCompartments++; | ||
|
||
// Return the sealed capability which acts as a token of authentication for a component. | ||
return compartment_id; | ||
} | ||
|
||
/* | ||
* Allocate `len` bytes in the compartment identified by `compartment_id`. | ||
* if `compartment_id` doesn't exist, returns NULL | ||
*/ | ||
void *__capability malloc_compartment(size_t len, void *__capability compartment_id) | ||
{ | ||
size_t rounded_length; | ||
size_t new_allocated; | ||
heap_compartment compartment = compartments[0]; | ||
void *__capability allocated; | ||
void *addr; | ||
int i; | ||
|
||
// Search for a compartment with the given identifier | ||
for (i = 0; i < maxCompartments && compartments[i].identifier != compartment_id; i++) | ||
{ | ||
} | ||
if (i == maxCompartments) | ||
{ | ||
perror("Given an ID for a non-existent compartment"); | ||
exit(1); | ||
} | ||
|
||
compartment = compartments[i]; | ||
|
||
// Try to "bump-allocate" some space in the compartment's buffer | ||
addr = compartment.buffer + compartment.bytes_allocated; | ||
|
||
addr = __builtin_align_up(addr, ~cheri_representable_alignment_mask(len) + 1); | ||
rounded_length = cheri_representable_length(len); | ||
|
||
new_allocated = (addr + rounded_length) - compartment.buffer; | ||
if (new_allocated > compartment.max_allocated) | ||
{ | ||
perror("Maximum # bytes in compartment exceeded."); | ||
exit(1); | ||
} | ||
|
||
compartment.bytes_allocated = new_allocated; | ||
|
||
// We allocated some space! | ||
// Create a capability pointing to it and return that. | ||
// The capability inherits the metadata of data capability for this compartment. | ||
allocated = cheri_address_set(compartment.datacap, (long) addr); | ||
allocated = cheri_bounds_set_exact(allocated, rounded_length); | ||
|
||
return allocated; | ||
} | ||
|
||
/* | ||
* Unmaps the compartment identified by the `compartment_id` argument, | ||
* effectively freeing it. | ||
* | ||
* Note that this is a naive implementation for demo purposes. The capabilities | ||
* previously returned by `malloc_compartment` now point to de-allocated memory. | ||
*/ | ||
void free_compartment(void *__capability compartment_id) | ||
{ | ||
int munmap_rc; | ||
int i; | ||
|
||
// Search for a compartment with the given identifier | ||
for (i = 0; i < maxCompartments && compartments[i].identifier != compartment_id; i++) | ||
{ | ||
} | ||
if (i == maxCompartments) | ||
{ | ||
perror("Given an ID for a non-existent compartment"); | ||
exit(1); | ||
} | ||
|
||
munmap_rc = munmap(compartments[i].buffer, compartments[i].max_allocated); | ||
if (munmap_rc != 0) | ||
{ | ||
perror("Attempted to deallocate a compartment, but munmap errored"); | ||
exit(1); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#define maxCompartments 5 | ||
|
||
typedef struct _heap_compartment | ||
{ | ||
void *buffer; | ||
size_t bytes_allocated; | ||
size_t max_allocated; | ||
void *__capability datacap; | ||
void *__capability identifier; // NOTE: THIS IS SEALED | ||
} heap_compartment; | ||
|
||
void *__capability init_compartment(size_t size_in_bytes, void *__capability dc); | ||
void *__capability malloc_compartment(size_t len, void *__capability component_id); | ||
void free_compartment(void *__capability compartment_id); |
Oops, something went wrong.