Skip to content

Commit

Permalink
Add example showing inter-compartment switch
Browse files Browse the repository at this point in the history
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`).
  • Loading branch information
0152la committed Dec 6, 2021
1 parent 9f7c157 commit 07f387e
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 0 deletions.
152 changes: 152 additions & 0 deletions hybrid/compartment_examples/inter_comp_call/main.c
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"
"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);
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);
}
6 changes: 6 additions & 0 deletions hybrid/compartment_examples/inter_comp_call/main.h
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
166 changes: 166 additions & 0 deletions hybrid/compartment_examples/inter_comp_call/shared.S
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

0 comments on commit 07f387e

Please sign in to comment.