Skip to content
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

Improve compartment example security #55

Merged
merged 1 commit into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors.
# SPDX-License-Identifier: MIT OR Apache-2.0

SHARED_SOURCES := shared.s
CFILES := $(wildcard *.c)
ROOTDIR=../../../..

include $(ROOTDIR)/build/Makefile.vars.morello-hybrid
include $(ROOTDIR)/build/Makefile.vars.common
include $(ROOTDIR)/build/Makefile.simple
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
* executable area is approximated (`main.c:96`).
*/

#include "../../../include/common.h"
#include "../../include/utils.h"
#include "../../../../include/common.h"
#include "../../../include/utils.h"

#include <assert.h>
#include <stddef.h>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors.
# SPDX-License-Identifier: MIT OR Apache-2.0

SHARED_SOURCES := shared_try_deref.s
CFILES := $(wildcard *.c)
ROOTDIR=../../../..

include $(ROOTDIR)/build/Makefile.vars.morello-hybrid
include $(ROOTDIR)/build/Makefile.vars.common
include $(ROOTDIR)/build/Makefile.simple
203 changes: 203 additions & 0 deletions hybrid/compartment_examples/inter_comp_call/secure-try_deref/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/* In this example, we tighten security even further, building on previous
* examples [1]. We store the DDC and the PCC of `switch_compartment`
* consecutively within its own bounds. Then, we create a capability pointing
* to the DDC, which is sealed such that it can only be used in an `lpb`-type
* call, and provide local copies of this capability to each compartment. Thus,
* compartments are allowed to call `switch_compartment` via `ldpblr`, without
* access to either its PCC or DDC (by nature of capabilities).
*
* The local copies are stored in the heap space of each compartment (as we do
* not implement memory management at this point, this has no bearing at
* present, but most likely will in the future). This could essentially be
* considered a separate region of memory. Further, when using the local
* capabilities to call `switch_compartment` from a compartment, the
* destination register *must* be `c29`.
*
* Note that in the current design, each compartment has a single function it
* executes when it is called. Allowing multiple entry points can be modelled
* by having more capabilities be saved, in addition to the compartment
* switching one, in each compartment's heap. Alternatively, we could, within
* the compartment switcher, determine in which compartment we need to switch
* to call a given function, based on PCC bounds for each compartment.

* Additionally, we also changed the expected memory layout in the
* compartments. In the absence of a custom memory allocator, we note that the
* heap is not used, except for manually storing the local capability copy as
* detailed above. Current compartment memory layout:
*
* ----------------- < DDC + total_comp_size
* | switcher_call |
* | |
* | ^ |
* | HEAP |
* sp > |-----------------| < DDC + comp_stack_size
* | STACK |
* | v |
* ----------------- < `DDC value`
*
* [1]
https://github.com/capablevms/cheri-examples/tree/master/hybrid/compartment_examples/inter_comp_call
*/

#include "../../../../include/common.h"
#include "../../../include/utils.h"

#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sysctl.h>

#if !defined(__CHERI_CAPABILITY_WIDTH__) || defined(__CHERI_PURE_CAPABILITY__)
#error "This example only works on CHERI hybrid mode"
#endif

/*******************************************************************************
* Assembly Functions & Consts
******************************************************************************/

extern void executive_switch(void *__capability);
extern int switch_compartment();
extern void comp_f_fn();
extern void comp_g_fn();
extern void *comp_f_fn_end;
extern void *comp_g_fn_end;

/*******************************************************************************
* Types & Consts
******************************************************************************/

const size_t comp_stack_size = 2000;
const size_t total_comp_size = 4992;
size_t id = 0;

/* Capabilities representing DDC and PCC of the switcher, stored in
* `switcher_caps`. They are saved successively to facilitate calling via
* `ldpblr`, with the first being the DDC, and the second, the PCC.
*/
const size_t switcher_caps_count = 2;
void *__capability switcher_caps[switcher_caps_count];

/* Capability required by compartments to call `switch_compartment` securely.
* Duplicated in each compartment. */
void *__capability switcher_call;

/* Abstract representation of a compartment. Within 80 bytes we represent:
* - an id (8B)
* - address to the start of the compartment memory area (also lowest address
* of the stack) (8B)
* - address to the start (highest address) of the compartment's stack
* (corresponding to the lowest address of the heap) (8B)
* the size of the stack (8B)
* - address to the top (highest addressof 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 *compartment_start;
void *stack_addr;
size_t stack_len;
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
******************************************************************************/

/* Create and save required capabilities. Currently, this means:
* - PCC of `switch_compartment`
* - DDC of `switch_compartment`
* - capability to allow compartments to call `switch_compartment` via `lpdblr`
*/
void init_comps()
{
void *__capability switch_cap = (void *__capability) switch_compartment;
switch_cap = cheri_bounds_set(switch_cap, 80 * 4);
switcher_caps[1] = switch_cap;

void *__capability comps_addr = (void *__capability) &comps;
comps_addr = cheri_bounds_set(comps_addr, COMP_COUNT * COMP_SIZE);
switcher_caps[0] = comps_addr;

switcher_call = (void *__capability) switcher_caps;
// Seal this capability to be only used via a `lpb` type call
asm("seal %w0, %w0, lpb" : "+r"(switcher_call) :);
}

void add_comp(uint8_t *_start_addr, void (*_comp_fn)(), void *_comp_fn_end)
{
assert(id < COMP_COUNT);
struct comp new_comp;
new_comp.id = id;

new_comp.compartment_start = (void *) _start_addr;
new_comp.stack_addr = (void *) (_start_addr + comp_stack_size);
new_comp.stack_len = comp_stack_size;
new_comp.heap_len = total_comp_size - comp_stack_size;

// Ensure 16-byte alignment throught the compartment bounds
assert(((uintptr_t) new_comp.compartment_start) % 16 == 0);
assert(((uintptr_t) new_comp.stack_addr) % 16 == 0);
assert(total_comp_size % 16 == 0);

// When creating a compartment, store a local copy of the capability which
// will allow us to call `switch_compartment` in the heap of the compartment.
void *heap_top = (void *) (_start_addr + total_comp_size - sizeof(void *__capability));
memcpy(heap_top, &switcher_call, sizeof(void *__capability));

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;
// 40 is arbitary; meant to be the size of the executable function within
// compartment
Comment on lines +176 to +177
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you addressed that with precise pointer arithmetic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I forgot to update this comment. I'm unsure if we can amend this PR as is, after merging, or should this be done as part of another PR? @ltratt

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is merged, so it can be done in another PR. I'm fine with small PRs that fix such issues!

size_t comp_fn_size = (uintptr_t) _comp_fn_end - (uintptr_t) _comp_fn;
comp_fn = cheri_bounds_set(comp_fn, comp_fn_size);
new_comp.comp_fn = comp_fn;

comps[id] = new_comp;
++id;
}

/*******************************************************************************
* Main
******************************************************************************/

int main()
{
init_comps();

uint8_t *comp_f = malloc(total_comp_size);
add_comp(comp_f, comp_f_fn, &comp_f_fn_end);
uint8_t *comp_g = malloc(total_comp_size);
add_comp(comp_g, comp_g_fn, &comp_g_fn_end);

executive_switch(switcher_caps[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 16
#define COMP_OFFSET_STK_LEN 24
#define COMP_OFFSET_DDC 48
#define COMP_OFFSET_PCC 64
Loading