From 50d1cde3645308702bfd209313055d45d5b64818 Mon Sep 17 00:00:00 2001 From: Andrei Lascu Date: Thu, 13 Jan 2022 10:59:12 +0000 Subject: [PATCH] Fix `switch_compartment` PCC bounds A hardcoded value of the bounds over `switch_compartment` meant that the bounds were much wider than expected. This meant two things: - the `clean` function called within `switch_compartment` was reachable and executable; - when deriving the `clr` within `switch_compartment`, we would derive with the larger bound, and return to a compartment with much more executable permissions than intended. We now tightly bind the PCC to `switch_compartment`. This means moving `clean` within these bounds (a better way would be to create capabilities allowing this function to be called, and provide local copies to each compartment, but that is beyond the scope of this example, and an exercise in engineering), and not deriving `clr` within `switch_compartment`, but just retaining the provided `clr`. Additional small cleanups, such as code-base splitting, and comment-fixing. --- .../secure-try_deref/Makefile.morello-hybrid | 2 +- .../secure-try_deref/compartments-try_deref.s | 31 +++ .../inter_comp_call/secure-try_deref/main.c | 10 +- .../inter_comp_call/secure-try_deref/shared.S | 29 +++ ...hared_try_deref.S => switch_compartment.s} | 68 +------ .../inter_comp_call/secure/compartments.s | 38 ++++ .../inter_comp_call/secure/main.c | 10 +- .../inter_comp_call/secure/shared.S | 181 +----------------- .../secure/switch_compartment.s | 125 ++++++++++++ 9 files changed, 245 insertions(+), 249 deletions(-) create mode 100644 hybrid/compartment_examples/inter_comp_call/secure-try_deref/compartments-try_deref.s create mode 100644 hybrid/compartment_examples/inter_comp_call/secure-try_deref/shared.S rename hybrid/compartment_examples/inter_comp_call/secure-try_deref/{shared_try_deref.S => switch_compartment.s} (69%) create mode 100644 hybrid/compartment_examples/inter_comp_call/secure/compartments.s create mode 100644 hybrid/compartment_examples/inter_comp_call/secure/switch_compartment.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 index b8e60df..79b20f2 100644 --- 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 @@ -1,7 +1,7 @@ # Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors. # SPDX-License-Identifier: MIT OR Apache-2.0 -SHARED_SOURCES := shared_try_deref.s +SHARED_SOURCES := shared.S CFILES := $(wildcard *.c) ROOTDIR=../../../.. diff --git a/hybrid/compartment_examples/inter_comp_call/secure-try_deref/compartments-try_deref.s b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/compartments-try_deref.s new file mode 100644 index 0000000..2d86023 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/compartments-try_deref.s @@ -0,0 +1,31 @@ +/* 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`. + */ +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: 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 index 12475c6..0e20bb7 100644 --- a/hybrid/compartment_examples/inter_comp_call/secure-try_deref/main.c +++ b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/main.c @@ -61,8 +61,9 @@ 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; +extern void comp_f_fn_end(); +extern void comp_g_fn_end(); +extern void switch_compartment_end(); /******************************************************************************* * Types & Consts @@ -133,7 +134,8 @@ struct comp comps[COMP_COUNT]; void init_comps() { void *__capability switch_cap = (void *__capability) switch_compartment; - switch_cap = cheri_bounds_set(switch_cap, 80 * 4); + size_t switcher_size = (uintptr_t) switch_compartment_end - (uintptr_t) switch_compartment; + switch_cap = cheri_bounds_set(switch_cap, switcher_size); switcher_caps[1] = switch_cap; void *__capability comps_addr = (void *__capability) &comps; @@ -173,8 +175,6 @@ void add_comp(uint8_t *_start_addr, void (*_comp_fn)(), void *_comp_fn_end) // 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; diff --git a/hybrid/compartment_examples/inter_comp_call/secure-try_deref/shared.S b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/shared.S new file mode 100644 index 0000000..5036672 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/shared.S @@ -0,0 +1,29 @@ +// Copyright (c) 2021 The CapableVMs "CHERI Examples" Contributors. +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#include "main.h" + +// Compartment functions +.global comp_f_fn +.global comp_g_fn + +// Labels for size computations +.global comp_f_fn_end +.global comp_g_fn_end +.global switch_compartment_end + +.text +.balign 4 + +.global executive_switch +.type executive_switch, "function" +executive_switch: + mov c29, c0 + mov x0, #0 + cvtp clr, lr + b switch_compartment + ret clr + +#include "switch_compartment.s" + +#include "compartments-try_deref.s" 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/switch_compartment.s similarity index 69% rename from hybrid/compartment_examples/inter_comp_call/secure-try_deref/shared_try_deref.S rename to hybrid/compartment_examples/inter_comp_call/secure-try_deref/switch_compartment.s index 6e37606..a9557b9 100644 --- a/hybrid/compartment_examples/inter_comp_call/secure-try_deref/shared_try_deref.S +++ b/hybrid/compartment_examples/inter_comp_call/secure-try_deref/switch_compartment.s @@ -1,23 +1,3 @@ -// 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 @@ -52,14 +32,11 @@ switch_compartment: 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]! + // Save old DDC (c2), old SP (x12), old LR (clr) on stack + stp c2, clr, [sp, #-48]! str x12, [sp, #32] // Stack layout at this point: @@ -95,46 +72,6 @@ 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: - // 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. @@ -184,4 +121,5 @@ clean: // 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 +switch_compartment_end: diff --git a/hybrid/compartment_examples/inter_comp_call/secure/compartments.s b/hybrid/compartment_examples/inter_comp_call/secure/compartments.s new file mode 100644 index 0000000..f482ac4 --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure/compartments.s @@ -0,0 +1,38 @@ +/* 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: diff --git a/hybrid/compartment_examples/inter_comp_call/secure/main.c b/hybrid/compartment_examples/inter_comp_call/secure/main.c index 12475c6..0e20bb7 100644 --- a/hybrid/compartment_examples/inter_comp_call/secure/main.c +++ b/hybrid/compartment_examples/inter_comp_call/secure/main.c @@ -61,8 +61,9 @@ 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; +extern void comp_f_fn_end(); +extern void comp_g_fn_end(); +extern void switch_compartment_end(); /******************************************************************************* * Types & Consts @@ -133,7 +134,8 @@ struct comp comps[COMP_COUNT]; void init_comps() { void *__capability switch_cap = (void *__capability) switch_compartment; - switch_cap = cheri_bounds_set(switch_cap, 80 * 4); + size_t switcher_size = (uintptr_t) switch_compartment_end - (uintptr_t) switch_compartment; + switch_cap = cheri_bounds_set(switch_cap, switcher_size); switcher_caps[1] = switch_cap; void *__capability comps_addr = (void *__capability) &comps; @@ -173,8 +175,6 @@ void add_comp(uint8_t *_start_addr, void (*_comp_fn)(), void *_comp_fn_end) // 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; diff --git a/hybrid/compartment_examples/inter_comp_call/secure/shared.S b/hybrid/compartment_examples/inter_comp_call/secure/shared.S index a863750..da7dd6b 100644 --- a/hybrid/compartment_examples/inter_comp_call/secure/shared.S +++ b/hybrid/compartment_examples/inter_comp_call/secure/shared.S @@ -3,10 +3,14 @@ #include "main.h" +// Compartment functions .global comp_f_fn .global comp_g_fn + +// Labels for size computations .global comp_f_fn_end .global comp_g_fn_end +.global switch_compartment_end .text .balign 4 @@ -16,179 +20,10 @@ executive_switch: mov c29, c0 mov x0, #0 + cvtp clr, lr 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: + 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 +#include "switch_compartment.s" +#include "compartments.s" diff --git a/hybrid/compartment_examples/inter_comp_call/secure/switch_compartment.s b/hybrid/compartment_examples/inter_comp_call/secure/switch_compartment.s new file mode 100644 index 0000000..5db007a --- /dev/null +++ b/hybrid/compartment_examples/inter_comp_call/secure/switch_compartment.s @@ -0,0 +1,125 @@ +/* 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 + + // Install compartment DDC + msr DDC, c1 + + // Save old DDC (c2), old SP (x12), old CLR (clr) on stack + stp c2, clr, [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 + + // 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 +switch_compartment_end: +