From 40def73a3d58da16ec7ac3a24d9fa406b0c0e7b4 Mon Sep 17 00:00:00 2001 From: Dapeng Gao Date: Fri, 1 Mar 2024 09:33:45 +0000 Subject: [PATCH] c18n: Full support for signal handling The signal handling mechanism is completely overhauled. RTLD now catches all signals. It then forwards the signal to libthr (if it is linked), which in turn forwards the signal to RTLD again. Here, RTLD determines the stack that the signal handler should run on. It copies the sigframe to the destination stack and then finally invoke the signal handler. longjmping out of a signal handler is now supported. --- lib/libc/Symbol-c18n.map | 3 + lib/libc/gen/dlfcn.c | 7 + lib/libc/sys/interposing_table.c | 4 + lib/libc/sys/sigaction.c | 45 ++++++ lib/libthr/thread/thr_sig.c | 17 +++ libexec/rtld-elf/Symbol-c18n.map | 3 + libexec/rtld-elf/aarch64/rtld_c18n_asm.S | 80 ++++++++++ libexec/rtld-elf/rtld_c18n.c | 178 ++++++++++++++++++++++- share/man/man7/compartmentalization.7 | 4 - 9 files changed, 329 insertions(+), 12 deletions(-) diff --git a/lib/libc/Symbol-c18n.map b/lib/libc/Symbol-c18n.map index ab637e996e87..c0f3f4ea314f 100644 --- a/lib/libc/Symbol-c18n.map +++ b/lib/libc/Symbol-c18n.map @@ -3,4 +3,7 @@ FBSDprivate_1.0 { _rtld_longjmp; _rtld_thread_start_init; _rtld_sighandler_init; + _rtld_dispatch_signal; + _rtld_sigaction_begin; + _rtld_sigaction_end; }; diff --git a/lib/libc/gen/dlfcn.c b/lib/libc/gen/dlfcn.c index 058739e56877..0c70fc2eb461 100644 --- a/lib/libc/gen/dlfcn.c +++ b/lib/libc/gen/dlfcn.c @@ -207,6 +207,13 @@ void _rtld_sighandler_init(void (*p)(int, siginfo_t *, void *) __unused) { } + +#pragma weak _rtld_dispatch_signal +void +_rtld_dispatch_signal(int sig __unused, siginfo_t *info __unused, + void *_ucp __unused) +{ +} #endif #ifndef IN_LIBDL diff --git a/lib/libc/sys/interposing_table.c b/lib/libc/sys/interposing_table.c index 20bfd6bf53a0..d578342f86d8 100644 --- a/lib/libc/sys/interposing_table.c +++ b/lib/libc/sys/interposing_table.c @@ -64,7 +64,11 @@ interpos_func_t __libc_interposing[INTERPOS_MAX] = { SLOT_SYS(sendmsg) SLOT_SYS(sendto) SLOT_SYS(setcontext) +#if defined(__CHERI_PURE_CAPABILITY__) && defined(RTLD_SANDBOX) + SLOT_LIBC(sigaction) +#else SLOT_SYS(sigaction) +#endif SLOT_SYS(sigprocmask) SLOT_SYS(sigsuspend) SLOT_LIBC(sigwait) diff --git a/lib/libc/sys/sigaction.c b/lib/libc/sys/sigaction.c index 27f003e0eb48..6ded30e6e792 100644 --- a/lib/libc/sys/sigaction.c +++ b/lib/libc/sys/sigaction.c @@ -34,7 +34,52 @@ #include "libc_private.h" __weak_reference(__sys_sigaction, __sigaction); +#if defined(__CHERI_PURE_CAPABILITY__) && defined(RTLD_SANDBOX) +#pragma weak _rtld_sighandler +void _rtld_sighandler(int sig __unused, siginfo_t *info __unused, + void *_ucp __unused) +{ +} + +#pragma weak _rtld_sigaction_begin +void *_rtld_sigaction_begin(int sig __unused, struct sigaction *act __unused) +{ + return (0); +} + +#pragma weak _rtld_sigaction_end +void _rtld_sigaction_end(int sig __unused, void *context __unused, + const struct sigaction *act __unused, struct sigaction *oldact __unused) +{ +} + +int +__libc_sigaction(int sig, const struct sigaction *act, struct sigaction *oact) +{ + int ret; + void *context = 0; + struct sigaction newact; + const struct sigaction *newactp = act; + + if (act && + act->sa_handler != SIG_DFL && act->sa_handler != SIG_IGN) { + newact = *act; + newactp = &newact; + + context = _rtld_sigaction_begin(sig, &newact); + newact.sa_sigaction = _rtld_sighandler; + } + + ret = __sys_sigaction(sig, newactp, oact); + + if (ret == 0) + _rtld_sigaction_end(sig, context, act, oact); + + return (ret); +} +#else __weak_reference(sigaction, __libc_sigaction); +#endif #pragma weak sigaction int diff --git a/lib/libthr/thread/thr_sig.c b/lib/libthr/thread/thr_sig.c index 94a0bbba4aba..e68d81c6ba34 100644 --- a/lib/libthr/thread/thr_sig.c +++ b/lib/libthr/thread/thr_sig.c @@ -75,6 +75,11 @@ __weak_reference(thr_sighandler, _thr_sighandler); void _thr_sighandler(int, siginfo_t *, void *); __weak_reference(thr_sighandler, _rtld_sighandler); void _rtld_sighandler(int, siginfo_t *, void *); + +void _rtld_dispatch_signal(int, siginfo_t *, void *); +void *_rtld_sigaction_begin(int, struct sigaction *); +void _rtld_sigaction_end(int, void *, const struct sigaction *, + struct sigaction *); #endif int _sigtimedwait(const sigset_t *set, siginfo_t *info, @@ -598,6 +603,9 @@ __thr_sigaction(int sig, const struct sigaction *act, struct sigaction *oact) sigset_t oldset; struct usigaction *usa; int ret, err; +#if defined(__CHERI_PURE_CAPABILITY__) && defined(RTLD_SANDBOX) + void *context = NULL; +#endif if (!_SIG_VALID(sig) || sig == SIGCANCEL) { errno = EINVAL; @@ -624,6 +632,10 @@ __thr_sigaction(int sig, const struct sigaction *act, struct sigaction *oact) */ if (newact.sa_handler != SIG_DFL && newact.sa_handler != SIG_IGN) { +#if defined(__CHERI_PURE_CAPABILITY__) && defined(RTLD_SANDBOX) + context = _rtld_sigaction_begin(sig, &newact); + newact.sa_sigaction = _rtld_dispatch_signal; +#endif usa->sigact = newact; remove_thr_signals(&usa->sigact.sa_mask); newact.sa_flags &= ~SA_NODEFER; @@ -652,6 +664,11 @@ __thr_sigaction(int sig, const struct sigaction *act, struct sigaction *oact) oldact = usa->sigact; } +#if defined(__CHERI_PURE_CAPABILITY__) && defined(RTLD_SANDBOX) + if (ret == 0) + _rtld_sigaction_end(sig, context, act, &oldact); +#endif + _thr_rwl_unlock(&usa->lock); __sys_sigprocmask(SIG_SETMASK, &oldset, NULL); diff --git a/libexec/rtld-elf/Symbol-c18n.map b/libexec/rtld-elf/Symbol-c18n.map index fe3122bc8caa..eb4ed2e15734 100644 --- a/libexec/rtld-elf/Symbol-c18n.map +++ b/libexec/rtld-elf/Symbol-c18n.map @@ -2,6 +2,9 @@ FBSDprivate_1.0 { _rtld_thread_start_init; _rtld_thread_start; _rtld_thr_exit; + _rtld_dispatch_signal; + _rtld_sigaction_begin; + _rtld_sigaction_end; _rtld_sighandler_init; _rtld_sighandler; _rtld_setjmp; diff --git a/libexec/rtld-elf/aarch64/rtld_c18n_asm.S b/libexec/rtld-elf/aarch64/rtld_c18n_asm.S index d59b800aeeaa..80a4a620baa8 100644 --- a/libexec/rtld-elf/aarch64/rtld_c18n_asm.S +++ b/libexec/rtld-elf/aarch64/rtld_c18n_asm.S @@ -71,6 +71,86 @@ ENTRY(_rtld_thread_start) b _rtld_thread_start_impl END(_rtld_thread_start) +/* + * void _rtld_sighandler(int, siginfo_t *, void *); + * + * This function clobbers some callee-saved registers. This is fine because it + * is only ever invoked via a trampoline by the kernel when a signal is + * delivered. + */ +ENTRY(_rtld_sighandler) +#ifndef __ARM_MORELLO_PURECAP_BENCHMARK_ABI + /* + * The sigframe is pushed onto the trusted stack, disrupting the linked- + * list. Repair the link by pointing it to the pre-signal top of the + * trusted stack. + */ + mov c19, c30 + mov c20, c0 + mov c21, c1 + mov c22, c2 + + mov c0, csp + mov c1, c2 + bl sighandler_fix_link + mov x23, x0 + + mov c0, c20 + mov c1, c21 + mov c2, c22 +#endif + + ldr c3, signal_dispatcher +#ifdef __ARM_MORELLO_PURECAP_BENCHMARK_ABI + br x3 +#else + blr c3 + + /* + * Restore the link to its original value. + */ + mov c0, csp + mov x1, x23 + mov c30, c19 + b sighandler_unfix_link +#endif +END(_rtld_sighandler) + +/* + * void _rtld_dispatch_signal(int, siginfo_t *, void *); + * + * This function clobbers some callee-saved registers. This is fine because it + * is only ever invoked by either RTLD code that is aware of this behaviour or + * external code via a trampoline. + */ +ENTRY(_rtld_dispatch_signal) + mov c24, c30 + mov w25, w0 + mov c26, c1 + mov c27, c2 + bl dispatch_signal_get + mov c28, c0 + + mov c1, c26 + mov c2, c27 + bl dispatch_signal_begin + mov c26, c1 + + mov c2, c1 + mov c1, c0 + mov w0, w25 +#ifdef __ARM_MORELLO_PURECAP_BENCHMARK_ABI + blr x28 +#else + blr c28 +#endif + + mov c0, c26 + mov c1, c27 + mov c30, c24 + b dispatch_signal_end +END(_rtld_dispatch_signal) + ENTRY(allocate_rstk) /* * NON-STANDARD CALLING CONVENTION diff --git a/libexec/rtld-elf/rtld_c18n.c b/libexec/rtld-elf/rtld_c18n.c index 560b334f236e..5a25e69b5498 100644 --- a/libexec/rtld-elf/rtld_c18n.c +++ b/libexec/rtld-elf/rtld_c18n.c @@ -1245,14 +1245,110 @@ _rtld_thr_exit(long *state) __sys_thr_exit(state); } -static void (*thr_sighandler)(int, siginfo_t *, void *); +/* + * Signal support + */ +void _rtld_dispatch_signal(int, siginfo_t *, void *); + +#ifndef __ARM_MORELLO_PURECAP_BENCHMARK_ABI +ptraddr_t sighandler_fix_link(struct trusted_frame *, ucontext_t *); + +ptraddr_t +sighandler_fix_link(struct trusted_frame *csp, ucontext_t *ucp) +{ + ptraddr_t ret = csp->next; + + csp->next = ucp->uc_mcontext.mc_capregs.cap_sp; + + return (ret); +} + +void sighandler_unfix_link(struct trusted_frame *, ptraddr_t); + +void sighandler_unfix_link(struct trusted_frame *csp, ptraddr_t link) +{ + csp->next = link; +} +#endif + +static struct sigaction_map { + void *o_handler; + __siginfohandler_t *n_handler; +} sigaction_map[_SIG_MAXSIG]; + +struct dispatch_signal_ret { + siginfo_t *info; + ucontext_t *ucp; +}; + +__siginfohandler_t *dispatch_signal_get(int); + +__siginfohandler_t * +dispatch_signal_get(int sig) +{ + return (sigaction_map[sig - 1].n_handler); +} + +struct dispatch_signal_ret dispatch_signal_begin(__siginfohandler_t, + siginfo_t *, void *); + +struct dispatch_signal_ret +dispatch_signal_begin(__siginfohandler_t sigfunc, siginfo_t *info, + void *_ucp) +{ + compart_id_t cid = tramp_reflect(sigfunc)->defobj->compart_id; + struct stk_table *table = stk_table_get(); + struct stk_table_stack stack; + ucontext_t *ucp = _ucp; + char **stk_bot; + void *stk_top; + + stack = table->stacks[compart_id_to_stack_index(cid)]; + + if (stack.size == 0) + stk_bot = allocate_rstk_impl(compart_id_to_index(cid)); + else + stk_bot = stack.bottom; + + stk_top = stk_bot[-1]; + + stk_bot[-1] -= sizeof(*ucp); + ucp = memcpy(stk_bot[-1], ucp, sizeof(*ucp)); + + stk_bot[-1] -= sizeof(*info); + info = memcpy(stk_bot[-1], info, sizeof(*info)); + + ucp->uc_mcontext.mc_capregs.cap_sp = (uintptr_t)stk_top; + + return (struct dispatch_signal_ret) { + .info = info, + .ucp = ucp + }; +} + +void dispatch_signal_end(ucontext_t *, ucontext_t *); + +void +dispatch_signal_end(ucontext_t *new, ucontext_t *old __unused) +{ + void *top = (void **)new->uc_mcontext.mc_capregs.cap_sp; + void **bot = cheri_setoffset(top, cheri_getlen(top)); + + memset(new, 0, sizeof(*new)); + + bot[-1] = top; +} + +extern void (*signal_dispatcher)(int, siginfo_t *, void *); + +void (*signal_dispatcher)(int, siginfo_t *, void *) = _rtld_dispatch_signal; void _rtld_sighandler_init(void (*p)(int, siginfo_t *, void *)) { - assert((cheri_getperm(p) & CHERI_PERM_EXECUTIVE) == 0); - assert(thr_sighandler == NULL); - thr_sighandler = tramp_intern(NULL, &(struct tramp_data) { + assert(signal_dispatcher == _rtld_dispatch_signal && + (cheri_getperm(p) & CHERI_PERM_EXECUTIVE) == 0); + signal_dispatcher = tramp_intern(NULL, &(struct tramp_data) { .target = p, .defobj = obj_from_addr(p), .sig = (struct func_sig) { @@ -1262,10 +1358,76 @@ _rtld_sighandler_init(void (*p)(int, siginfo_t *, void *)) }); } -void _rtld_sighandler(int, siginfo_t *, void *); +void *_rtld_sigaction_begin(int, struct sigaction *); -void -_rtld_sighandler(int sig, siginfo_t *info, void *_ucp) +void * +_rtld_sigaction_begin(int sig, struct sigaction *act) +{ + struct func_sig fsig; + void *context = act->sa_sigaction; + struct tramp_header *header = tramp_reflect(context); + const Obj_Entry *defobj; + + /* + * If the signal handler is not already wrapped by a trampoline, wrap it + * in one. + */ + if (header == NULL) { + defobj = obj_from_addr(context); + + /* + * If SA_SIGINFO is not set, the signal handler can have one of + * two possible signatures. + */ + if ((act->sa_flags & SA_SIGINFO) == 0) + fsig = (struct func_sig) { .valid = false }; + else + fsig = (struct func_sig) { + .valid = true, + .reg_args = 3, .mem_args = false, + .ret_args = NONE + }; + + context = tramp_intern(NULL, &(struct tramp_data) { + .target = context, + .defobj = defobj, + .sig = fsig + }); + } else + defobj = header->defobj; + + /* + * XXX: Enforce signal handling policy. We need to determine who is + * registering the signal, who is handling the signal, and ensure both + * are allowed. + */ + (void)sig; + if (defobj->compart_id == C18N_RTLD_COMPART_ID) + rtld_fatal("c18n: Attempting to register an RTLD function as " + "a signal handler"); + + act->sa_flags |= SA_SIGINFO; + + return (context); +} + +void _rtld_sigaction_end(int, void *, const struct sigaction *, + struct sigaction *); + +void _rtld_sigaction_end(int sig, void *context, const struct sigaction *act, + struct sigaction *oact) { - thr_sighandler(sig, info, _ucp); + struct sigaction_map *slot = &sigaction_map[sig - 1]; + + /* + * If o_handler == NULL, then we must have oact->sa_sigaction == NULL + */ + if (oact != NULL && slot->o_handler != NULL) + oact->sa_sigaction = slot->o_handler; + + if (act != NULL) { + slot->o_handler = act->sa_sigaction; + if (context != NULL) + slot->n_handler = context; + } } diff --git a/share/man/man7/compartmentalization.7 b/share/man/man7/compartmentalization.7 index c495b8dc4d54..239616e1ab4d 100644 --- a/share/man/man7/compartmentalization.7 +++ b/share/man/man7/compartmentalization.7 @@ -129,10 +129,6 @@ For more up-to-date information, visit .Lk https://github.com/CTSRD-CHERI/cheripedia/wiki/Library-based-Compartmentalisation . .Bl -bullet .It -Calling -.Xr longjmp 3 -in a signal handler is unsupported. -.It Stack unwinding is not expected to work. This includes C++ exceptions, stack tracing in debuggers, and usage of .Lb libunwind .