-
Notifications
You must be signed in to change notification settings - Fork 13
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
Add example showing inter-compartment switch #53
Changes from all commits
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,152 @@ | ||
/* This example adds a second compartment, and shows a possible design to | ||
* achieve inter-compartment switching without (much) privilege escalation. In | ||
* this file, we setup compartment information within array `comps` | ||
* (`main.c:84`). This information will be used by the assembly function | ||
* `switch_compartment`. Further, we save a capability to | ||
* `switch_compartment()` in a register (`main.c:102`), to be able to call it | ||
* from a bounded-PCC context, as well as a capability defining the area of | ||
* memory with compartment information (`main.c:101`). These could be added to | ||
* the memory area of each created compartment for better security, as well as | ||
* sealed. | ||
* | ||
* Our two compartments provide one entry point each, `comp_f_fn` | ||
* (`shared.S:93`), and `comp_g_fn` (`shared.S:109`). The design is that | ||
* compartment `f` will perform a switch to compartment `g`, which performs | ||
* some observable action (in this instance, sets a specific memory location to | ||
* a specific integer value). The switching process in (almost) entirely bound | ||
* within `switch_compartment`, on the execution side, and the memory area with | ||
* compartment information, for memory access. We note almost, as the | ||
* executable area is approximated (`main.c:96`). | ||
*/ | ||
|
||
#include "../../../include/common.h" | ||
#include "../../include/utils.h" | ||
|
||
#include <assert.h> | ||
#include <stddef.h> | ||
#include <stdint.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#if !defined(__CHERI_CAPABILITY_WIDTH__) || defined(__CHERI_PURE_CAPABILITY__) | ||
#error "This example only works on CHERI hybrid mode" | ||
#endif | ||
|
||
/******************************************************************************* | ||
* Assembly Functions & Consts | ||
******************************************************************************/ | ||
|
||
extern int switch_compartment(); | ||
extern void comp_f_fn(); | ||
extern void comp_g_fn(); | ||
|
||
/******************************************************************************* | ||
* Types & Consts | ||
******************************************************************************/ | ||
|
||
const size_t comp_stack_size = 2000; | ||
const size_t total_comp_size = 5000; | ||
size_t id = 0; | ||
|
||
/* Abstract representation of a compartment. Within 80 bytes we represent: | ||
* - an id (8B) | ||
* - address to the start of the compartment's stack (8B) | ||
* the size of the stack (8B) | ||
* - address to the start of the compartment's heap (8B) | ||
* the size of the heap (8B) | ||
* - alignment padding (8B) | ||
* - the ddc corresponding to the compartment (16B) | ||
* - the function within the compartment to be executed upon switching (16B) | ||
*/ | ||
struct comp | ||
{ | ||
size_t id; | ||
void *stack_addr; | ||
size_t stack_len; | ||
void *heap_addr; | ||
size_t heap_len; | ||
void *__capability ddc; | ||
void *__capability comp_fn; | ||
}; | ||
|
||
// ASM offsets, included here for validation | ||
#include "main.h" | ||
|
||
static_assert(COMP_SIZE == sizeof(struct comp), "Invalid `COMP_SIZE` provided"); | ||
static_assert(COMP_OFFSET_STK_ADDR == offsetof(struct comp, stack_addr), | ||
"Invalid `COMP_OFFSET_STK_ADDR` provided."); | ||
static_assert(COMP_OFFSET_STK_LEN == offsetof(struct comp, stack_len), | ||
"Invalid `COMP_OFFSET_STK_LEN` provided."); | ||
static_assert(COMP_OFFSET_DDC == offsetof(struct comp, ddc), "Invalid `COMP_OFFSET_DDC` provided."); | ||
static_assert(COMP_OFFSET_PCC == offsetof(struct comp, comp_fn), | ||
"Invalid `COMP_OFFSET_PCC` provided."); | ||
|
||
struct comp comps[COMP_COUNT]; | ||
|
||
/******************************************************************************* | ||
* Privileged Functions | ||
******************************************************************************/ | ||
|
||
/* This function stores the compartment data in memory, so that it can be | ||
* accessed by the compartments as needed. | ||
*/ | ||
void executive_switch(struct comp c) | ||
{ | ||
void *__capability switch_cap = (void *__capability) switch_compartment; | ||
switch_cap = cheri_bounds_set(switch_cap, 80 * 4); | ||
|
||
void *__capability comps_addr = (void *__capability) &comps; | ||
comps_addr = cheri_bounds_set(comps_addr, COMP_COUNT * COMP_SIZE); | ||
|
||
asm("mov c19, %w0\n\t" | ||
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 think there might be a mix of tabs and spaces here? Let's use all spaces. 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. Ah, well, this will be fixed by the subsequent 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. Does it? Ye gods! In general, it's best to do 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. For ease, I've just ran one now [1]. In the future, I will run one at the point of making the PR, and the point of merging (until I hopefully am able to do the hooks thing). [1] 5ae74cf 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. Thanks! |
||
"mov c20, %w1\n\t" | ||
"mov x0, #0\n\t" | ||
"msr CID_EL0, c0" | ||
: | ||
: "r"(comps_addr), "r"(switch_cap)); | ||
|
||
switch_compartment(); | ||
} | ||
|
||
void add_comp(uint8_t *_start_addr, void (*_comp_fn)()) | ||
{ | ||
assert(id < COMP_COUNT); | ||
|
||
struct comp new_comp; | ||
new_comp.id = id; | ||
|
||
new_comp.stack_addr = (void *) _start_addr; | ||
new_comp.stack_len = comp_stack_size; | ||
new_comp.heap_addr = (void *) (_start_addr + comp_stack_size); | ||
new_comp.heap_len = total_comp_size - comp_stack_size; | ||
|
||
void *__capability comp_ddc = (void *__capability) _start_addr; | ||
comp_ddc = cheri_bounds_set(comp_ddc, total_comp_size); | ||
new_comp.ddc = comp_ddc; | ||
|
||
// Set up a capability pointing to the function we want to call within the | ||
// compartment. This will be loaded as the PCC when the function is called. | ||
void *__capability comp_fn = (void *__capability) _comp_fn; | ||
comp_fn = cheri_bounds_set(comp_fn, 40); | ||
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 guess these are also PCC bounds? 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. Yes, bounds as well. 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. Can I suggest we use 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've added a comment which helpfully clarifies things. At this point, I think the following is a rather core property of CHERI, and maybe we should make it obvious somewhere: creating a capability which has the |
||
new_comp.comp_fn = comp_fn; | ||
|
||
comps[id] = new_comp; | ||
++id; | ||
} | ||
|
||
/******************************************************************************* | ||
* Main | ||
******************************************************************************/ | ||
|
||
int main() | ||
{ | ||
uint8_t *comp_f = malloc(total_comp_size); | ||
add_comp(comp_f, comp_f_fn); | ||
uint8_t *comp_g = malloc(total_comp_size); | ||
add_comp(comp_g, comp_g_fn); | ||
|
||
executive_switch(comps[0]); | ||
|
||
// Check compartment did indeed execute | ||
assert(comp_g[4000] == 42); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#define COMP_COUNT 2 | ||
#define COMP_SIZE 80 | ||
#define COMP_OFFSET_STK_ADDR 8 | ||
#define COMP_OFFSET_STK_LEN 16 | ||
#define COMP_OFFSET_DDC 48 | ||
#define COMP_OFFSET_PCC 64 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
// Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors. | ||
// SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
||
#include "main.h" | ||
|
||
.global comp_f_fn | ||
.global comp_g_fn | ||
|
||
.text | ||
.balign 4 | ||
|
||
/* The compartment switch function. Expects compartment information to be | ||
* stored in memory (defined by the capability stored in register `c19`). | ||
* Performs a compartment switch based on the id saved in `CID_EL0` (currently | ||
* just an integer). | ||
*/ | ||
.global switch_compartment | ||
.type switch_compartment, "function" | ||
switch_compartment: | ||
|
||
// Store entering compartment's DDC, and move to memory containing | ||
// compartment info | ||
mrs c2, DDC | ||
msr DDC, c19 | ||
|
||
// Get compartment to switch to data | ||
mrs c10, CID_EL0 | ||
mov x11, #COMP_SIZE | ||
mul x10, x10, x11 | ||
|
||
// Load PCC, including function we are jumping to within compartment | ||
add x11, x10, #COMP_OFFSET_PCC | ||
ldr c0, [x19, x11] | ||
|
||
// Load DDC | ||
add x11, x10, #COMP_OFFSET_DDC | ||
ldr c1, [x19, x11] | ||
|
||
// Setup SP | ||
mov x12, sp | ||
add x11, x10, #COMP_OFFSET_STK_ADDR | ||
ldr x11, [x19, x11] | ||
add x13, x10, #COMP_OFFSET_STK_LEN | ||
ldr x13, [x19, x13] | ||
add sp, x11, x13 | ||
|
||
// Derive a new clr to restore PCC, and store it. | ||
cvtp c11, lr | ||
|
||
// Install compartment DDC | ||
msr DDC, c1 | ||
|
||
// Save old DDC (c2), old SP (x12), old LR (c11) on stack | ||
stp c2, c11, [sp, #-48]! | ||
str x12, [sp, #32] | ||
|
||
// Stack layout at this point: | ||
// | ||
// `stack + size` -> ________________________ | ||
// sp + 40 -> [ <alignment pad> ] ^ | ||
// sp + 32 -> [ old SP ] | | ||
// sp + 24 -> [ old CLR (hi64) ] | | ||
// sp + 16 -> [ old CLR (lo64) ] | | ||
// sp + 8 -> [ old DDC (high 64) ] | DDC bounds | ||
// sp + 0 -> [ old DDC (low 64) ] | | ||
// : : | ||
// `stack` -> ________________________v | ||
|
||
bl clean+4 | ||
|
||
// Jump to the function within the compartment we are switching to (this | ||
// also sets PCC) | ||
blr c0 | ||
|
||
// Clean capabilities left in the return value. | ||
mov w0, w0 | ||
bl clean | ||
|
||
// Restore the caller's context and compartment. | ||
ldp c10, clr, [sp] | ||
ldr x12, [sp, #32] | ||
msr DDC, c10 | ||
mov x10, #0 | ||
mov sp, x12 | ||
|
||
ret clr | ||
|
||
/* Compartment from which we call the switcher to perform inter-compartment | ||
* transition. The call is via a capability, to update the PCC bounds | ||
* appropriately to cover `switch_compartment`. | ||
*/ | ||
.type comp_f_fn, "function" | ||
comp_f_fn: | ||
mov x0, #1 | ||
msr CID_EL0, c0 | ||
|
||
// Store the `clr` for exitting `comp_f_fn`; this is overwritten by | ||
// `switch_compartment`. | ||
str clr, [sp, #-16]! | ||
blr c20 | ||
ldr clr, [sp], #16 | ||
|
||
ret clr | ||
|
||
/* The function in this compartment just writes to some memory within its | ||
* bounds, to ensure it is properly called. | ||
*/ | ||
.type comp_g_fn, "function" | ||
comp_g_fn: | ||
mrs c10, DDC | ||
mov x11, 42 | ||
str x11, [x10, #4000] | ||
|
||
ret clr | ||
|
||
|
||
// Inner helper for cleaning capabilities from registers, either side of an | ||
// AAPCS64 function call where some level of distrust exists between caller | ||
// and callee. | ||
// | ||
// Depending on the trust model, this might not be required, but the process | ||
// is included here for demonstration purposes. Note that if data needs to | ||
// be scrubbed as well as capabilities, then NEON registers also need to be | ||
// cleaned. | ||
// | ||
// Callers should enter at an appropriate offset so that live registers | ||
// holding arguments and return values (c0-c7) are preserved. | ||
clean: | ||
mov x0, #0 | ||
mov x1, #0 | ||
mov x2, #0 | ||
mov x3, #0 | ||
mov x4, #0 | ||
mov x5, #0 | ||
mov x6, #0 | ||
mov x7, #0 | ||
mov x8, #0 | ||
mov x9, #0 | ||
mov x10, #0 | ||
mov x11, #0 | ||
mov x12, #0 | ||
mov x13, #0 | ||
mov x14, #0 | ||
mov x15, #0 | ||
mov x16, #0 | ||
mov x17, #0 | ||
// x18 is the "platform register" (for some platforms). If so, it needs to | ||
// be preserved, but here we assume that only the lower 64 bits are | ||
// required. | ||
mov x18, x18 | ||
// x19-x29 are callee-saved, but only the lower 64 bits. | ||
// mov x19, x19 | ||
// mov x20, x20 | ||
mov x21, x21 | ||
mov x22, x22 | ||
mov x23, x23 | ||
mov x24, x24 | ||
mov x25, x25 | ||
mov x26, x26 | ||
mov x27, x27 | ||
mov x28, x28 | ||
mov x29, x29 // FP | ||
// We need LR (x30) to return. The call to this helper already cleaned it. | ||
// Don't replace SP; this needs special handling by the caller anyway. | ||
ret | ||
|
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.
Ah, are these used as PCC bounds?
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.
Yes, hardcoded approximate bounds so things can execute, since I'm unaware of a way to compute this at compile-time, if it is possible. They would potentially be handled by the linker, an an actual implementation. Could maybe use labels, to mark start/end?
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.
Good question -- I have no idea! Certainly it should be possible via ELF but it might be possible some other way, though I doubt that it's portable either way. I'm fine with punting on that for this PR. Maybe add an issue to this repo "How to calculate a function's length at runtime" as we'll then be able to ask Edd for suggestions (he knows more about such things than I do!).
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.
Opened #54 to at least start a conversation maybe.
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.
Thanks!