diff options
author | Florian Weimer <fweimer@redhat.com> | 2021-09-13 11:06:08 +0200 |
---|---|---|
committer | Florian Weimer <fweimer@redhat.com> | 2021-09-13 11:06:08 +0200 |
commit | 526c3cf11ee9367344b6b15d669e4c3cb461a2be (patch) | |
tree | 428aa0c50880ee5001732622b0afe94ba7e113d9 /nptl/pthread_kill.c | |
parent | 8af8456004edbab71f8903a60a3cae442cf6fe69 (diff) | |
download | glibc-526c3cf11ee9367344b6b15d669e4c3cb461a2be.tar.gz glibc-526c3cf11ee9367344b6b15d669e4c3cb461a2be.tar.xz glibc-526c3cf11ee9367344b6b15d669e4c3cb461a2be.zip |
nptl: Fix race between pthread_kill and thread exit (bug 12889)
A new thread exit lock and flag are introduced. They are used to detect that the thread is about to exit or has exited in __pthread_kill_internal, and the signal is not sent in this case. The test sysdeps/pthread/tst-pthread_cancel-select-loop.c is derived from a downstream test originally written by Marek Polacek. Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Diffstat (limited to 'nptl/pthread_kill.c')
-rw-r--r-- | nptl/pthread_kill.c | 65 |
1 files changed, 40 insertions, 25 deletions
diff --git a/nptl/pthread_kill.c b/nptl/pthread_kill.c index 5d4c86f920..fb7862eff7 100644 --- a/nptl/pthread_kill.c +++ b/nptl/pthread_kill.c @@ -16,6 +16,7 @@ License along with the GNU C Library; if not, see <https://www.gnu.org/licenses/>. */ +#include <libc-lock.h> #include <unistd.h> #include <pthreadP.h> #include <shlib-compat.h> @@ -23,37 +24,51 @@ int __pthread_kill_internal (pthread_t threadid, int signo) { - pid_t tid; struct pthread *pd = (struct pthread *) threadid; - if (pd == THREAD_SELF) - /* It is a special case to handle raise() implementation after a vfork - call (which does not update the PD tid field). */ - tid = INLINE_SYSCALL_CALL (gettid); - else - /* Force load of pd->tid into local variable or register. Otherwise - if a thread exits between ESRCH test and tgkill, we might return - EINVAL, because pd->tid would be cleared by the kernel. */ - tid = atomic_forced_read (pd->tid); - - int val; - if (__glibc_likely (tid > 0)) { - pid_t pid = __getpid (); - - val = INTERNAL_SYSCALL_CALL (tgkill, pid, tid, signo); - val = (INTERNAL_SYSCALL_ERROR_P (val) - ? INTERNAL_SYSCALL_ERRNO (val) : 0); + /* Use the actual TID from the kernel, so that it refers to the + current thread even if called after vfork. There is no + signal blocking in this case, so that the signal is delivered + immediately, before __pthread_kill_internal returns: a signal + sent to the thread itself needs to be delivered + synchronously. (It is unclear if Linux guarantees the + delivery of all pending signals after unblocking in the code + below. POSIX only guarantees delivery of a single signal, + which may not be the right one.) */ + pid_t tid = INTERNAL_SYSCALL_CALL (gettid); + int ret = INTERNAL_SYSCALL_CALL (kill, tid, signo); + return INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO (ret) : 0; } + + /* Block all signals, as required by pd->exit_lock. */ + sigset_t old_mask; + __libc_signal_block_all (&old_mask); + __libc_lock_lock (pd->exit_lock); + + int ret; + if (pd->exiting) + /* The thread is about to exit (or has exited). Sending the + signal is either not observable (the target thread has already + blocked signals at this point), or it will fail, or it might be + delivered to a new, unrelated thread that has reused the TID. + So do not actually send the signal. Do not report an error + because the threadid argument is still valid (the thread ID + lifetime has not ended), and ESRCH (for example) would be + misleading. */ + ret = 0; else - /* The kernel reports that the thread has exited. POSIX specifies - the ESRCH error only for the case when the lifetime of a thread - ID has ended, but calling pthread_kill on such a thread ID is - undefined in glibc. Therefore, do not treat kernel thread exit - as an error. */ - val = 0; + { + /* Using tgkill is a safety measure. pd->exit_lock ensures that + the target thread cannot exit. */ + ret = INTERNAL_SYSCALL_CALL (tgkill, __getpid (), pd->tid, signo); + ret = INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO (ret) : 0; + } + + __libc_lock_unlock (pd->exit_lock); + __libc_signal_restore_set (&old_mask); - return val; + return ret; } int |