From 07f387e9c6e859facc790dd04f8fd7edebc8dcc4 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Fri, 3 Dec 2021 14:24:12 +0000 Subject: [PATCH] Add example showing inter-compartment switch This example adds a second compartment, and shows a possible design to achieve inter-compartment switching without (much) privilege escalation. In this file, we setup compartment information within array `comps` (`main.c:84`). This information will be used by the assembly function `switch_compartment`. Further, we save a capability to `switch_compartment()` in a register (`main.c:102`), to be able to call it from a bounded-PCC context, as well as a capability defining the area of memory with compartment information (`main.c:101`). These could be added to the memory area of each created compartment for better security, as well as sealed. Our two compartments provide one entry point each, `comp_f_fn` (`shared.S:93`), and `comp_g_fn` (`shared.S:109`). The design is that compartment `f` will perform a switch to compartment `g`, which performs some observable action (in this instance, sets a specific memory location to a specific integer value). The switching process in (almost) entirely bound within `switch_compartment`, on the execution side, and the memory area with compartment information, for memory access. We note almost, as the executable area is approximated (`main.c:96`). --- .../inter_comp_call/main.c | 152 ++++++++++++++++ .../inter_comp_call/main.h | 6 + .../inter_comp_call/shared.S | 166 ++++++++++++++++++ 3 files changed, 324 insertions(+) create mode 100644 hybrid/compartment_examples/inter_comp_call/main.c create mode 100644 hybrid/compartment_examples/inter_comp_call/main.h create mode 100644 hybrid/compartment_examples/inter_comp_call/shared.S diff --git a/hybrid/compartment_examples/inter_comp_call/main.c b/hybrid/compartment_examples/inter_comp_call/main.c new file mode 100644 index 0000000..dbebfda --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/main.c @@ -0,0 +1,152 @@ +/* This example adds a second compartment, and shows a possible design to + * achieve inter-compartment switching without (much) privilege escalation. In + * this file, we setup compartment information within array `comps` + * (`main.c:84`). This information will be used by the assembly function + * `switch_compartment`. Further, we save a capability to + * `switch_compartment()` in a register (`main.c:102`), to be able to call it + * from a bounded-PCC context, as well as a capability defining the area of + * memory with compartment information (`main.c:101`). These could be added to + * the memory area of each created compartment for better security, as well as + * sealed. + * + * Our two compartments provide one entry point each, `comp_f_fn` + * (`shared.S:93`), and `comp_g_fn` (`shared.S:109`). The design is that + * compartment `f` will perform a switch to compartment `g`, which performs + * some observable action (in this instance, sets a specific memory location to + * a specific integer value). The switching process in (almost) entirely bound + * within `switch_compartment`, on the execution side, and the memory area with + * compartment information, for memory access. We note almost, as the + * executable area is approximated (`main.c:96`). + */ + +#include "../../../include/common.h" +#include "../../include/utils.h" + +#include +#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 = 2000; +const size_t total_comp_size = 5000; +size_t id = 0; + +/* Abstract representation of a compartment. Within 80 bytes we represent: + * - an id (8B) + * - address to the start of the compartment's stack (8B) + * the size of the stack (8B) + * - address to the start of the compartment's heap (8B) + * the size of the heap (8B) + * - alignment padding (8B) + * - the ddc corresponding to the compartment (16B) + * - the function within the compartment to be executed upon switching (16B) + */ +struct comp +{ + size_t id; + void *stack_addr; + size_t stack_len; + void *heap_addr; + size_t heap_len; + void *__capability ddc; + void *__capability comp_fn; +}; + +// ASM offsets, included here for validation +#include "main.h" + +static_assert(COMP_SIZE == sizeof(struct comp), "Invalid `COMP_SIZE` provided"); +static_assert(COMP_OFFSET_STK_ADDR == offsetof(struct comp, stack_addr), + "Invalid `COMP_OFFSET_STK_ADDR` provided."); +static_assert(COMP_OFFSET_STK_LEN == offsetof(struct comp, stack_len), + "Invalid `COMP_OFFSET_STK_LEN` provided."); +static_assert(COMP_OFFSET_DDC == offsetof(struct comp, ddc), "Invalid `COMP_OFFSET_DDC` provided."); +static_assert(COMP_OFFSET_PCC == offsetof(struct comp, comp_fn), + "Invalid `COMP_OFFSET_PCC` provided."); + +struct comp comps[COMP_COUNT]; + +/******************************************************************************* + * Privileged Functions + ******************************************************************************/ + +/* This function stores the compartment data in memory, so that it can be + * accessed by the compartments as needed. + */ +void executive_switch(struct comp c) +{ + void *__capability switch_cap = (void *__capability) switch_compartment; + switch_cap = cheri_bounds_set(switch_cap, 80 * 4); + + void *__capability comps_addr = (void *__capability) &comps; + comps_addr = cheri_bounds_set(comps_addr, COMP_COUNT * COMP_SIZE); + + asm("mov c19, %w0\n\t" + "mov c20, %w1\n\t" + "mov x0, #0\n\t" + "msr CID_EL0, c0" + : + : "r"(comps_addr), "r"(switch_cap)); + + switch_compartment(); +} + +void add_comp(uint8_t *_start_addr, void (*_comp_fn)()) +{ + assert(id < COMP_COUNT); + + struct comp new_comp; + new_comp.id = id; + + new_comp.stack_addr = (void *) _start_addr; + new_comp.stack_len = comp_stack_size; + new_comp.heap_addr = (void *) (_start_addr + comp_stack_size); + new_comp.heap_len = total_comp_size - comp_stack_size; + + void *__capability comp_ddc = (void *__capability) _start_addr; + comp_ddc = cheri_bounds_set(comp_ddc, total_comp_size); + new_comp.ddc = comp_ddc; + + // Set up a capability pointing to the function we want to call within the + // compartment. This will be loaded as the PCC when the function is called. + void *__capability comp_fn = (void *__capability) _comp_fn; + comp_fn = cheri_bounds_set(comp_fn, 40); + new_comp.comp_fn = comp_fn; + + comps[id] = new_comp; + ++id; +} + +/******************************************************************************* + * Main + ******************************************************************************/ + +int main() +{ + uint8_t *comp_f = malloc(total_comp_size); + add_comp(comp_f, comp_f_fn); + uint8_t *comp_g = malloc(total_comp_size); + add_comp(comp_g, comp_g_fn); + + executive_switch(comps[0]); + + // Check compartment did indeed execute + assert(comp_g[4000] == 42); +} diff --git a/hybrid/compartment_examples/inter_comp_call/main.h b/hybrid/compartment_examples/inter_comp_call/main.h new file mode 100644 index 0000000..93354ec --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/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/shared.S b/hybrid/compartment_examples/inter_comp_call/shared.S new file mode 100644 index 0000000..9d53164 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/shared.S @@ -0,0 +1,166 @@ +// Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors. +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#include "main.h" + +.global comp_f_fn +.global comp_g_fn + +.text +.balign 4 + +/* The compartment switch function. Expects compartment information to be + * stored in memory (defined by the capability stored in register `c19`). + * Performs a compartment switch based on the id saved in `CID_EL0` (currently + * just an integer). + */ +.global switch_compartment +.type switch_compartment, "function" +switch_compartment: + + // Store entering compartment's DDC, and move to memory containing + // compartment info + mrs c2, DDC + msr DDC, c19 + + // Get compartment to switch to data + mrs c10, CID_EL0 + mov x11, #COMP_SIZE + mul x10, x10, x11 + + // Load PCC, including function we are jumping to within compartment + add x11, x10, #COMP_OFFSET_PCC + ldr c0, [x19, x11] + + // Load DDC + add x11, x10, #COMP_OFFSET_DDC + ldr c1, [x19, x11] + + // Setup SP + mov x12, sp + add x11, x10, #COMP_OFFSET_STK_ADDR + ldr x11, [x19, x11] + add x13, x10, #COMP_OFFSET_STK_LEN + ldr x13, [x19, x13] + add sp, x11, x13 + + // Derive a new clr to restore PCC, and store it. + cvtp c11, lr + + // Install compartment DDC + msr DDC, c1 + + // Save old DDC (c2), old SP (x12), old LR (c11) on stack + stp c2, c11, [sp, #-48]! + str x12, [sp, #32] + + // Stack layout at this point: + // + // `stack + size` -> ________________________ + // sp + 40 -> [ ] ^ + // sp + 32 -> [ old SP ] | + // sp + 24 -> [ old CLR (hi64) ] | + // sp + 16 -> [ old CLR (lo64) ] | + // sp + 8 -> [ old DDC (high 64) ] | DDC bounds + // sp + 0 -> [ old DDC (low 64) ] | + // : : + // `stack` -> ________________________v + + bl clean+4 + + // Jump to the function within the compartment we are switching to (this + // also sets PCC) + blr c0 + + // Clean capabilities left in the return value. + mov w0, w0 + bl clean + + // Restore the caller's context and compartment. + ldp c10, clr, [sp] + ldr x12, [sp, #32] + msr DDC, c10 + mov x10, #0 + mov sp, x12 + + ret clr + +/* Compartment from which we call the switcher to perform inter-compartment + * transition. The call is via a capability, to update the PCC bounds + * appropriately to cover `switch_compartment`. + */ +.type comp_f_fn, "function" +comp_f_fn: + mov x0, #1 + msr CID_EL0, c0 + + // Store the `clr` for exitting `comp_f_fn`; this is overwritten by + // `switch_compartment`. + str clr, [sp, #-16]! + blr c20 + ldr clr, [sp], #16 + + ret clr + +/* The function in this compartment just writes to some memory within its + * bounds, to ensure it is properly called. + */ +.type comp_g_fn, "function" +comp_g_fn: + mrs c10, DDC + mov x11, 42 + str x11, [x10, #4000] + + ret clr + + + // Inner helper for cleaning capabilities from registers, either side of an + // AAPCS64 function call where some level of distrust exists between caller + // and callee. + // + // Depending on the trust model, this might not be required, but the process + // is included here for demonstration purposes. Note that if data needs to + // be scrubbed as well as capabilities, then NEON registers also need to be + // cleaned. + // + // Callers should enter at an appropriate offset so that live registers + // holding arguments and return values (c0-c7) are preserved. +clean: + mov x0, #0 + mov x1, #0 + mov x2, #0 + mov x3, #0 + mov x4, #0 + mov x5, #0 + mov x6, #0 + mov x7, #0 + mov x8, #0 + mov x9, #0 + mov x10, #0 + mov x11, #0 + mov x12, #0 + mov x13, #0 + mov x14, #0 + mov x15, #0 + mov x16, #0 + mov x17, #0 + // x18 is the "platform register" (for some platforms). If so, it needs to + // be preserved, but here we assume that only the lower 64 bits are + // required. + mov x18, x18 + // x19-x29 are callee-saved, but only the lower 64 bits. + // mov x19, x19 + // mov x20, x20 + mov x21, x21 + mov x22, x22 + mov x23, x23 + mov x24, x24 + mov x25, x25 + mov x26, x26 + mov x27, x27 + mov x28, x28 + mov x29, x29 // FP + // We need LR (x30) to return. The call to this helper already cleaned it. + // Don't replace SP; this needs special handling by the caller anyway. + ret +