From 686f1a118c045c74f6a8446aef5508ad9cddd6fc Mon Sep 17 00:00:00 2001 From: Abhinav Anil Sharma Date: Thu, 16 Dec 2021 21:14:36 -0500 Subject: [PATCH] i#5254: handle rt_sigprocmask error cases. (#5255) Handles the EINVAL and EFAULT cases in rt_sigprocmask. Some code uses this syscall to check whether a given address is valid; e.g. https://github.com/abseil/abseil-cpp/blob/master/absl/debugging/internal/address_is_readable.cc#L85 This requires DR to return EFAULT as expected by the app. Also adds tests for some error and success cases of rt_sigprocmask. Fixes: #5254 --- core/unix/os.c | 19 +++++++++--- core/unix/os_private.h | 2 +- core/unix/signal.c | 45 +++++++++++++++++++++++++++-- suite/tests/linux/sigaction.c | 54 +++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/core/unix/os.c b/core/unix/os.c index e63bb29a63c..db89d26cb3f 100644 --- a/core/unix/os.c +++ b/core/unix/os.c @@ -7331,22 +7331,33 @@ pre_system_call(dcontext_t *dcontext) break; } case IF_MACOS_ELSE(SYS_sigprocmask, SYS_rt_sigprocmask): { /* 175 */ + /* TODO i#5256: Fx this path and enable linux.sigaction on MacOS. */ /* in /usr/src/linux/kernel/signal.c: asmlinkage long sys_rt_sigprocmask(int how, sigset_t *set, sigset_t *oset, size_t sigsetsize) */ /* we also need access to the params in post_system_call */ + uint error_code = 0; dcontext->sys_param0 = sys_param(dcontext, 0); dcontext->sys_param1 = sys_param(dcontext, 1); dcontext->sys_param2 = sys_param(dcontext, 2); - dcontext->sys_param3 = sys_param(dcontext, 3); + /* SYS_sigprocmask on MacOS does not have a size arg. So we use the + * kernel_sigset_t size instead. + */ + size_t sigsetsize = + (size_t)IF_MACOS_ELSE(sizeof(kernel_sigset_t), sys_param(dcontext, 3)); + dcontext->sys_param3 = (reg_t)sigsetsize; execute_syscall = handle_sigprocmask(dcontext, (int)sys_param(dcontext, 0), (kernel_sigset_t *)sys_param(dcontext, 1), (kernel_sigset_t *)sys_param(dcontext, 2), - (size_t)sys_param(dcontext, 3)); - if (!execute_syscall) - set_success_return_val(dcontext, 0); + sigsetsize, &error_code); + if (!execute_syscall) { + if (error_code == 0) + set_success_return_val(dcontext, 0); + else + set_failure_return_val(dcontext, error_code); + } break; } #ifdef MACOS diff --git a/core/unix/os_private.h b/core/unix/os_private.h index a078be33542..a9e76492c57 100644 --- a/core/unix/os_private.h +++ b/core/unix/os_private.h @@ -350,7 +350,7 @@ handle_sigaltstack(dcontext_t *dcontext, const stack_t *stack, stack_t *old_stac bool handle_sigprocmask(dcontext_t *dcontext, int how, kernel_sigset_t *set, - kernel_sigset_t *oset, size_t sigsetsize); + kernel_sigset_t *oset, size_t sigsetsize, uint *error_code); void handle_post_sigprocmask(dcontext_t *dcontext, int how, kernel_sigset_t *set, kernel_sigset_t *oset, size_t sigsetsize); diff --git a/core/unix/signal.c b/core/unix/signal.c index f8bc34fe428..b8311991995 100644 --- a/core/unix/signal.c +++ b/core/unix/signal.c @@ -2293,17 +2293,52 @@ check_signals_pending(dcontext_t *dcontext, thread_sig_info_t *info) /* Returns whether to execute the syscall */ bool handle_sigprocmask(dcontext_t *dcontext, int how, kernel_sigset_t *app_set, - kernel_sigset_t *oset, size_t sigsetsize) + kernel_sigset_t *oset, size_t sigsetsize, uint *error_code) { thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; int i; - kernel_sigset_t safe_set; + kernel_sigset_t safe_set, safe_old_set; + /* Some code uses this syscall to check whether the given address is + * readable. E.g. + * github.com/abseil/abseil-cpp/blob/master/absl/debugging/internal/ + * address_is_readable.cc#L85 + * Those uses as well as our checks below don't guarantee that the given + * address will _remain_ readable or writable, even if they succeed now. + * TODO i#5255: DR can at least pass the safe_set to the actual syscall + * (in cases where it is not skipped) to avoid a second read of app_set, + * by the kernel. + */ + if (sigsetsize != sizeof(kernel_sigset_t)) { + if (error_code != NULL) + *error_code = EINVAL; + return false; + } + if (app_set != NULL && !d_r_safe_read(app_set, sizeof(safe_set), &safe_set)) { + if (error_code != NULL) + *error_code = EFAULT; + return false; + } + if (app_set != NULL && !(how >= SIG_BLOCK && how <= SIG_SETMASK)) { + if (error_code != NULL) + *error_code = EINVAL; + return false; + } + if (oset != NULL) { + /* Old sigset should be writable too. */ + if (!d_r_safe_read(oset, sizeof(safe_old_set), &safe_old_set) || + !safe_write_ex(oset, sizeof(safe_old_set), &safe_old_set, NULL)) { + if (error_code != NULL) + *error_code = EFAULT; + return false; + } + } /* If we're intercepting all, we emulate the whole thing */ bool execute_syscall = !DYNAMO_OPTION(intercept_all_signals); LOG(THREAD, LOG_ASYNCH, 2, "handle_sigprocmask\n"); if (oset != NULL) info->pre_syscall_app_sigblocked = info->app_sigblocked; - if (app_set != NULL && d_r_safe_read(app_set, sizeof(safe_set), &safe_set)) { + if (app_set != NULL) { + /* app_set was already read into safe_set above. */ if (execute_syscall) { /* The syscall will execute, so remove from the set passed * to it. We restore post-syscall. @@ -2382,6 +2417,10 @@ handle_post_sigprocmask(dcontext_t *dcontext, int how, kernel_sigset_t *app_set, { thread_sig_info_t *info = (thread_sig_info_t *)dcontext->signal_field; int i; + /* TODO i#5255: Handle the case where the old sigset was writable in + * handle_sigprocmask but not now. Also, for the case where app_set is only + * readable but not writable. + */ if (!DYNAMO_OPTION(intercept_all_signals)) { /* Restore app memory */ safe_write_ex(app_set, sizeof(*app_set), &info->pre_syscall_app_sigprocmask, diff --git a/suite/tests/linux/sigaction.c b/suite/tests/linux/sigaction.c index 51f42ca71d7..7b783f464ff 100644 --- a/suite/tests/linux/sigaction.c +++ b/suite/tests/linux/sigaction.c @@ -115,6 +115,57 @@ set_sigaction_handler(int sig, void *action) assert(rc == 0); } +#if !defined(MACOS) +static void +test_rt_sigprocmask() +{ + uint64 new = 0xf00d, old, original; + /* Save original sigprocmask. Both return the current sigprocmask. */ + assert(syscall(SYS_rt_sigprocmask, SIG_SETMASK, NULL, &original, + /*sizeof(kernel_sigset_t)*/ 8) == 0); + assert(syscall(SYS_rt_sigprocmask, ~0, NULL, &original, 8) == 0); + + /* EFAULT cases. */ + assert(syscall(SYS_rt_sigprocmask, ~0, NULL, 0x123, 8) == -1); + assert(errno == EFAULT); + assert(syscall(SYS_rt_sigprocmask, SIG_BLOCK, 0x123, NULL, 8) == -1); + assert(errno == EFAULT); + assert(syscall(SYS_rt_sigprocmask, SIG_BLOCK, NULL, 0x123, 8) == -1); + assert(errno == EFAULT); + /* Bad new sigmask EFAULT gets reported before bad 'how' EINVAL. */ + assert(syscall(SYS_rt_sigprocmask, ~0, 0x123, NULL, 8) == -1); + assert(errno == EFAULT); + /* EFAULT due to unwritable address. */ + assert(syscall(SYS_rt_sigprocmask, SIG_BLOCK, NULL, test_rt_sigprocmask, 8) == -1); + assert(errno == EFAULT); + + /* EINVAL cases. */ + /* Bad size. */ + assert(syscall(SYS_rt_sigprocmask, SIG_SETMASK, &new, NULL, 7) == -1); + assert(errno == EINVAL); + /* Bad size EINVAL gets reported before bad new sigmask EFAULT. */ + assert(syscall(SYS_rt_sigprocmask, SIG_SETMASK, 0x123, NULL, 7) == -1); + assert(errno == EINVAL); + /* Bad 'how' arg. */ + assert(syscall(SYS_rt_sigprocmask, ~0, &new, NULL, 8) == -1); + assert(errno == EINVAL); + assert(syscall(SYS_rt_sigprocmask, SIG_SETMASK + 1, &new, NULL, 8) == -1); + assert(errno == EINVAL); + /* Bad 'how' EINVAL gets reported before bad old sigset EFAULT. */ + assert(syscall(SYS_rt_sigprocmask, ~0, &new, 0x123, 8) == -1); + assert(errno == EINVAL); + + /* Success. */ + assert(syscall(SYS_rt_sigprocmask, ~0, NULL, NULL, 8) == 0); + assert(syscall(SYS_rt_sigprocmask, SIG_SETMASK, &new, NULL, 8) == 0); + assert(syscall(SYS_rt_sigprocmask, ~0, NULL, &old, 8) == 0); + assert(new == old); + + /* Restore original sigprocmask. */ + assert(syscall(SYS_rt_sigprocmask, SIG_SETMASK, &original, NULL, 8) == 0); +} +#endif + #if !defined(MACOS) && !defined(X64) static void test_non_rt_sigaction(int sig) @@ -163,6 +214,9 @@ int main(int argc, char **argv) { test_query(SIGTERM); +#if !defined(MACOS) + test_rt_sigprocmask(); +#endif #if !defined(MACOS) && !defined(X64) test_non_rt_sigaction(SIGPIPE); #endif