From 3b76a6e60c4ca878a1d414cbbef873717e38ca8b Mon Sep 17 00:00:00 2001 From: Dapeng Gao Date: Fri, 21 Jun 2024 16:10:41 +0100 Subject: [PATCH] c18n: Expose new unwinding APIs Previously, libunwind hard-codes knowledge about the layout of the trusted frame and has read access to the trusted stack. This is fragile and insecure. Now, the task of extracting the relevant registers from the trusted stack is delegated to RTLD, and libunwind no longer has access to the trusted stack. In addition, unify the APIs exposed to setjmp/longjmp and libunwind. --- lib/libc/aarch64/gen/_setjmp.S | 4 +- lib/libc/aarch64/gen/setjmp.S | 9 +- lib/libgcc_s/Symbol-c18n.map | 8 +- libexec/rtld-elf/Symbol-c18n.map | 10 +-- libexec/rtld-elf/aarch64/rtld_c18n_machdep.h | 34 +------ libexec/rtld-elf/rtld_c18n.c | 93 +++++++++----------- libexec/rtld-elf/rtld_c18n.h | 46 +++++++++- libexec/rtld-elf/rtld_c18n_policy.txt | 14 +-- 8 files changed, 111 insertions(+), 107 deletions(-) diff --git a/lib/libc/aarch64/gen/_setjmp.S b/lib/libc/aarch64/gen/_setjmp.S index b0f9c97236d9..ce8a7fe0ad3b 100644 --- a/lib/libc/aarch64/gen/_setjmp.S +++ b/lib/libc/aarch64/gen/_setjmp.S @@ -63,7 +63,7 @@ ENTRY(_setjmp) /* * Tail-call to save Executive mode state */ - b _rtld_setjmp + b c18n_get_trusted_stk #else RETURN #endif @@ -112,7 +112,7 @@ ENTRY(_longjmp) /* * Tail-call to restore Executive mode state */ - b _rtld_longjmp + b c18n_unwind_trusted_stk #else RETURN #endif diff --git a/lib/libc/aarch64/gen/setjmp.S b/lib/libc/aarch64/gen/setjmp.S index aefc6bffc094..47fc275c527a 100644 --- a/lib/libc/aarch64/gen/setjmp.S +++ b/lib/libc/aarch64/gen/setjmp.S @@ -32,11 +32,6 @@ #include #include -#ifdef CHERI_LIB_C18N -.weak _rtld_setjmp -.weak _rtld_longjmp -#endif - ENTRY(setjmp) sub REGN(sp), REGN(sp), #(REG_WIDTH * 2) stp REG(0), REGN(lr), [REGN(sp)] @@ -78,7 +73,7 @@ ENTRY(setjmp) /* * Tail-call to save Executive mode state */ - b _rtld_setjmp + b c18n_get_trusted_stk #else RETURN #endif @@ -139,7 +134,7 @@ ENTRY(longjmp) /* * Tail-call to restore Executive mode state */ - b _rtld_longjmp + b c18n_unwind_trusted_stk #else RETURN #endif diff --git a/lib/libgcc_s/Symbol-c18n.map b/lib/libgcc_s/Symbol-c18n.map index 6cddda834561..d9aa2213192d 100644 --- a/lib/libgcc_s/Symbol-c18n.map +++ b/lib/libgcc_s/Symbol-c18n.map @@ -1,5 +1,7 @@ FBSDprivate_1.0 { - _rtld_unw_getcontext; - _rtld_unw_setcontext; - _rtld_unw_getsealer; + c18n_get_trusted_stk; + c18n_unwind_trusted_stk; + c18n_is_enabled; + c18n_is_tramp; + c18n_pop_trusted_stk; }; diff --git a/libexec/rtld-elf/Symbol-c18n.map b/libexec/rtld-elf/Symbol-c18n.map index 2081b927e800..2e7fe32e69b7 100644 --- a/libexec/rtld-elf/Symbol-c18n.map +++ b/libexec/rtld-elf/Symbol-c18n.map @@ -8,9 +8,9 @@ FBSDprivate_1.0 { _rtld_sighandler; _rtld_siginvoke; _rtld_sigaction; - _rtld_setjmp; - _rtld_longjmp; - _rtld_unw_getcontext; - _rtld_unw_setcontext; - _rtld_unw_getsealer; + c18n_get_trusted_stk; + c18n_unwind_trusted_stk; + c18n_is_enabled; + c18n_is_tramp; + c18n_pop_trusted_stk; }; diff --git a/libexec/rtld-elf/aarch64/rtld_c18n_machdep.h b/libexec/rtld-elf/aarch64/rtld_c18n_machdep.h index 5e2607f42f08..bd1330ea55c9 100644 --- a/libexec/rtld-elf/aarch64/rtld_c18n_machdep.h +++ b/libexec/rtld-elf/aarch64/rtld_c18n_machdep.h @@ -133,7 +133,7 @@ set_untrusted_stk(const void *sp) } #endif -struct trusted_frame { +struct compart_state { void *fp; void *pc; /* @@ -145,38 +145,6 @@ struct trusted_frame { * caller made the call. */ void *sp; - /* - * INVARIANT: This field contains the top of the caller's stack when the - * caller was last entered. - */ - void *osp; - /* - * Address of the previous trusted frame - */ - struct trusted_frame *previous; - /* - * Compartment ID of the caller - */ - stk_table_index caller; - /* - * Zeros - */ - uint16_t zeros; - /* - * Compartment ID of the callee - */ - stk_table_index callee; - /* - * Number of return value registers, encoded in enum tramp_ret_args - */ - uint8_t ret_args : 2; - uint16_t reserved : 14; - /* - * This field contains the code address in the trampoline that the - * callee should return to. This is used by trampolines to detect cross- - * compartment tail-calls. - */ - ptraddr_t landing; }; #endif #endif diff --git a/libexec/rtld-elf/rtld_c18n.c b/libexec/rtld-elf/rtld_c18n.c index 3c6a3c8cb3a0..82f1e48018ff 100644 --- a/libexec/rtld-elf/rtld_c18n.c +++ b/libexec/rtld-elf/rtld_c18n.c @@ -87,7 +87,7 @@ _Static_assert( TRUSTED_FRAME_SIZE * sizeof(uintptr_t) == sizeof(struct trusted_frame), "Unexpected struct trusted_frame size"); _Static_assert( - TRUSTED_FRAME_SP_OSP == offsetof(struct trusted_frame, sp), + TRUSTED_FRAME_SP_OSP == offsetof(struct trusted_frame, state.sp), "Unexpected struct trusted_frame member offset"); _Static_assert( TRUSTED_FRAME_PREV == offsetof(struct trusted_frame, previous), @@ -117,8 +117,7 @@ _Static_assert( * Sealers for RTLD privileged information */ static uintptr_t sealer_tcb; -static uintptr_t sealer_jmpbuf; -static uintptr_t sealer_unwbuf; +static uintptr_t sealer_trusted_stk; uintptr_t sealer_pltgot, sealer_tramp; @@ -859,9 +858,19 @@ resolve_untrusted_stk_impl(stk_table_index index) /* * Stack unwinding + * + * APIs exposed to stack unwinders (e.g., libc setjmp/longjmp and libunwind) */ -static void * -unwind_cursor(void) +uintptr_t c18n_get_trusted_stk(uintptr_t, struct trusted_frame **); +uintptr_t c18n_unwind_trusted_stk(uintptr_t, void *, struct trusted_frame *); + +bool c18n_is_enabled(void); +bool c18n_is_tramp(ptraddr_t, struct trusted_frame *); +struct trusted_frame *c18n_pop_trusted_stk(struct compart_state *, + struct trusted_frame *); + +uintptr_t +c18n_get_trusted_stk(uintptr_t ret, struct trusted_frame **buf) { /* * This helper is used by functions like setjmp. Before setjmp is @@ -875,30 +884,14 @@ unwind_cursor(void) * buffer. */ - return (get_trusted_stk()->previous); -} - -uintptr_t _rtld_setjmp(uintptr_t, void **); -uintptr_t _rtld_unw_getcontext(uintptr_t, void **); -uintptr_t _rtld_unw_getcontext_unsealed(uintptr_t, void **); - -uintptr_t -_rtld_setjmp(uintptr_t ret, void **buf) -{ - *buf = cheri_seal(unwind_cursor(), sealer_jmpbuf); - return (ret); -} - -uintptr_t -_rtld_unw_getcontext(uintptr_t ret, void **buf) -{ if (C18N_ENABLED) - *buf = cheri_seal(unwind_cursor(), sealer_unwbuf); + *buf = cheri_seal(get_trusted_stk()->previous, + sealer_trusted_stk); return (ret); } -static uintptr_t -unwind_stack(uintptr_t ret, void *rcsp, struct trusted_frame *target) +uintptr_t +c18n_unwind_trusted_stk(uintptr_t ret, void *rcsp, struct trusted_frame *target) { /* * This helper is used by functions like longjmp. Before longjmp is @@ -920,6 +913,9 @@ unwind_stack(uintptr_t ret, void *rcsp, struct trusted_frame *target) struct trusted_frame *cur, *tf; sigset_t nset, oset; + if (!C18N_ENABLED) + return (ret); + /* * Make the function re-entrant by blocking all signals. */ @@ -927,6 +923,7 @@ unwind_stack(uintptr_t ret, void *rcsp, struct trusted_frame *target) sigprocmask(SIG_SETMASK, &nset, &oset); tf = get_trusted_stk(); + target = cheri_unseal(target, sealer_trusted_stk); if (!cheri_is_subset(tf, target) || tf->previous >= target) { rtld_fdprintf(STDERR_FILENO, @@ -974,7 +971,7 @@ unwind_stack(uintptr_t ret, void *rcsp, struct trusted_frame *target) abort(); } - tf->sp = rcsp; + tf->state.sp = rcsp; tf->osp = *ospp; tf->previous = cur; tf->caller = index; @@ -984,28 +981,25 @@ unwind_stack(uintptr_t ret, void *rcsp, struct trusted_frame *target) return (ret); } -uintptr_t _rtld_longjmp(uintptr_t, void *, void *); -uintptr_t _rtld_unw_setcontext(uintptr_t, void *, void *); - -uintptr_t -_rtld_longjmp(uintptr_t ret, void *rcsp, void *csp) +bool +c18n_is_enabled(void) { - return (unwind_stack(ret, rcsp, cheri_unseal(csp, sealer_jmpbuf))); + return (C18N_ENABLED); } -uintptr_t -_rtld_unw_setcontext(uintptr_t ret, void *rcsp, void *csp) +bool +c18n_is_tramp(ptraddr_t pc, struct trusted_frame *tf) { - if (C18N_ENABLED) - ret = unwind_stack(ret, rcsp, cheri_unseal(csp, sealer_unwbuf)); - return (ret); + tf = cheri_unseal(tf, sealer_trusted_stk); + return (pc == tf->landing); } -uintptr_t _rtld_unw_getsealer(void); -uintptr_t -_rtld_unw_getsealer(void) +struct trusted_frame * +c18n_pop_trusted_stk(struct compart_state *state, struct trusted_frame *tf) { - return (sealer_unwbuf); + tf = cheri_unseal(tf, sealer_trusted_stk); + *state = tf->state; + return (cheri_seal(tf->previous, sealer_trusted_stk)); } /* @@ -1195,9 +1189,9 @@ tramp_hook_impl(int event, const struct tramp_header *hdr, memcpy(ut.sig, C18N_UTRACE_SIG, C18N_UTRACE_SIG_SZ); ut.event = event; ut.symnum = hdr->symnum; - ut.fp = tf->fp; - ut.pc = tf->pc; - ut.sp = tf->sp; + ut.fp = tf->state.fp; + ut.pc = tf->state.pc; + ut.sp = tf->state.sp; ut.osp = tf->osp; ut.previous = tf->previous; memcpy(&ut.fsig, &hdr->sig, sizeof(ut.fsig)); @@ -1616,10 +1610,7 @@ c18n_init2(void) sealer_tcb = cheri_setboundsexact(sealer, 1); sealer += 1; - sealer_jmpbuf = cheri_setboundsexact(sealer, 1); - sealer += 1; - - sealer_unwbuf = cheri_setboundsexact(sealer, 1); + sealer_trusted_stk = cheri_setboundsexact(sealer, 1); sealer += 1; sealer_tramp = cheri_setboundsexact(sealer, C18N_FUNC_SIG_COUNT); @@ -1942,7 +1933,9 @@ _rtld_sighandler_impl(int sig, siginfo_t *info, ucontext_t *ucp, void *nsp) */ ntf = tf - 2; *ntf = (struct trusted_frame) { - .sp = nsp, + .state = (struct compart_state) { + .sp = nsp + }, .osp = osp, .previous = tf, .caller = intr_idx, @@ -1997,7 +1990,7 @@ _rtld_sighandler_impl(int sig, siginfo_t *info, ucontext_t *ucp, void *nsp) * compartment. */ #ifndef __ARM_MORELLO_PURECAP_BENCHMARK_ABI - set_untrusted_stk(ntf->sp); + set_untrusted_stk(ntf->state.sp); #endif } diff --git a/libexec/rtld-elf/rtld_c18n.h b/libexec/rtld-elf/rtld_c18n.h index 9ebe627f98f5..e3aefcd3cdde 100644 --- a/libexec/rtld-elf/rtld_c18n.h +++ b/libexec/rtld-elf/rtld_c18n.h @@ -120,6 +120,48 @@ struct stk_table { #include "rtld_c18n_machdep.h" +struct trusted_frame { + /* + * Architecture-specific callee-saved registers, including fp, sp, and + * the return address + */ + struct compart_state state; + /* + * INVARIANT: This field contains the top of the caller's stack when the + * caller was last entered. + */ + void *osp; + /* + * Address of the previous trusted frame + */ + struct trusted_frame *previous; + /* + * Compartment ID of the caller + */ + stk_table_index caller; + /* + * This padding space must be filled with zeros so that an optimised + * trampoline can use a wide load to load multiple fields of the trusted + * frame and then use a word-sized register to extract the caller field. + */ + uint16_t zeros; + /* + * Compartment ID of the callee + */ + stk_table_index callee; + /* + * Number of return value registers, encoded in enum tramp_ret_args + */ + uint8_t ret_args : 2; + uint16_t reserved : 14; + /* + * This field contains the code address in the trampoline that the + * callee should return to. This is used by trampolines to detect cross- + * compartment tail-calls. + */ + ptraddr_t landing; +}; + struct tcb *c18n_allocate_tcb(struct tcb *); void c18n_free_tcb(void); @@ -217,8 +259,8 @@ func_sig_legal(struct func_sig sig) /* * This macro can only be used in a function directly invoked by a trampoline. */ -#define c18n_return_address() \ - (C18N_ENABLED ? get_trusted_stk()->pc : __builtin_return_address(0)) +#define c18n_return_address() (C18N_ENABLED ? \ + get_trusted_stk()->state.pc : __builtin_return_address(0)) void *_rtld_sandbox_code(void *, struct func_sig); void *_rtld_safebox_code(void *, struct func_sig); diff --git a/libexec/rtld-elf/rtld_c18n_policy.txt b/libexec/rtld-elf/rtld_c18n_policy.txt index d373837982ab..a3840600ddd3 100644 --- a/libexec/rtld-elf/rtld_c18n_policy.txt +++ b/libexec/rtld-elf/rtld_c18n_policy.txt @@ -56,11 +56,15 @@ export to [TCB] _rtld_sighandler _rtld_siginvoke _rtld_sigaction - _rtld_setjmp - _rtld_longjmp + +callee [RTLD] +export to [TCB] +export to [libunwind] + c18n_get_trusted_stk + c18n_unwind_trusted_stk callee [RTLD] export to [libunwind] - _rtld_unw_getcontext - _rtld_unw_setcontext - _rtld_unw_getsealer + c18n_is_enabled + c18n_is_tramp + c18n_pop_trusted_stk