summary refs log tree commit diff
path: root/nptl/pthread_cancel.c
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2021-05-21 22:35:00 +0200
committerFlorian Weimer <fweimer@redhat.com>2021-05-21 22:35:00 +0200
commit2f69522d460611b1018e15df6c238dda2d8d6609 (patch)
tree1e7a59afe9de337ad58dfdebab4502794db51732 /nptl/pthread_cancel.c
parent06a36b70f946548d7bc5bc1b163d1ecf877da071 (diff)
downloadglibc-2f69522d460611b1018e15df6c238dda2d8d6609.tar.gz
glibc-2f69522d460611b1018e15df6c238dda2d8d6609.tar.xz
glibc-2f69522d460611b1018e15df6c238dda2d8d6609.zip
nptl: Perform signal initialization upon pthread_create
Install signal handlers and unblock signals before pthread_create
creates the first thread.

create_thread in sysdeps/unix/sysv/linux/createthread.c can send
SIGCANCEL to the current thread, so the SIGCANCEL handler is currently
needed even if pthread_cancel is never called.  (The way timer_create
uses SIGCANCEL does not need a signal handler; both SIG_DFL and SIG_IGN
dispositions should work.)

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
Diffstat (limited to 'nptl/pthread_cancel.c')
-rw-r--r--nptl/pthread_cancel.c88
1 files changed, 79 insertions, 9 deletions
diff --git a/nptl/pthread_cancel.c b/nptl/pthread_cancel.c
index e4ad602900..802c691874 100644
--- a/nptl/pthread_cancel.c
+++ b/nptl/pthread_cancel.c
@@ -26,6 +26,63 @@
 #include <unwind-link.h>
 #include <stdio.h>
 #include <gnu/lib-names.h>
+#include <sys/single_threaded.h>
+
+/* For asynchronous cancellation we use a signal.  This is the core
+   logic of the signal handler.  */
+static void
+sigcancel_handler (void)
+{
+  struct pthread *self = THREAD_SELF;
+
+  int oldval = THREAD_GETMEM (self, cancelhandling);
+  while (1)
+    {
+      /* We are canceled now.  When canceled by another thread this flag
+	 is already set but if the signal is directly send (internally or
+	 from another process) is has to be done here.  */
+      int newval = oldval | CANCELING_BITMASK | CANCELED_BITMASK;
+
+      if (oldval == newval || (oldval & EXITING_BITMASK) != 0)
+	/* Already canceled or exiting.  */
+	break;
+
+      int curval = THREAD_ATOMIC_CMPXCHG_VAL (self, cancelhandling, newval,
+					      oldval);
+      if (curval == oldval)
+	{
+	  /* Set the return value.  */
+	  THREAD_SETMEM (self, result, PTHREAD_CANCELED);
+
+	  /* Make sure asynchronous cancellation is still enabled.  */
+	  if ((newval & CANCELTYPE_BITMASK) != 0)
+	    /* Run the registered destructors and terminate the thread.  */
+	    __do_cancel ();
+
+	  break;
+	}
+
+      oldval = curval;
+    }
+}
+
+/* This is the actually installed SIGCANCEL handler.  It adds some
+   safety checks before performing the cancellation.  */
+void
+__nptl_sigcancel_handler (int sig, siginfo_t *si, void *ctx)
+{
+  /* Safety check.  It would be possible to call this function for
+     other signals and send a signal from another process.  This is not
+     correct and might even be a security problem.  Try to catch as
+     many incorrect invocations as possible.  */
+  if (sig != SIGCANCEL
+      || si->si_pid != __getpid()
+      || si->si_code != SI_TKILL)
+    return;
+
+  sigcancel_handler ();
+}
+libc_hidden_def (__nptl_sigcancel_handler)
 
 int
 __pthread_cancel (pthread_t th)
@@ -72,14 +129,23 @@ __pthread_cancel (pthread_t th)
 						    oldval))
 	    goto again;
 
-	  /* The cancellation handler will take care of marking the
-	     thread as canceled.  */
-	  pid_t pid = __getpid ();
-
-	  int val = INTERNAL_SYSCALL_CALL (tgkill, pid, pd->tid,
-					   SIGCANCEL);
-	  if (INTERNAL_SYSCALL_ERROR_P (val))
-	    result = INTERNAL_SYSCALL_ERRNO (val);
+	  if (pd == THREAD_SELF)
+	    /* This is not merely an optimization: An application may
+	       call pthread_cancel (pthread_self ()) without calling
+	       pthread_create, so the signal handler may not have been
+	       set up for a self-cancel.  */
+	    sigcancel_handler ();
+	  else
+	    {
+	      /* The cancellation handler will take care of marking the
+		 thread as canceled.  */
+	      pid_t pid = __getpid ();
+
+	      int val = INTERNAL_SYSCALL_CALL (tgkill, pid, pd->tid,
+					       SIGCANCEL);
+	      if (INTERNAL_SYSCALL_ERROR_P (val))
+		result = INTERNAL_SYSCALL_ERRNO (val);
+	    }
 
 	  break;
 	}
@@ -106,4 +172,8 @@ versioned_symbol (libc, __pthread_cancel, pthread_cancel, GLIBC_2_34);
 compat_symbol (libpthread, __pthread_cancel, pthread_cancel, GLIBC_2_0);
 #endif
 
-PTHREAD_STATIC_FN_REQUIRE (__pthread_create)
+/* Ensure that the unwinder is always linked in (the __pthread_unwind
+   reference from __do_cancel is weak).  Use ___pthread_unwind_next
+   (three underscores) to produce a strong reference to the same
+   file.  */
+PTHREAD_STATIC_FN_REQUIRE (___pthread_unwind_next)