0b26f7
commit a8ac8c4725ddb1119764126a8674a04c9dd5aea8
0b26f7
Author: Florian Weimer <fweimer@redhat.com>
0b26f7
Date:   Mon Sep 13 11:06:08 2021 +0200
0b26f7
0b26f7
    nptl: Fix race between pthread_kill and thread exit (bug 12889)
0b26f7
    
0b26f7
    A new thread exit lock and flag are introduced.  They are used to
0b26f7
    detect that the thread is about to exit or has exited in
0b26f7
    __pthread_kill_internal, and the signal is not sent in this case.
0b26f7
    
0b26f7
    The test sysdeps/pthread/tst-pthread_cancel-select-loop.c is derived
0b26f7
    from a downstream test originally written by Marek Polacek.
0b26f7
    
0b26f7
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
0b26f7
    (cherry picked from commit 526c3cf11ee9367344b6b15d669e4c3cb461a2be)
0b26f7
0b26f7
diff --git a/nptl/allocatestack.c b/nptl/allocatestack.c
0b26f7
index cfe37a3443b69454..50065bc9bd8a28e5 100644
0b26f7
--- a/nptl/allocatestack.c
0b26f7
+++ b/nptl/allocatestack.c
0b26f7
@@ -32,6 +32,7 @@
0b26f7
 #include <futex-internal.h>
0b26f7
 #include <kernel-features.h>
0b26f7
 #include <nptl-stack.h>
0b26f7
+#include <libc-lock.h>
0b26f7
 
0b26f7
 /* Default alignment of stack.  */
0b26f7
 #ifndef STACK_ALIGN
0b26f7
@@ -127,6 +128,8 @@ get_cached_stack (size_t *sizep, void **memp)
0b26f7
   /* No pending event.  */
0b26f7
   result->nextevent = NULL;
0b26f7
 
0b26f7
+  result->exiting = false;
0b26f7
+  __libc_lock_init (result->exit_lock);
0b26f7
   result->tls_state = (struct tls_internal_t) { 0 };
0b26f7
 
0b26f7
   /* Clear the DTV.  */
0b26f7
diff --git a/nptl/descr.h b/nptl/descr.h
0b26f7
index c85778d44941a42f..4de84138fb960fa4 100644
0b26f7
--- a/nptl/descr.h
0b26f7
+++ b/nptl/descr.h
0b26f7
@@ -396,6 +396,12 @@ struct pthread
0b26f7
      PTHREAD_CANCEL_ASYNCHRONOUS).  */
0b26f7
   unsigned char canceltype;
0b26f7
 
0b26f7
+  /* Used in __pthread_kill_internal to detected a thread that has
0b26f7
+     exited or is about to exit.  exit_lock must only be acquired
0b26f7
+     after blocking signals.  */
0b26f7
+  bool exiting;
0b26f7
+  int exit_lock; /* A low-level lock (for use with __libc_lock_init etc).  */
0b26f7
+
0b26f7
   /* Used on strsignal.  */
0b26f7
   struct tls_internal_t tls_state;
0b26f7
 
0b26f7
diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c
0b26f7
index d8ec299cb1661e82..33b426fc682300dc 100644
0b26f7
--- a/nptl/pthread_create.c
0b26f7
+++ b/nptl/pthread_create.c
0b26f7
@@ -37,6 +37,7 @@
0b26f7
 #include <sys/single_threaded.h>
0b26f7
 #include <version.h>
0b26f7
 #include <clone_internal.h>
0b26f7
+#include <futex-internal.h>
0b26f7
 
0b26f7
 #include <shlib-compat.h>
0b26f7
 
0b26f7
@@ -485,6 +486,19 @@ start_thread (void *arg)
0b26f7
     /* This was the last thread.  */
0b26f7
     exit (0);
0b26f7
 
0b26f7
+  /* This prevents sending a signal from this thread to itself during
0b26f7
+     its final stages.  This must come after the exit call above
0b26f7
+     because atexit handlers must not run with signals blocked.  */
0b26f7
+  __libc_signal_block_all (NULL);
0b26f7
+
0b26f7
+  /* Tell __pthread_kill_internal that this thread is about to exit.
0b26f7
+     If there is a __pthread_kill_internal in progress, this delays
0b26f7
+     the thread exit until the signal has been queued by the kernel
0b26f7
+     (so that the TID used to send it remains valid).  */
0b26f7
+  __libc_lock_lock (pd->exit_lock);
0b26f7
+  pd->exiting = true;
0b26f7
+  __libc_lock_unlock (pd->exit_lock);
0b26f7
+
0b26f7
 #ifndef __ASSUME_SET_ROBUST_LIST
