From 8a1622b1897b9070cd7feafee2b4f2556f814d91 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Thu, 16 Dec 2021 15:21:13 +0000 Subject: [PATCH 01/15] Improve compartment security In this example, we tighten security even further. For each compartment we create, we store some capabilities of interest. Essentially, we setup certain entry points, and make them available to each compartment. Currently, these involve the PCC/DDC of the `switch_compartment` function (PCC to execute, DDC to access memory where we store information pertaining to compartments; essentially, `switch_compartment` performs a limited privelege escalation, but only over prepared data within `main`). Additionally, we also seal the PCC of `switch_compartment`. --- .../inter_comp_call_secure/main.c | 194 ++++++++++++++++++ .../inter_comp_call_secure/main.h | 6 + .../inter_comp_call_secure/shared.S | 171 +++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 hybrid/compartment_examples/inter_comp_call_secure/main.c create mode 100644 hybrid/compartment_examples/inter_comp_call_secure/main.h create mode 100644 hybrid/compartment_examples/inter_comp_call_secure/shared.S 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..9f5ba5bc --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call_secure/main.c @@ -0,0 +1,194 @@ +/* In this example, we tighten security even further. For each compartment we + * create, we store some capabilities of interest. Essentially, we setup + * certain entry points, and make them available to each compartment. + * Currently, these involve the PCC/DDC of the `switch_compartment` function + * (PCC to execute, DDC to access memory where we store information pertaining + * to compartments; essentially, `switch_compartment` performs a limited + * privelege escalation, but only over prepared data within `main`). + * Additionally, we also seal the PCC of `switch_compartment`. + * + * 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 ones, 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 capabilities as detailed + * above. Current compartment memory layout: + * + * `DDC value` > ----------------- + * | | + * | HEAP | + * | | + * | saved caps | + * sp > |-----------------| < DDC + total_comp_size - comp_stack_size + * | | + * | STACK | + * ----------------- < DDC + total_comp_size + */ + +#include "../../../include/common.h" +#include "../../include/utils.h" + +#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 int switch_compartment(); +extern void comp_f_fn(); +extern void comp_g_fn(); + +/******************************************************************************* + * Types & Consts + ******************************************************************************/ + +const size_t comp_stack_size = 1992; +const size_t total_comp_size = 5000; +size_t id = 0; + +// Duplicate certain capabilities within the heap of each compartments; +// currently we save DDC and PCC of `switch_compartment` +const size_t saved_caps_count = 2; +void *__capability saved_caps[saved_caps_count]; + +/* 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 + ******************************************************************************/ + +void init_comps() +{ + void *__capability switch_cap = (void *__capability) switch_compartment; + switch_cap = cheri_bounds_set(switch_cap, 80 * 4); + asm("seal %w0, %w0, rb" : "+r"(switch_cap) :); + saved_caps[0] = switch_cap; + + void *__capability comps_addr = (void *__capability) &comps; + comps_addr = cheri_bounds_set(comps_addr, COMP_COUNT * COMP_SIZE); + saved_caps[1] = comps_addr; +} + +/* 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 comps_addr = saved_caps[1]; + + asm("mov c19, %w0\n\t" + "mov x0, #0\n\t" + "msr CID_EL0, c0" + : + : "r"(comps_addr)); + + void *__capability chk = *((void *__capability *) (comps[0].heap_addr)); + assert(cheri_is_valid(chk)); + switch_compartment(); +} + +/* Store capabilities we want saved within each compartment */ +void update_comp_saved_caps(void *__capability *addr) +{ + for (size_t i = 0; i < saved_caps_count; ++i) + { + addr[i] = saved_caps[i]; + } +} + +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 + total_comp_size - comp_stack_size); + new_comp.stack_len = comp_stack_size; + new_comp.heap_addr = (void *) _start_addr; + new_comp.heap_len = total_comp_size - comp_stack_size; + + // Memory magic; we will store capabilities at the memory location pointed + // by `heap_addr` of current compartment + update_comp_saved_caps((void *__capability *) new_comp.heap_addr); + + 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 + comp_fn = cheri_bounds_set(comp_fn, 40); + 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); + 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); +} 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..93354ec7 --- /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 8 +#define COMP_OFFSET_STK_LEN 16 +#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..5b188346 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call_secure/shared.S @@ -0,0 +1,171 @@ +// 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 + // Expect switcher DDC in c19 + 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 -> [ ] ^ + // 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]! + + // Retrieve PCC and DDC of switcher from the heap + mrs c20, DDC + ldp c20, c19, [x20, #0] + blrs 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 + From 3955d6af194c6f59f5bc9f71b06702e66ecaa9dc Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Fri, 17 Dec 2021 09:13:35 +0000 Subject: [PATCH 02/15] Spell and comments --- hybrid/compartment_examples/inter_comp_call_secure/main.c | 2 +- hybrid/compartment_examples/inter_comp_call_secure/shared.S | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call_secure/main.c b/hybrid/compartment_examples/inter_comp_call_secure/main.c index 9f5ba5bc..1d9bec87 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/main.c +++ b/hybrid/compartment_examples/inter_comp_call_secure/main.c @@ -4,7 +4,7 @@ * Currently, these involve the PCC/DDC of the `switch_compartment` function * (PCC to execute, DDC to access memory where we store information pertaining * to compartments; essentially, `switch_compartment` performs a limited - * privelege escalation, but only over prepared data within `main`). + * privilege escalation, but only over prepared data within `main`). * Additionally, we also seal the PCC of `switch_compartment`. * * Note that in the current design, each compartment has a single function it diff --git a/hybrid/compartment_examples/inter_comp_call_secure/shared.S b/hybrid/compartment_examples/inter_comp_call_secure/shared.S index 5b188346..c4e82ce4 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/shared.S +++ b/hybrid/compartment_examples/inter_comp_call_secure/shared.S @@ -41,8 +41,6 @@ switch_compartment: 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. From 4f2848d8934cc60d1ad2f6c2a94ac00835f0c84f Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Fri, 7 Jan 2022 12:08:06 +0000 Subject: [PATCH 03/15] Rather major logic update We now use `ldpblr` to obfuscate the switcher DDC and PCC, and provide compartments only a capability which indirectly allows to call `switch_compartment`, exclusively via a `lpb` instruction. --- .../inter_comp_call_secure/main.c | 87 ++++++++++--------- .../inter_comp_call_secure/shared.S | 24 ++--- 2 files changed, 60 insertions(+), 51 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call_secure/main.c b/hybrid/compartment_examples/inter_comp_call_secure/main.c index 1d9bec87..fc219981 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/main.c +++ b/hybrid/compartment_examples/inter_comp_call_secure/main.c @@ -1,29 +1,35 @@ -/* In this example, we tighten security even further. For each compartment we - * create, we store some capabilities of interest. Essentially, we setup - * certain entry points, and make them available to each compartment. - * Currently, these involve the PCC/DDC of the `switch_compartment` function - * (PCC to execute, DDC to access memory where we store information pertaining - * to compartments; essentially, `switch_compartment` performs a limited - * privilege escalation, but only over prepared data within `main`). - * Additionally, we also seal the PCC of `switch_compartment`. +/* In this example, we tighten security even further. 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 ones, in each compartment's heap. Alternatively, we could, within + * 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 capabilities as detailed - * above. Current compartment memory layout: + * heap is not used, except for manually storing the local capability copy as + * detailed above. Current compartment memory layout: * * `DDC value` > ----------------- + * | switcher_call | * | | - * | HEAP | + * | HEAP | * | | - * | saved caps | * sp > |-----------------| < DDC + total_comp_size - comp_stack_size * | | * | STACK | @@ -38,6 +44,7 @@ #include #include #include +#include #if !defined(__CHERI_CAPABILITY_WIDTH__) || defined(__CHERI_PURE_CAPABILITY__) #error "This example only works on CHERI hybrid mode" @@ -59,10 +66,16 @@ const size_t comp_stack_size = 1992; const size_t total_comp_size = 5000; size_t id = 0; -// Duplicate certain capabilities within the heap of each compartments; -// currently we save DDC and PCC of `switch_compartment` -const size_t saved_caps_count = 2; -void *__capability saved_caps[saved_caps_count]; +/* 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) @@ -103,45 +116,37 @@ 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); - asm("seal %w0, %w0, rb" : "+r"(switch_cap) :); - saved_caps[0] = switch_cap; + switcher_caps[1] = switch_cap; void *__capability comps_addr = (void *__capability) &comps; comps_addr = cheri_bounds_set(comps_addr, COMP_COUNT * COMP_SIZE); - saved_caps[1] = comps_addr; + 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) : ); } -/* 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 comps_addr = saved_caps[1]; - - asm("mov c19, %w0\n\t" + asm("mov c29, %w0\n\t" "mov x0, #0\n\t" "msr CID_EL0, c0" : - : "r"(comps_addr)); + : "r"(switcher_caps[0])); - void *__capability chk = *((void *__capability *) (comps[0].heap_addr)); - assert(cheri_is_valid(chk)); switch_compartment(); } -/* Store capabilities we want saved within each compartment */ -void update_comp_saved_caps(void *__capability *addr) -{ - for (size_t i = 0; i < saved_caps_count; ++i) - { - addr[i] = saved_caps[i]; - } -} - void add_comp(uint8_t *_start_addr, void (*_comp_fn)()) { assert(id < COMP_COUNT); @@ -154,9 +159,9 @@ void add_comp(uint8_t *_start_addr, void (*_comp_fn)()) new_comp.heap_addr = (void *) _start_addr; new_comp.heap_len = total_comp_size - comp_stack_size; - // Memory magic; we will store capabilities at the memory location pointed - // by `heap_addr` of current compartment - update_comp_saved_caps((void *__capability *) new_comp.heap_addr); + // 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. + memcpy(new_comp.heap_addr, &switcher_call, sizeof(void *__capability)); void *__capability comp_ddc = (void *__capability) _start_addr; comp_ddc = cheri_bounds_set(comp_ddc, total_comp_size); diff --git a/hybrid/compartment_examples/inter_comp_call_secure/shared.S b/hybrid/compartment_examples/inter_comp_call_secure/shared.S index c4e82ce4..98cb5ea1 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/shared.S +++ b/hybrid/compartment_examples/inter_comp_call_secure/shared.S @@ -10,19 +10,19 @@ .balign 4 /* The compartment switch function. Expects compartment information to be - * stored in memory (defined by the capability stored in register `c19`). + * stored in memory (defined by the capability stored in register `c29`). * 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 - // Expect switcher DDC in c19 - msr DDC, c19 + + // Expect switcher DDC in c29 + msr DDC, c29 // Get compartment to switch to data mrs c10, CID_EL0 @@ -31,16 +31,16 @@ switch_compartment: // Load PCC, including function we are jumping to within compartment add x11, x10, #COMP_OFFSET_PCC - ldr c0, [x19, x11] + ldr c0, [x29, x11] // Load DDC add x11, x10, #COMP_OFFSET_DDC - ldr c1, [x19, x11] + ldr c1, [x29, x11] // Setup SP mov x12, sp add x11, x10, #COMP_OFFSET_STK_ADDR - ldr x11, [x19, x11] + ldr x11, [x29, x11] add sp, x11, x13 // Derive a new clr to restore PCC, and store it. @@ -65,6 +65,8 @@ switch_compartment: // : : // `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 @@ -97,10 +99,12 @@ comp_f_fn: // `switch_compartment`. str clr, [sp, #-16]! - // Retrieve PCC and DDC of switcher from the heap + // 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 c20, DDC - ldp c20, c19, [x20, #0] - blrs c20 + ldr c20, [c20] + ldpblr c29, [c20] ldr clr, [sp], #16 From db7617bdb8a0bfc3b6cec9413d285b91f5b2a000 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Mon, 10 Jan 2022 12:45:12 +0000 Subject: [PATCH 04/15] Further updates * Fix memory allocation within compartments to match desired internal structure (as per `main.c:28-36`); * Add asserts to check for correct compartment memory alignment (to 16 bytes, as required); * Add a better way to determine sizes of functions, rather than hard-coding stuff. --- .../inter_comp_call_secure/main.c | 61 +++++++++++-------- .../inter_comp_call_secure/shared.S | 37 +++++++---- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call_secure/main.c b/hybrid/compartment_examples/inter_comp_call_secure/main.c index fc219981..ff570930 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/main.c +++ b/hybrid/compartment_examples/inter_comp_call_secure/main.c @@ -25,15 +25,15 @@ * heap is not used, except for manually storing the local capability copy as * detailed above. Current compartment memory layout: * - * `DDC value` > ----------------- - * | switcher_call | - * | | - * | HEAP | - * | | - * sp > |-----------------| < DDC + total_comp_size - comp_stack_size - * | | - * | STACK | - * ----------------- < DDC + total_comp_size + * ----------------- < DDC + total_comp_size + * | switcher_call | + * | | + * | ^ | + * | HEAP | + * sp > |-----------------| < DDC + comp_stack_size + * | STACK | + * | v | + * ----------------- < `DDC value` */ #include "../../../include/common.h" @@ -57,13 +57,15 @@ 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 = 1992; -const size_t total_comp_size = 5000; +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 @@ -79,9 +81,12 @@ void *__capability switcher_call; /* Abstract representation of a compartment. Within 80 bytes we represent: * - an id (8B) - * - address to the start of the compartment's stack (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 start of the compartment's heap (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) @@ -90,9 +95,9 @@ void *__capability switcher_call; struct comp { size_t id; + void* compartment_start; void *stack_addr; size_t stack_len; - void *heap_addr; size_t heap_len; void *__capability ddc; void *__capability comp_fn; @@ -146,22 +151,25 @@ void executive_switch(struct comp c) switch_compartment(); } - -void add_comp(uint8_t *_start_addr, void (*_comp_fn)()) +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; + assert(id < COMP_COUNT); + struct comp new_comp; new_comp.id = id; - new_comp.stack_addr = (void *) (_start_addr + total_comp_size - comp_stack_size); + 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_addr = (void *) _start_addr; 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. - memcpy(new_comp.heap_addr, &switcher_call, sizeof(void *__capability)); + 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); @@ -172,7 +180,8 @@ void add_comp(uint8_t *_start_addr, void (*_comp_fn)()) void *__capability comp_fn = (void *__capability) _comp_fn; // 40 is arbitary; meant to be the size of the executable function within // compartment - comp_fn = cheri_bounds_set(comp_fn, 40); + 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; @@ -188,9 +197,9 @@ int main() init_comps(); uint8_t *comp_f = malloc(total_comp_size); - add_comp(comp_f, comp_f_fn); + 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); + add_comp(comp_g, comp_g_fn, &comp_g_fn_end); executive_switch(comps[0]); diff --git a/hybrid/compartment_examples/inter_comp_call_secure/shared.S b/hybrid/compartment_examples/inter_comp_call_secure/shared.S index 98cb5ea1..052eea24 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/shared.S +++ b/hybrid/compartment_examples/inter_comp_call_secure/shared.S @@ -5,6 +5,8 @@ .global comp_f_fn .global comp_g_fn +.global comp_f_fn_end +.global comp_g_fn_end .text .balign 4 @@ -41,7 +43,7 @@ switch_compartment: mov x12, sp add x11, x10, #COMP_OFFSET_STK_ADDR ldr x11, [x29, x11] - add sp, x11, x13 + mov sp, x11 // Derive a new clr to restore PCC, and store it. cvtp c11, lr @@ -86,40 +88,51 @@ switch_compartment: 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: - mov x0, #1 - msr CID_EL0, c0 + 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]! + 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 c20, DDC - ldr c20, [c20] - ldpblr c29, [c20] + mrs c0, DDC + gclim x0, c0 + sub x0, x0, #16 + ldr c0, [x0] + ldpblr c29, [c0] - ldr clr, [sp], #16 + ldr clr, [sp], #16 - ret clr + 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] + 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 From 9fe7172cb480ec112d21e24f84656c559fee9f3b Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Mon, 10 Jan 2022 12:51:43 +0000 Subject: [PATCH 05/15] Clarify over what we are improving security --- .../inter_comp_call_secure/main.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call_secure/main.c b/hybrid/compartment_examples/inter_comp_call_secure/main.c index ff570930..763636c0 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/main.c +++ b/hybrid/compartment_examples/inter_comp_call_secure/main.c @@ -1,10 +1,10 @@ -/* In this example, we tighten security even further. 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). +/* 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 @@ -34,6 +34,8 @@ * | STACK | * | v | * ----------------- < `DDC value` + * + * [1] https://github.com/capablevms/cheri-examples/tree/master/hybrid/compartment_examples/inter_comp_call */ #include "../../../include/common.h" From 47e39b76d2af704cb87d2bf7653a3b2c5b032d47 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 10:01:18 +0000 Subject: [PATCH 06/15] Remove usage of `CID_EL0` register --- .../inter_comp_call_secure/main.c | 1 - .../inter_comp_call_secure/shared.S | 18 +++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call_secure/main.c b/hybrid/compartment_examples/inter_comp_call_secure/main.c index 763636c0..06b5e2f8 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/main.c +++ b/hybrid/compartment_examples/inter_comp_call_secure/main.c @@ -147,7 +147,6 @@ void executive_switch(struct comp c) { asm("mov c29, %w0\n\t" "mov x0, #0\n\t" - "msr CID_EL0, c0" : : "r"(switcher_caps[0])); diff --git a/hybrid/compartment_examples/inter_comp_call_secure/shared.S b/hybrid/compartment_examples/inter_comp_call_secure/shared.S index 052eea24..2edc39f7 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/shared.S +++ b/hybrid/compartment_examples/inter_comp_call_secure/shared.S @@ -13,8 +13,8 @@ /* 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 `CID_EL0` (currently - * just an integer). + * 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" @@ -22,12 +22,12 @@ 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 - mrs c10, CID_EL0 mov x11, #COMP_SIZE mul x10, x10, x11 @@ -101,8 +101,8 @@ get_comp_switcher_ref: */ .type comp_f_fn, "function" comp_f_fn: + // Set compartment ID we want to switch to mov x0, #1 - msr CID_EL0, c0 // Store the `clr` for exitting `comp_f_fn`; this is overwritten by // `switch_compartment`. @@ -111,11 +111,11 @@ comp_f_fn: // 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 c0, DDC - gclim x0, c0 - sub x0, x0, #16 - ldr c0, [x0] - ldpblr c29, [c0] + mrs c1, DDC + gclim x1, c1 + sub x1, x1, #16 + ldr c1, [x1] + ldpblr c29, [c1] ldr clr, [sp], #16 From 5d529edf16f81593d7d5c93db5ab868e4937f978 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 10:01:46 +0000 Subject: [PATCH 07/15] Apply `clang-format` --- .../inter_comp_call_secure/main.c | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call_secure/main.c b/hybrid/compartment_examples/inter_comp_call_secure/main.c index 06b5e2f8..dff38019 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/main.c +++ b/hybrid/compartment_examples/inter_comp_call_secure/main.c @@ -35,7 +35,8 @@ * | v | * ----------------- < `DDC value` * - * [1] https://github.com/capablevms/cheri-examples/tree/master/hybrid/compartment_examples/inter_comp_call + * [1] + https://github.com/capablevms/cheri-examples/tree/master/hybrid/compartment_examples/inter_comp_call */ #include "../../../include/common.h" @@ -59,8 +60,8 @@ 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; +extern void *comp_f_fn_end; +extern void *comp_g_fn_end; /******************************************************************************* * Types & Consts @@ -97,7 +98,7 @@ void *__capability switcher_call; struct comp { size_t id; - void* compartment_start; + void *compartment_start; void *stack_addr; size_t stack_len; size_t heap_len; @@ -138,9 +139,9 @@ void init_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) : ); + 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 executive_switch(struct comp c) @@ -152,25 +153,26 @@ void executive_switch(struct comp c) switch_compartment(); } -void add_comp(uint8_t *_start_addr, void (*_comp_fn)(), void * _comp_fn_end) +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; + 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); + // 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)); + // 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); @@ -181,7 +183,7 @@ void add_comp(uint8_t *_start_addr, void (*_comp_fn)(), void * _comp_fn_end) 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; + 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; @@ -200,7 +202,7 @@ int main() 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); + add_comp(comp_g, comp_g_fn, &comp_g_fn_end); executive_switch(comps[0]); From d61e140e8702ab51a16184e69e5befcb69f0bb08 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 10:52:50 +0000 Subject: [PATCH 08/15] Add negative example Example in which a compartment attempts to dereference its local capability to access DDC of `switch_compartment`. --- .../inter_comp_call_secure/shared_try_deref.S | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 hybrid/compartment_examples/inter_comp_call_secure/shared_try_deref.S diff --git a/hybrid/compartment_examples/inter_comp_call_secure/shared_try_deref.S b/hybrid/compartment_examples/inter_comp_call_secure/shared_try_deref.S new file mode 100644 index 00000000..f5fb2ece --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call_secure/shared_try_deref.S @@ -0,0 +1,186 @@ +// Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors. +// SPDX-License-Identifier: MIT OR Apache-2.0 + +/* This example showcases an adversarial compartment. Compartment `f`, + * comprising of function `comp_f_fn`, attempts to misuse its local capability + * for `switch_compartment` access, by dereferencing it + * (`shared_try_deref.S:118`), in order to access the DDC of + * `switch_compartment. + +#include "main.h" + +.global comp_f_fn +.global comp_g_fn +.global comp_f_fn_end +.global comp_g_fn_end + +.text +.balign 4 + +/* 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 + From 6deca14077a2b41d3ab9acef3abd78032f8b351b Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 14:35:50 +0000 Subject: [PATCH 09/15] Move executive switch in asm --- .../inter_comp_call_secure/main.c | 12 ++---------- .../inter_comp_call_secure/shared.S | 8 ++++++++ .../inter_comp_call_secure/shared_try_deref.S | 13 +++++++------ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call_secure/main.c b/hybrid/compartment_examples/inter_comp_call_secure/main.c index dff38019..5a7f5abc 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/main.c +++ b/hybrid/compartment_examples/inter_comp_call_secure/main.c @@ -57,6 +57,7 @@ * Assembly Functions & Consts ******************************************************************************/ +extern void executive_switch(void *__capability); extern int switch_compartment(); extern void comp_f_fn(); extern void comp_g_fn(); @@ -144,15 +145,6 @@ void init_comps() asm("seal %w0, %w0, lpb" : "+r"(switcher_call) :); } -void executive_switch(struct comp c) -{ - asm("mov c29, %w0\n\t" - "mov x0, #0\n\t" - : - : "r"(switcher_caps[0])); - - switch_compartment(); -} void add_comp(uint8_t *_start_addr, void (*_comp_fn)(), void *_comp_fn_end) { assert(id < COMP_COUNT); @@ -204,7 +196,7 @@ int main() uint8_t *comp_g = malloc(total_comp_size); add_comp(comp_g, comp_g_fn, &comp_g_fn_end); - executive_switch(comps[0]); + 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/shared.S b/hybrid/compartment_examples/inter_comp_call_secure/shared.S index 2edc39f7..a863750c 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/shared.S +++ b/hybrid/compartment_examples/inter_comp_call_secure/shared.S @@ -11,6 +11,14 @@ .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 diff --git a/hybrid/compartment_examples/inter_comp_call_secure/shared_try_deref.S b/hybrid/compartment_examples/inter_comp_call_secure/shared_try_deref.S index f5fb2ece..6e376066 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/shared_try_deref.S +++ b/hybrid/compartment_examples/inter_comp_call_secure/shared_try_deref.S @@ -1,12 +1,6 @@ // Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors. // SPDX-License-Identifier: MIT OR Apache-2.0 -/* This example showcases an adversarial compartment. Compartment `f`, - * comprising of function `comp_f_fn`, attempts to misuse its local capability - * for `switch_compartment` access, by dereferencing it - * (`shared_try_deref.S:118`), in order to access the DDC of - * `switch_compartment. - #include "main.h" .global comp_f_fn @@ -17,6 +11,13 @@ .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 From e3455dd6c56f465b7f5a65a9f2fab9e6a3292e28 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 15:51:40 +0000 Subject: [PATCH 10/15] Add two recent examples to `buildbot` --- .../inter_comp_call/Makefile.morello-hybrid | 9 +++++++++ .../inter_comp_call_secure/Makefile.morello-hybrid | 9 +++++++++ tests/run_tests.sh | 2 ++ 3 files changed, 20 insertions(+) create mode 100644 hybrid/compartment_examples/inter_comp_call/Makefile.morello-hybrid create mode 100644 hybrid/compartment_examples/inter_comp_call_secure/Makefile.morello-hybrid diff --git a/hybrid/compartment_examples/inter_comp_call/Makefile.morello-hybrid b/hybrid/compartment_examples/inter_comp_call/Makefile.morello-hybrid new file mode 100644 index 00000000..aa99a374 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/Makefile.morello-hybrid @@ -0,0 +1,9 @@ +# Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors. +# SPDX-License-Identifier: MIT OR Apache-2.0 + +SHARED_SOURCES := shared.s +CFILES := $(wildcard *.c) + +include ../../../build/Makefile.vars.morello-hybrid +include ../../../build/Makefile.vars.common +include ../../../build/Makefile.simple 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..aa99a374 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call_secure/Makefile.morello-hybrid @@ -0,0 +1,9 @@ +# Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors. +# SPDX-License-Identifier: MIT OR Apache-2.0 + +SHARED_SOURCES := shared.s +CFILES := $(wildcard *.c) + +include ../../../build/Makefile.vars.morello-hybrid +include ../../../build/Makefile.vars.common +include ../../../build/Makefile.simple diff --git a/tests/run_tests.sh b/tests/run_tests.sh index e9836d39..03e856dd 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -65,6 +65,8 @@ run to_fail hybrid ddc_invalid ddc_null # 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 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? From 705ae238ada80431bc1b91cc86a9d5f2b74088a1 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 16:11:42 +0000 Subject: [PATCH 11/15] Update comp struct offsets --- hybrid/compartment_examples/inter_comp_call/main.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call/main.h b/hybrid/compartment_examples/inter_comp_call/main.h index 93354ec7..50a8dc84 100644 --- a/hybrid/compartment_examples/inter_comp_call/main.h +++ b/hybrid/compartment_examples/inter_comp_call/main.h @@ -1,6 +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_STK_ADDR 16 +#define COMP_OFFSET_STK_LEN 24 #define COMP_OFFSET_DDC 48 #define COMP_OFFSET_PCC 64 From 2450baa5f405810b267f2b8caa9880f30357f905 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 16:12:43 +0000 Subject: [PATCH 12/15] Why is this missing locally --- .../inter_comp_call/shared_try_deref.S | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 hybrid/compartment_examples/inter_comp_call/shared_try_deref.S diff --git a/hybrid/compartment_examples/inter_comp_call/shared_try_deref.S b/hybrid/compartment_examples/inter_comp_call/shared_try_deref.S new file mode 100644 index 00000000..6e376066 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/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 + From cc545831fb562465f90093623612021c461c50b9 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 16:14:42 +0000 Subject: [PATCH 13/15] That's why --- .../inter_comp_call/shared_try_deref.S | 187 ------------------ 1 file changed, 187 deletions(-) delete mode 100644 hybrid/compartment_examples/inter_comp_call/shared_try_deref.S diff --git a/hybrid/compartment_examples/inter_comp_call/shared_try_deref.S b/hybrid/compartment_examples/inter_comp_call/shared_try_deref.S deleted file mode 100644 index 6e376066..00000000 --- a/hybrid/compartment_examples/inter_comp_call/shared_try_deref.S +++ /dev/null @@ -1,187 +0,0 @@ -// 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 - From 82d8f809b7e48a53a60d435d602c7c94876c50be Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 16:16:03 +0000 Subject: [PATCH 14/15] Undo wrong updates --- hybrid/compartment_examples/inter_comp_call/main.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call/main.h b/hybrid/compartment_examples/inter_comp_call/main.h index 50a8dc84..93354ec7 100644 --- a/hybrid/compartment_examples/inter_comp_call/main.h +++ b/hybrid/compartment_examples/inter_comp_call/main.h @@ -1,6 +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_STK_ADDR 8 +#define COMP_OFFSET_STK_LEN 16 #define COMP_OFFSET_DDC 48 #define COMP_OFFSET_PCC 64 From f41d67fefe5024c125df3fadc79de37c7bc14130 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Tue, 11 Jan 2022 16:16:55 +0000 Subject: [PATCH 15/15] Update comp offsets --- hybrid/compartment_examples/inter_comp_call_secure/main.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hybrid/compartment_examples/inter_comp_call_secure/main.h b/hybrid/compartment_examples/inter_comp_call_secure/main.h index 93354ec7..50a8dc84 100644 --- a/hybrid/compartment_examples/inter_comp_call_secure/main.h +++ b/hybrid/compartment_examples/inter_comp_call_secure/main.h @@ -1,6 +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_STK_ADDR 16 +#define COMP_OFFSET_STK_LEN 24 #define COMP_OFFSET_DDC 48 #define COMP_OFFSET_PCC 64