diff --git a/hybrid/compartment_examples/inter_comp_call/base/Makefile.morello-hybrid b/hybrid/compartment_examples/inter_comp_call/base/Makefile.morello-hybrid new file mode 100644 index 00000000..84ba7ca9 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/base/Makefile.morello-hybrid @@ -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 diff --git a/hybrid/compartment_examples/inter_comp_call/main.c b/hybrid/compartment_examples/inter_comp_call/base/main.c similarity index 98% rename from hybrid/compartment_examples/inter_comp_call/main.c rename to hybrid/compartment_examples/inter_comp_call/base/main.c index dbebfda1..ef4ada3d 100644 --- a/hybrid/compartment_examples/inter_comp_call/main.c +++ b/hybrid/compartment_examples/inter_comp_call/base/main.c @@ -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 #include diff --git a/hybrid/compartment_examples/inter_comp_call/main.h b/hybrid/compartment_examples/inter_comp_call/base/main.h similarity index 100% rename from hybrid/compartment_examples/inter_comp_call/main.h rename to hybrid/compartment_examples/inter_comp_call/base/main.h diff --git a/hybrid/compartment_examples/inter_comp_call/shared.S b/hybrid/compartment_examples/inter_comp_call/base/shared.S similarity index 100% rename from hybrid/compartment_examples/inter_comp_call/shared.S rename to hybrid/compartment_examples/inter_comp_call/base/shared.S diff --git a/hybrid/compartment_examples/inter_comp_call/secure-try_deref/Makefile.morello-hybrid b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/Makefile.morello-hybrid new file mode 100644 index 00000000..b8e60df6 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/Makefile.morello-hybrid @@ -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 diff --git a/hybrid/compartment_examples/inter_comp_call/secure-try_deref/main.c b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/main.c new file mode 100644 index 00000000..12475c65 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/main.c @@ -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 +#include +#include +#include +#include +#include + +#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 + 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); +} diff --git a/hybrid/compartment_examples/inter_comp_call/secure-try_deref/main.h b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/main.h new file mode 100644 index 00000000..50a8dc84 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/main.h @@ -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 diff --git a/hybrid/compartment_examples/inter_comp_call/secure-try_deref/shared_try_deref.S b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/shared_try_deref.S new file mode 100644 index 00000000..6e376066 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/shared_try_deref.S @@ -0,0 +1,187 @@ +// 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 +.global comp_f_fn_end +.global comp_g_fn_end + +.text +.balign 4 + +.global executive_switch +.type executive_switch, "function" +executive_switch: + mov c29, c0 + mov x0, #0 + bl switch_compartment + +/* The compartment switch function. Expects compartment information to be + * stored in memory (defined by the capability stored in register `c29`). + * Performs a compartment switch based on the id saved in `x0` (currently just + * an integer index into the `comps` array). + */ +.global switch_compartment +.type switch_compartment, "function" +switch_compartment: + // Store entering compartment's DDC, and move to memory containing + // compartment info + mrs c2, DDC + mov x10, x0 + + // Expect switcher DDC in c29 + msr DDC, c29 + + // Get compartment to switch to data + 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, [x29, x11] + + // Load DDC + add x11, x10, #COMP_OFFSET_DDC + ldr c1, [x29, x11] + + // Setup SP + mov x12, sp + add x11, x10, #COMP_OFFSET_STK_ADDR + ldr x11, [x29, x11] + mov sp, x11 + + // 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 -> [ ] ^ + // 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 + + // Clean all registers, except register used to call function within + // compartment we are transitioning to + 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 + +.type get_comp_switcher_ref, "function" +get_comp_switcher_ref: + mov x1, #5000 + sub x1, x1, #16 + add c0, c0, x1 + ret + +/* 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: + // Retrieve local capability containing switcher information + mrs c1, DDC + gclim x1, c1 + sub x1, x1, #16 + ldr c1, [x1] + + // Try to dereference it to retrieve switcher DDC; this is expected to fail + // due to the local capability being sealed (`main.c:143`). + ldr c1, [c1] + + ldr clr, [sp], #16 + + ret clr +comp_f_fn_end: + +/* 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 +comp_g_fn_end: + + // 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 + diff --git a/hybrid/compartment_examples/inter_comp_call/secure/Makefile.morello-hybrid b/hybrid/compartment_examples/inter_comp_call/secure/Makefile.morello-hybrid new file mode 100644 index 00000000..84ba7ca9 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure/Makefile.morello-hybrid @@ -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 diff --git a/hybrid/compartment_examples/inter_comp_call/secure/main.c b/hybrid/compartment_examples/inter_comp_call/secure/main.c new file mode 100644 index 00000000..12475c65 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure/main.c @@ -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 +#include +#include +#include +#include +#include + +#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 + 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); +} diff --git a/hybrid/compartment_examples/inter_comp_call/secure/main.h b/hybrid/compartment_examples/inter_comp_call/secure/main.h new file mode 100644 index 00000000..50a8dc84 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure/main.h @@ -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 diff --git a/hybrid/compartment_examples/inter_comp_call/secure/shared.S b/hybrid/compartment_examples/inter_comp_call/secure/shared.S new file mode 100644 index 00000000..a863750c --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure/shared.S @@ -0,0 +1,194 @@ +// 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 +.global comp_f_fn_end +.global comp_g_fn_end + +.text +.balign 4 + +.global executive_switch +.type executive_switch, "function" +executive_switch: + mov c29, c0 + mov x0, #0 + b switch_compartment + ret + +/* The compartment switch function. Expects compartment information to be + * stored in memory (defined by the capability stored in register `c29`). + * Performs a compartment switch based on the id saved in `x0` (currently just + * an integer index into the `comps` array). + */ +.global switch_compartment +.type switch_compartment, "function" +switch_compartment: + // Store entering compartment's DDC, and move to memory containing + // compartment info + mrs c2, DDC + mov x10, x0 + + // Expect switcher DDC in c29 + msr DDC, c29 + + // Get compartment to switch to data + 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, [x29, x11] + + // Load DDC + add x11, x10, #COMP_OFFSET_DDC + ldr c1, [x29, x11] + + // Setup SP + mov x12, sp + add x11, x10, #COMP_OFFSET_STK_ADDR + ldr x11, [x29, x11] + mov sp, x11 + + // 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 -> [ ] ^ + // 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 + + // Clean all registers, except register used to call function within + // compartment we are transitioning to + 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 + +.type get_comp_switcher_ref, "function" +get_comp_switcher_ref: + mov x1, #5000 + sub x1, x1, #16 + add c0, c0, x1 + ret + +/* 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: + // Set compartment ID we want to switch to + mov x0, #1 + + // Store the `clr` for exitting `comp_f_fn`; this is overwritten by + // `switch_compartment`. + str clr, [sp, #-16]! + + // Retrieve local capability containing switcher information for `pdlblr` + // instruction (DDC is used as it contains the address where the capability + // is stored in this particular example) + mrs c1, DDC + gclim x1, c1 + sub x1, x1, #16 + ldr c1, [x1] + ldpblr c29, [c1] + + ldr clr, [sp], #16 + + ret clr +comp_f_fn_end: + +/* 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 +comp_g_fn_end: + + // 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 + diff --git a/tests/run_tests.sh b/tests/run_tests.sh index e9836d39..53c23c74 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -62,9 +62,12 @@ function run { # Tests that should fail run to_fail hybrid/ddc_compartment_switching ddc_compartment_switching_nok run to_fail hybrid ddc_invalid ddc_null +run OK hybrid/compartment_examples/inter_comp_call/secure-try_deref main # Tests that should pass run OK hybrid/ddc_compartment_switching ddc_compartment_switching run OK hybrid basic_ddc +run OK hybrid/compartment_examples/inter_comp_call/base main +run OK hybrid/compartment_examples/inter_comp_call/secure main # TODO: 'timsort' works, but takes a very long time. Is it useful to test a # smaller data set?