0b26f7
   /* If this thread has any robust mutexes locked, handle them now.  */
0b26f7
 # if __PTHREAD_MUTEX_HAVE_PREV
0b26f7
diff --git a/nptl/pthread_kill.c b/nptl/pthread_kill.c
0b26f7
index 5d4c86f9205a6fb5..fb7862eff787a94f 100644
0b26f7
--- a/nptl/pthread_kill.c
0b26f7
+++ b/nptl/pthread_kill.c
0b26f7
@@ -16,6 +16,7 @@
0b26f7
    License along with the GNU C Library; if not, see
0b26f7
    <https://www.gnu.org/licenses/>.  */
0b26f7
 
0b26f7
+#include <libc-lock.h>
0b26f7
 #include <unistd.h>
0b26f7
 #include <pthreadP.h>
0b26f7
 #include <shlib-compat.h>
0b26f7
@@ -23,37 +24,51 @@
0b26f7
 int
0b26f7
 __pthread_kill_internal (pthread_t threadid, int signo)
0b26f7
 {
0b26f7
-  pid_t tid;
0b26f7
   struct pthread *pd = (struct pthread *) threadid;
0b26f7
-
0b26f7
   if (pd == THREAD_SELF)
0b26f7
-    /* It is a special case to handle raise() implementation after a vfork
0b26f7
-       call (which does not update the PD tid field).  */
0b26f7
-    tid = INLINE_SYSCALL_CALL (gettid);
0b26f7
-  else
0b26f7
-    /* Force load of pd->tid into local variable or register.  Otherwise
0b26f7
-       if a thread exits between ESRCH test and tgkill, we might return
0b26f7
-       EINVAL, because pd->tid would be cleared by the kernel.  */
0b26f7
-    tid = atomic_forced_read (pd->tid);
0b26f7
-
0b26f7
-  int val;
0b26f7
-  if (__glibc_likely (tid > 0))
0b26f7
     {
0b26f7
-      pid_t pid = __getpid ();
0b26f7
-
0b26f7
-      val = INTERNAL_SYSCALL_CALL (tgkill, pid, tid, signo);
0b26f7
-      val = (INTERNAL_SYSCALL_ERROR_P (val)
0b26f7
-	    ? INTERNAL_SYSCALL_ERRNO (val) : 0);
0b26f7
+      /* Use the actual TID from the kernel, so that it refers to the
0b26f7
+         current thread even if called after vfork.  There is no
0b26f7
+         signal blocking in this case, so that the signal is delivered
0b26f7
+         immediately, before __pthread_kill_internal returns: a signal
0b26f7
+         sent to the thread itself needs to be delivered
0b26f7
+         synchronously.  (It is unclear if Linux guarantees the
0b26f7
+         delivery of all pending signals after unblocking in the code
0b26f7
+         below.  POSIX only guarantees delivery of a single signal,
0b26f7
+         which may not be the right one.)  */
0b26f7
+      pid_t tid = INTERNAL_SYSCALL_CALL (gettid);
0b26f7
+      int ret = INTERNAL_SYSCALL_CALL (kill, tid, signo);
0b26f7
+      return INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO (ret) : 0;
0b26f7
     }
0b26f7
+
0b26f7
+  /* Block all signals, as required by pd->exit_lock.  */
0b26f7
+  sigset_t old_mask;
0b26f7
+  __libc_signal_block_all (&old_mask);
0b26f7
+  __libc_lock_lock (pd->exit_lock);
0b26f7
+
0b26f7
+  int ret;
0b26f7
+  if (pd->exiting)
0b26f7
+    /* The thread is about to exit (or has exited).  Sending the
0b26f7
+       signal is either not observable (the target thread has already
0b26f7
+       blocked signals at this point), or it will fail, or it might be
0b26f7
+       delivered to a new, unrelated thread that has reused the TID.
0b26f7
+       So do not actually send the signal.  Do not report an error
0b26f7
+       because the threadid argument is still valid (the thread ID
0b26f7
+       lifetime has not ended), and ESRCH (for example) would be
0b26f7
+       misleading.  */
0b26f7
+    ret = 0;
0b26f7
   else
0b26f7
-    /* The kernel reports that the thread has exited.  POSIX specifies
0b26f7
-       the ESRCH error only for the case when the lifetime of a thread
0b26f7
-       ID has ended, but calling pthread_kill on such a thread ID is
0b26f7
-       undefined in glibc.  Therefore, do not treat kernel thread exit
0b26f7
-       as an error.  */
0b26f7
-    val = 0;
0b26f7
+    {
0b26f7
+      /* Using tgkill is a safety measure.  pd->exit_lock ensures that
0b26f7
+	 the target thread cannot exit.  */
0b26f7
+      ret = INTERNAL_SYSCALL_CALL (tgkill, __getpid (), pd->tid, signo);
0b26f7
+      ret = INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO (ret) : 0;
0b26f7
+    }
0b26f7
+
0b26f7
+  __libc_lock_unlock (pd->exit_lock);
0b26f7
+  __libc_signal_restore_set (&old_mask);
0b26f7
 
0b26f7
-  return val;
0b26f7
+  return ret;
0b26f7
 }
