diff options
author | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2016-04-22 09:25:20 -0300 |
---|---|---|
committer | Adhemerval Zanella <adhemerval.zanella@linaro.com> | 2016-07-13 17:08:51 +0100 |
commit | 2ac88eecc57ff00e0b5ff803ebcc3465d2d640dd (patch) | |
tree | 5b425337c8ccc0f0cb878df1f599fa3a2974f01d /sysdeps/unix/sysv | |
parent | e15eaa8f335ebfd565ab7752c64f3415d427d9b2 (diff) | |
download | glibc-2ac88eecc57ff00e0b5ff803ebcc3465d2d640dd.tar.gz glibc-2ac88eecc57ff00e0b5ff803ebcc3465d2d640dd.tar.xz glibc-2ac88eecc57ff00e0b5ff803ebcc3465d2d640dd.zip |
Refactor Linux raise implementation (BZ#15368)
This patch changes both the nptl and libc Linux raise implementation to avoid the issues described in BZ#15368. The strategy used is summarized in bug report first comment: 1. Block all signals (including internal NPTL ones); 2. Get pid and tid directly from syscall (not relying on cached values); 3. Call tgkill; 4. Restore old signal mask. Tested on x86_64 and i686. [BZ #15368] * sysdeps/unix/sysv/linux/nptl-signals.h (__nptl_clear_internal_signals): New function. (__libc_signal_block_all): Likewise. (__libc_signal_block_app): Likewise. (__libc_signal_restore_set): Likewise. * sysdeps/unix/sysv/linux/pt-raise.c (raise): Use Linux raise.c implementation. * sysdeps/unix/sysv/linux/raise.c (raise): Reimplement to not use the cached pid/tid value in pthread structure.
Diffstat (limited to 'sysdeps/unix/sysv')
-rw-r--r-- | sysdeps/unix/sysv/linux/nptl-signals.h | 41 | ||||
-rw-r--r-- | sysdeps/unix/sysv/linux/pt-raise.c | 23 | ||||
-rw-r--r-- | sysdeps/unix/sysv/linux/raise.c | 63 |
3 files changed, 77 insertions, 50 deletions
diff --git a/sysdeps/unix/sysv/linux/nptl-signals.h b/sysdeps/unix/sysv/linux/nptl-signals.h index 01f34c27ec..271015903e 100644 --- a/sysdeps/unix/sysv/linux/nptl-signals.h +++ b/sysdeps/unix/sysv/linux/nptl-signals.h @@ -39,5 +39,46 @@ __nptl_is_internal_signal (int sig) return (sig == SIGCANCEL) || (sig == SIGTIMER) || (sig == SIGSETXID); } +/* Remove internal glibc signal from the mask. */ +static inline void +__nptl_clear_internal_signals (sigset_t *set) +{ + __sigdelset (set, SIGCANCEL); + __sigdelset (set, SIGTIMER); + __sigdelset (set, SIGSETXID); +} + +#define SIGALL_SET \ + ((__sigset_t) { .__val = {[0 ... _SIGSET_NWORDS-1 ] = -1 } }) + +/* Block all signals, including internal glibc ones. */ +static inline int +__libc_signal_block_all (sigset_t *set) +{ + INTERNAL_SYSCALL_DECL (err); + return INTERNAL_SYSCALL (rt_sigprocmask, err, 4, SIG_BLOCK, &SIGALL_SET, + set, _NSIG / 8); +} + +/* Block all application signals (excluding internal glibc ones). */ +static inline int +__libc_signal_block_app (sigset_t *set) +{ + sigset_t allset = SIGALL_SET; + __nptl_clear_internal_signals (&allset); + INTERNAL_SYSCALL_DECL (err); + return INTERNAL_SYSCALL (rt_sigprocmask, err, 4, SIG_BLOCK, &allset, set, + _NSIG / 8); +} + +/* Restore current process signal mask. */ +static inline int +__libc_signal_restore_set (const sigset_t *set) +{ + INTERNAL_SYSCALL_DECL (err); + return INTERNAL_SYSCALL (rt_sigprocmask, err, 4, SIG_SETMASK, set, NULL, + _NSIG / 8); +} + /* Used to communicate with signal handler. */ extern struct xid_command *__xidcmd attribute_hidden; diff --git a/sysdeps/unix/sysv/linux/pt-raise.c b/sysdeps/unix/sysv/linux/pt-raise.c index 715bbe9f2e..5f6dea1716 100644 --- a/sysdeps/unix/sysv/linux/pt-raise.c +++ b/sysdeps/unix/sysv/linux/pt-raise.c @@ -1,4 +1,5 @@ -/* Copyright (C) 2002-2016 Free Software Foundation, Inc. +/* ISO C raise function for libpthread. + Copyright (C) 2002-2016 Free Software Foundation, Inc. This file is part of the GNU C Library. Contributed by Ulrich Drepper <drepper@redhat.com>, 2002. @@ -16,22 +17,4 @@ License along with the GNU C Library; if not, see <http://www.gnu.org/licenses/>. */ -#include <errno.h> -#include <signal.h> -#include <sysdep.h> -#include <tls.h> - - -int -raise (int sig) -{ - /* raise is an async-safe function. It could be called while the - fork function temporarily invalidated the PID field. Adjust for - that. */ - pid_t pid = THREAD_GETMEM (THREAD_SELF, pid); - if (__glibc_unlikely (pid < 0)) - pid = -pid; - - return INLINE_SYSCALL (tgkill, 3, pid, THREAD_GETMEM (THREAD_SELF, tid), - sig); -} +#include <sysdeps/unix/sysv/linux/raise.c> diff --git a/sysdeps/unix/sysv/linux/raise.c b/sysdeps/unix/sysv/linux/raise.c index 3795e6e5cc..470033e83d 100644 --- a/sysdeps/unix/sysv/linux/raise.c +++ b/sysdeps/unix/sysv/linux/raise.c @@ -16,42 +16,45 @@ License along with the GNU C Library; if not, see <http://www.gnu.org/licenses/>. */ -#include <errno.h> -#include <limits.h> #include <signal.h> #include <sysdep.h> -#include <nptl/pthreadP.h> - +#include <errno.h> +#include <sys/types.h> +#include <unistd.h> +#include <nptl-signals.h> int raise (int sig) { - struct pthread *pd = THREAD_SELF; - pid_t pid = THREAD_GETMEM (pd, pid); - pid_t selftid = THREAD_GETMEM (pd, tid); - if (selftid == 0) - { - /* This system call is not supposed to fail. */ -#ifdef INTERNAL_SYSCALL - INTERNAL_SYSCALL_DECL (err); - selftid = INTERNAL_SYSCALL (gettid, err, 0); -#else - selftid = INLINE_SYSCALL (gettid, 0); -#endif - THREAD_SETMEM (pd, tid, selftid); - - /* We do not set the PID field in the TID here since we might be - called from a signal handler while the thread executes fork. */ - pid = selftid; - } - else - /* raise is an async-safe function. It could be called while the - fork/vfork function temporarily invalidated the PID field. Adjust for - that. */ - if (__glibc_unlikely (pid <= 0)) - pid = (pid & INT_MAX) == 0 ? selftid : -pid; - - return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig); + /* raise is an async-safe function so it could be called while the + fork/vfork function temporarily invalidated the PID field. To avoid + relying on cached value we block all user-defined signal handler + (which might call fork/vfork) and issue the getpid and gettid + syscalls directly. */ + + + /* rt_sigprocmask may fail if: + + 1. sigsetsize != sizeof (sigset_t) (EINVAL) + 2. a failure in copy from/to user space (EFAULT) + 3. an invalid 'how' operation (EINVAL) + + The first case is already handle in glibc syscall call by using the arch + defined _NSIG. Second case is handled by using a stack allocated mask. + The last one should be handled by the block/unblock functions. */ + + sigset_t set; + __libc_signal_block_app (&set); + + INTERNAL_SYSCALL_DECL (err); + pid_t pid = INTERNAL_SYSCALL (getpid, err, 0); + pid_t tid = INTERNAL_SYSCALL (gettid, err, 0); + + int ret = INLINE_SYSCALL (tgkill, 3, pid, tid, sig); + + __libc_signal_restore_set (&set); + + return ret; } libc_hidden_def (raise) weak_alias (raise, gsignal) |