Skip to content

Commit

Permalink
c18n: Save caller's stack pointer in trusted frame
Browse files Browse the repository at this point in the history
Previously, the caller's stack pointer is only saved at the bottom of
the caller's stack during domain transition. This means that the act of
unwinding a trusted frame relies on external state, namely the value at
the bottom of the caller's stack.

We now also save the caller's stack pointer in the trusted frame so that
unwinding can be stateless, i.e., inspecting the content of the trusted
frame alone is sufficient for restoring the stack pointer of the caller.
  • Loading branch information
dpgao committed Mar 20, 2024
1 parent b39d77d commit 9adf052
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 36 deletions.
67 changes: 37 additions & 30 deletions libexec/rtld-elf/aarch64/rtld_c18n_asm.S
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
*/

#include <machine/asm.h>
#define IN_ASM
#include "rtld_c18n_machdep.h"
#undef IN_ASM

ENTRY(_rtld_setjmp)
#ifdef __ARM_MORELLO_PURECAP_BENCHMARK_ABI
Expand Down Expand Up @@ -345,7 +348,7 @@ TRAMP(tramp_save_caller)
*/
gclim x11, c10
scvalue c18, c10, x11
ldr c17, [c18, #-CAP_WIDTH]
ldr x17, [c18, #-CAP_WIDTH]
str c10, [c18, #-CAP_WIDTH]

#ifdef __ARM_MORELLO_PURECAP_BENCHMARK_ABI
Expand All @@ -364,14 +367,15 @@ TRAMP(tramp_save_caller)
2: add x13, x13, #0 /* To be patched at runtime */

/* Push frame */
stp c29, c30, [TRUSTED_STACK, #-(CAP_WIDTH * 14)]!
stp c29, c30, [TRUSTED_STACK, #-(CAP_WIDTH * C18N_TRUSTED_FRAME_SIZE)]!
stp x12, x13, [TRUSTED_STACK, #(CAP_WIDTH * 2)]
stp c17, c19, [TRUSTED_STACK, #(CAP_WIDTH * 3)]
stp c20, c21, [TRUSTED_STACK, #(CAP_WIDTH * 5)]
stp c22, c23, [TRUSTED_STACK, #(CAP_WIDTH * 7)]
stp c24, c25, [TRUSTED_STACK, #(CAP_WIDTH * 9)]
stp c26, c27, [TRUSTED_STACK, #(CAP_WIDTH * 11)]
str c28, [TRUSTED_STACK, #(CAP_WIDTH * 13)]
str c10, [TRUSTED_STACK, #(CAP_WIDTH * 3)]
stp x17, x12, [TRUSTED_STACK, #(CAP_WIDTH * 4)]
stp c19, c20, [TRUSTED_STACK, #(CAP_WIDTH * 5)]
stp c21, c22, [TRUSTED_STACK, #(CAP_WIDTH * 7)]
stp c23, c24, [TRUSTED_STACK, #(CAP_WIDTH * 9)]
stp c25, c26, [TRUSTED_STACK, #(CAP_WIDTH * 11)]
stp c27, c28, [TRUSTED_STACK, #(CAP_WIDTH * 13)]
#ifdef __ARM_MORELLO_PURECAP_BENCHMARK_ABI
msr rcsp_el0, TRUSTED_STACK
#endif
Expand Down Expand Up @@ -435,12 +439,12 @@ TRAMP(tramp_switch_stack)
/*
* If the stack table index is out-of-bounds, set it to zero.
*/
csel w17, w14, wzr, hi
csel w26, w14, wzr, hi
/*
* Load the callee's stack if the stack table index is within bounds.
* Otherwise the resolver will be loaded.
*/
ldr c20, [c30, w17, uxtw #4]
ldr c20, [c30, w26, uxtw #4]
/*
* If the resolver has been loaded, set the branch target to it.
*/
Expand Down Expand Up @@ -529,8 +533,8 @@ TRAMP(tramp_invoke_res)
mov x23, xzr
mov x24, xzr
mov x25, xzr
mov x26, xzr
/*
* - c26: Stack table index (scalar)
* - c27: Test result (scalar)
* - c28: Permission bits (scalar)
* - c29: Frame pointer (scalar)
Expand All @@ -539,13 +543,13 @@ TRAMP(tramp_invoke_res)
/*
* Clear temporary registers, except
* - c10: Callee's stack
* - c11: Top of caller's stack (scalar)
* - c12: Link to previous frame (scalar)
* - c13: Number of unused return argument registers (scalar)
* - c11: Limit of caller's stack (scalar)
* - c12: Old trusted stack (scalar)
* - c13: Cookie and number of unused return argument registers (scalar)
* - c14: Callee's compartment ID (scalar)
* - c15: Length of stack table (scalar)
* - c16: Comparison result (scalar)
* - c17: Stack table index (scalar)
* - c17: Old bottom of caller's stack (scalar)
* - c18: CHERI_PERM_EXECUTE (scalar)
*/

Expand All @@ -563,20 +567,20 @@ TRAMP(tramp_pop_frame)
/* Restore callee-saved registers */
ldp c29, c30, [TRUSTED_STACK]
ldp x10, x11, [TRUSTED_STACK, #(CAP_WIDTH * 2)]
ldp c12, c19, [TRUSTED_STACK, #(CAP_WIDTH * 3)]
ldp c20, c21, [TRUSTED_STACK, #(CAP_WIDTH * 5)]
ldp c22, c23, [TRUSTED_STACK, #(CAP_WIDTH * 7)]
ldp c24, c25, [TRUSTED_STACK, #(CAP_WIDTH * 9)]
ldp c26, c27, [TRUSTED_STACK, #(CAP_WIDTH * 11)]
ldr c28, [TRUSTED_STACK, #(CAP_WIDTH * 13)]
ldp c15, c12, [TRUSTED_STACK, #(CAP_WIDTH * 3)]
ldp c19, c20, [TRUSTED_STACK, #(CAP_WIDTH * 5)]
ldp c21, c22, [TRUSTED_STACK, #(CAP_WIDTH * 7)]
ldp c23, c24, [TRUSTED_STACK, #(CAP_WIDTH * 9)]
ldp c25, c26, [TRUSTED_STACK, #(CAP_WIDTH * 11)]
ldp c27, c28, [TRUSTED_STACK, #(CAP_WIDTH * 13)]

/*
* Restore caller's saved rcsp.
*/
gclim x13, c12
scvalue c14, c12, x13
ldr c15, [c14, #-CAP_WIDTH]
str c12, [c14, #-CAP_WIDTH]
gclim x13, c15
scvalue c14, c15, x13
scvalue c2, c15, x12
str c2, [c14, #-CAP_WIDTH]

/*
* Clear unused return value registers. The registers to clear is
Expand All @@ -598,7 +602,10 @@ TRAMP(tramp_pop_frame)
msr rcsp_el0, TRUSTED_STACK
#endif

mov x2, xzr
/*
* Clear temporary registers, except
* - c2: Old bottom of caller's stack
*/
mov x3, xzr
mov x4, xzr
mov x5, xzr
Expand All @@ -610,10 +617,10 @@ TRAMP(tramp_pop_frame)
/*
* Clear temporary registers, except
* - c10: Link to previous frame (scalar)
* - c11: Number of unused return argument registers (scalar)
* - c12: Old top of caller's stack
* - c13: Bottom of caller's stack (scalar)
* - c14: Bottom of caller's stack
* - c11: Cookie and number of unused return argument registers (scalar)
* - c12: Old bottom of caller's stack (scalar)
* - c13: Limit of caller's stack (scalar)
* - c14: Limit of caller's stack
* - c15: Current top of caller's stack
* - c16: Logical operation result (scalar)
* - c17: Comparison result (scalar)
Expand Down
35 changes: 33 additions & 2 deletions libexec/rtld-elf/aarch64/rtld_c18n_machdep.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,51 @@
#ifndef RTLD_C18N_MACHDEP_H
#define RTLD_C18N_MACHDEP_H

#define C18N_TRUSTED_FRAME_SIZE 15

#ifndef IN_ASM
/*
* Stack unwinding
*/
struct trusted_frame {
void *fp;
void *pc;
/*
* Address of the next trusted frame
*/
ptraddr_t next;
/*
* Number of return value registers, encoded in enum tramp_ret_args
*/
uint8_t ret_args : 2;
/*
* This field contains the code address in the trampoline that the
* callee should return to. This is only used by unwinders to detect
* compartment boundaries.
*/
ptraddr_t cookie : 62;
/*
* INVARIANT: This field contains the top of the caller's stack when the
* caller made the call.
*/
void *n_sp;
/*
* INVARIANT: This field contains the top of the caller's stack when the
* caller was last entered.
*/
void *o_sp;
ptraddr_t o_sp;
/*
* This field contains the address of the trusted stack before the
* current frame was pushed. It is only used by unwinders.
*/
ptraddr_t csp;
/*
* c19 to c28
*/
void *regs[10];
};

_Static_assert(
sizeof(struct trusted_frame) == sizeof(uintptr_t) * C18N_TRUSTED_FRAME_SIZE,
"Unexpected struct trusted_frame size");
#endif
#endif
8 changes: 4 additions & 4 deletions libexec/rtld-elf/rtld_c18n.c
Original file line number Diff line number Diff line change
Expand Up @@ -567,9 +567,9 @@ _rtld_longjmp_impl(uintptr_t ret, void **buf, struct trusted_frame *csp,
* Unwind each frame before the target frame.
*/
do {
stk = cheri_setoffset(cur->o_sp, cheri_getlen(cur->o_sp));
stk = cheri_setoffset(cur->n_sp, cheri_getlen(cur->n_sp));
--stk;
stk->top = cur->o_sp;
stk->top = cheri_setaddress(cur->n_sp, cur->o_sp);
cur = cheri_setaddress(cur, cur->next);
} while (cur < target);

Expand All @@ -587,8 +587,8 @@ _rtld_longjmp_impl(uintptr_t ret, void **buf, struct trusted_frame *csp,
*/
stk = cheri_setoffset(rcsp, cheri_getlen(rcsp));
--stk;
csp->o_sp = stk->top;
stk->top = rcsp;
csp->n_sp = rcsp;
csp->o_sp = (ptraddr_t)stk->top;

return ((struct jmp_args) { .ret = ret });
}
Expand Down

0 comments on commit 9adf052

Please sign in to comment.