0b26f7
 
0b26f7
 int
0b26f7
diff --git a/sysdeps/pthread/Makefile b/sysdeps/pthread/Makefile
0b26f7
index dedfa0d290da4949..48dba717a1cdc20a 100644
0b26f7
--- a/sysdeps/pthread/Makefile
0b26f7
+++ b/sysdeps/pthread/Makefile
0b26f7
@@ -119,7 +119,9 @@ tests += tst-cnd-basic tst-mtx-trylock tst-cnd-broadcast \
0b26f7
 	 tst-unwind-thread \
0b26f7
 	 tst-pt-vfork1 tst-pt-vfork2 tst-vfork1x tst-vfork2x \
0b26f7
 	 tst-pthread_cancel-exited \
0b26f7
+	 tst-pthread_cancel-select-loop \
0b26f7
 	 tst-pthread_kill-exited \
0b26f7
+	 tst-pthread_kill-exiting \
0b26f7
 	 # tests
0b26f7
 
0b26f7
 tests-time64 := \
0b26f7
diff --git a/sysdeps/pthread/tst-pthread_cancel-select-loop.c b/sysdeps/pthread/tst-pthread_cancel-select-loop.c
0b26f7
new file mode 100644
0b26f7
index 0000000000000000..a62087589cee24b5
0b26f7
--- /dev/null
0b26f7
+++ b/sysdeps/pthread/tst-pthread_cancel-select-loop.c
0b26f7
@@ -0,0 +1,87 @@
0b26f7
+/* Test that pthread_cancel succeeds during thread exit.
0b26f7
+   Copyright (C) 2021 Free Software Foundation, Inc.
0b26f7
+   This file is part of the GNU C Library.
0b26f7
+
0b26f7
+   The GNU C Library is free software; you can redistribute it and/or
0b26f7
+   modify it under the terms of the GNU Lesser General Public
0b26f7
+   License as published by the Free Software Foundation; either
0b26f7
+   version 2.1 of the License, or (at your option) any later version.
0b26f7
+
0b26f7
+   The GNU C Library is distributed in the hope that it will be useful,
0b26f7
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
0b26f7
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0b26f7
+   Lesser General Public License for more details.
0b26f7
+
0b26f7
+   You should have received a copy of the GNU Lesser General Public
0b26f7
+   License along with the GNU C Library; if not, see
0b26f7
+   <https://www.gnu.org/licenses/>.  */
0b26f7
+
0b26f7
+/* This test tries to trigger an internal race condition in
0b26f7
+   pthread_cancel, where the cancellation signal is sent after the
0b26f7
+   thread has begun the cancellation process.  This can result in a
0b26f7
+   spurious ESRCH error.  For the original bug 12889, the window is
0b26f7
+   quite small, so the bug was not reproduced in every run.  */
0b26f7
+
0b26f7
+#include <stdbool.h>
0b26f7
+#include <stddef.h>
0b26f7
+#include <support/check.h>
0b26f7
+#include <support/xthread.h>
0b26f7
+#include <support/xunistd.h>
0b26f7
+#include <sys/select.h>
0b26f7
+#include <unistd.h>
0b26f7
+
0b26f7
+/* Set to true by timeout_thread_function when the test should
0b26f7
+   terminate.  */
0b26f7
+static bool timeout;
0b26f7
+
0b26f7
+static void *
0b26f7
+timeout_thread_function (void *unused)
0b26f7
+{
0b26f7
+  usleep (5 * 1000 * 1000);
0b26f7
+  __atomic_store_n (&timeout, true, __ATOMIC_RELAXED);
0b26f7
+  return NULL;
0b26f7
+}
0b26f7
+
0b26f7
+/* Used for blocking the select function below.  */
0b26f7
+static int pipe_fds[2];
0b26f7
+
0b26f7
+static void *
0b26f7
+canceled_thread_function (void *unused)
0b26f7
+{
0b26f7
+  while (true)
0b26f7
+    {
0b26f7
+      fd_set rfs;
0b26f7
+      fd_set wfs;
0b26f7
+      fd_set efs;
0b26f7
+      FD_ZERO (&rfs;;
0b26f7
+      FD_ZERO (&wfs;;
0b26f7
+      FD_ZERO (&efs;;
0b26f7
+      FD_SET (pipe_fds[0], &rfs;;
0b26f7
+
0b26f7
+      /* If the cancellation request is recognized early, the thread
0b26f7
+         begins exiting while the cancellation signal arrives.  */
0b26f7
+      select (FD_SETSIZE, &rfs, &wfs, &efs, NULL);
0b26f7
+    }
0b26f7
+  return NULL;
0b26f7
+}
0b26f7
+
0b26f7
+static int
0b26f7
+do_test (void)
0b26f7
+{
0b26f7
+  xpipe (pipe_fds);
0b26f7
+  pthread_t thr_timeout = xpthread_create (NULL, timeout_thread_function, NULL);
0b26f7
+
0b26f7
+  while (!__atomic_load_n (&timeout, __ATOMIC_RELAXED))
0b26f7
+    {
0b26f7
+      pthread_t thr = xpthread_create (NULL, canceled_thread_function, NULL);
0b26f7
+      xpthread_cancel (thr);
0b26f7
+      TEST_VERIFY (xpthread_join (thr) == PTHREAD_CANCELED);
0b26f7
+    }
0b26f7
+
0b26f7
+  xpthread_join (thr_timeout);
0b26f7
+  xclose (pipe_fds[0]);
0b26f7
+  xclose (pipe_fds[1]);
0b26f7
+  return 0;
0b26f7
+}
0b26f7
+
0b26f7
+#include <support/test-driver.c>
0b26f7
diff --git a/sysdeps/pthread/tst-pthread_kill-exiting.c b/sysdeps/pthread/tst-pthread_kill-exiting.c
0b26f7
new file mode 100644
0b26f7
index 0000000000000000..f803e94f1195f204
0b26f7
--- /dev/null
0b26f7
+++ b/sysdeps/pthread/tst-pthread_kill-exiting.c
0b26f7
@@ -0,0 +1,123 @@
0b26f7
+/* Test that pthread_kill succeeds during thread exit.
0b26f7
+   Copyright (C) 2021 Free Software Foundation, Inc.
0b26f7
+   This file is part of the GNU C Library.
0b26f7
+
0b26f7
+   The GNU C Library is free software; you can redistribute it and/or
0b26f7
+   modify it under the terms of the GNU Lesser General Public
0b26f7
+   License as published by the Free Software Foundation; either
0b26f7
+   version 2.1 of the License, or (at your option) any later version.
0b26f7
+
0b26f7
+   The GNU C Library is distributed in the hope that it will be useful,
0b26f7
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
0b26f7
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0b26f7
+   Lesser General Public License for more details.
0b26f7
+
0b26f7
+   You should have received a copy of the GNU Lesser General Public
0b26f7
+   License along with the GNU C Library; if not, see
0b26f7
+   <https://www.gnu.org/licenses/>.  */
0b26f7
+
0b26f7
+/* This test verifies that pthread_kill for a thread that is exiting
0b26f7
+   succeeds (with or without actually delivering the signal).  */
0b26f7
+
0b26f7
+#include <array_length.h>
0b26f7
+#include <stdbool.h>
0b26f7
+#include <stddef.h>
0b26f7
+#include <support/xsignal.h>
0b26f7
+#include <support/xthread.h>
0b26f7
+#include <unistd.h>
0b26f7
+
0b26f7
+/* Set to true by timeout_thread_function when the test should
0b26f7
+   terminate.  */
0b26f7
+static bool timeout;
0b26f7
+
0b26f7
+static void *
0b26f7
+timeout_thread_function (void *unused)
0b26f7
+{
0b26f7
+  usleep (1000 * 1000);
0b26f7
+  __atomic_store_n (&timeout, true, __ATOMIC_RELAXED);
0b26f7
+  return NULL;
0b26f7
+}
0b26f7
+
0b26f7
+/* Used to synchronize the sending threads with the target thread and
0b26f7
+   main thread.  */
0b26f7
+static pthread_barrier_t barrier_1;
0b26f7
+static pthread_barrier_t barrier_2;
0b26f7
+
0b26f7
+/* The target thread to which signals are to be sent.  */
0b26f7
+static pthread_t target_thread;
0b26f7
+
0b26f7
+/* Set by the main thread to true after timeout has been set to
0b26f7
+   true.  */
0b26f7
+static bool exiting;
0b26f7
+
0b26f7
+static void *
0b26f7
+sender_thread_function (void *unused)
0b26f7
+{
0b26f7
+  while (true)
0b26f7
+    {
0b26f7
+      /* Wait until target_thread has been initialized.  The target
0b26f7
+         thread and main thread participate in this barrier.  */
0b26f7
+      xpthread_barrier_wait (&barrier_1);
0b26f7
+
0b26f7
+      if (exiting)
0b26f7
+        break;
0b26f7
+
0b26f7
+      xpthread_kill (target_thread, SIGUSR1);
0b26f7
+
0b26f7
+      /* Communicate that the signal has been sent.  The main thread
0b26f7
+         participates in this barrier.  */
0b26f7
+      xpthread_barrier_wait (&barrier_2);
0b26f7
+    }
0b26f7
+  return NULL;
0b26f7
+}
0b26f7
+
0b26f7
+static void *
0b26f7
+target_thread_function (void *unused)
0b26f7
+{
0b26f7
+  target_thread = pthread_self ();
0b26f7
+  xpthread_barrier_wait (&barrier_1);
0b26f7
+  return NULL;
0b26f7
+}
0b26f7
+
0b26f7
+static int
0b26f7
+do_test (void)
0b26f7
+{
0b26f7
+  xsignal (SIGUSR1, SIG_IGN);
0b26f7
+
0b26f7
+  pthread_t thr_timeout = xpthread_create (NULL, timeout_thread_function, NULL);
0b26f7
+
0b26f7
+  pthread_t threads[4];
0b26f7
+  xpthread_barrier_init (&barrier_1, NULL, array_length (threads) + 2);
0b26f7
+  xpthread_barrier_init (&barrier_2, NULL, array_length (threads) + 1);
0b26f7
+
0b26f7
+  for (int i = 0; i < array_length (threads); ++i)
0b26f7
+    threads[i] = xpthread_create (NULL, sender_thread_function, NULL);
0b26f7
+
0b26f7
+  while (!__atomic_load_n (&timeout, __ATOMIC_RELAXED))
0b26f7
+    {
0b26f7
+      xpthread_create (NULL, target_thread_function, NULL);
0b26f7
+
0b26f7
+      /* Wait for the target thread to be set up and signal sending to
0b26f7
+         start.  */
0b26f7
+      xpthread_barrier_wait (&barrier_1);
0b26f7
+
0b26f7
+      /* Wait for signal sending to complete.  */
0b26f7
+      xpthread_barrier_wait (&barrier_2);
0b26f7
+
0b26f7
+      xpthread_join (target_thread);
0b26f7
+    }
0b26f7
+
0b26f7
+  exiting = true;
0b26f7
+
0b26f7
+  /* Signal the sending threads to exit.  */
0b26f7
+  xpthread_create (NULL, target_thread_function, NULL);
0b26f7
+  xpthread_barrier_wait (&barrier_1);
0b26f7
+
0b26f7
+  for (int i = 0; i < array_length (threads); ++i)
0b26f7
+    xpthread_join (threads[i]);
0b26f7
+  xpthread_join (thr_timeout);
0b26f7
+
0b26f7
+  return 0;
0b26f7
+}
0b26f7
+
0b26f7
+#include <support/test-driver.c>