Skip to content

Commit

Permalink
i#5254: handle rt_sigprocmask error cases. (#5255)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
abhinav92003 authored Dec 17, 2021
1 parent b56a61f commit 686f1a1
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 8 deletions.
19 changes: 15 additions & 4 deletions core/unix/os.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion core/unix/os_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
45 changes: 42 additions & 3 deletions core/unix/signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
54 changes: 54 additions & 0 deletions suite/tests/linux/sigaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 686f1a1

Please sign in to comment.