public inbox for glibc-cvs@sourceware.org
help / color / mirror / Atom feed
* [glibc/azanella/pthread-multiple-fixes] nptl: Fix Race conditions in pthread cancellation (BZ#12683, BZ#14147)
@ 2021-06-08 20:48 Adhemerval Zanella
  0 siblings, 0 replies; only message in thread
From: Adhemerval Zanella @ 2021-06-08 20:48 UTC (permalink / raw)
  To: glibc-cvs

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=447822a07dc16f406e86fbc44fa1cd54dfa04dae

commit 447822a07dc16f406e86fbc44fa1cd54dfa04dae
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date:   Mon May 31 16:10:25 2021 -0300

    nptl: Fix Race conditions in pthread cancellation (BZ#12683, BZ#14147)
    
    This patch the race conditions in NPTL cancellation code by redefining
    how cancellable syscalls are defined and handled.  The current buggy
    approach is to enable asynchronous cancellation before making the
    syscall and restore the previous cancellation type once the syscall
    returns.
    
    As described in BZ#12683, this approach shows 2 important problems:
    
      1. Cancellation can act after the syscall has returned from the
         kernel, but before userspace saves the return value.  It might
         result in a resource leak if the syscall allocated a resource or a
         side effect (partial read/write), and there is no way to program
         handle it with cancellation handlers.
    
      2. If a signal is handled while the thread is blocked at a cancellable
         syscall, the entire signal handler runs with asynchronous
         cancellation enabled.  This can lead to issues if the signal
         handler call functions which are async-signal-safe but not
         async-cancel-safe.
    
    For the cancellation to work correctly, there are 5 points at which the
    cancellation signal could arrive:
    
      1. Before the final "testcancel" and before the syscall is made.
      2. Between the "testcancel" and the syscall.
      3. While the syscall is blocked and no side effects have yet taken
         place.
      4. While the syscall is blocked but with some side effects already
         having taken place (e.g. a partial read or write).
      5. After the syscall has returned.
    
    And GLIBC wants to act on cancellation in cases 1, 2, and 3 but not
    in cases 4 or 5.  For the 4 and 5 cases, the cancellation will eventually
    happen in the next cancellable entry point without any further external
    event.
    
    The proposed solution follows for each case:
    
      1. Do a conditional branch based on whether the thread has received
         a cancellation request;
    
      2. It can be caught by the signal handler determining that the saved
         program counter (from the ucontext_t) is in some address range
         beginning just before the "testcancel" and ending with the
         syscall instruction.
    
      3. In this case, except for certain syscalls that ALWAYS fail with
         EINTR even for non-interrupting signals, the kernel will reset
         the program counter to point at the syscall instruction during
         signal handling, so that the syscall is restarted when the signal
         handler returns.  So, from the signal handler's standpoint, this
         looks the same as case 2, and thus it's taken care of.
    
      4. For syscalls with side-effects, the kernel cannot restart the
         syscall; when it's interrupted by a signal, the kernel must cause
         the syscall to return with whatever partial result is obtained
         (e.g. partial read or write).
    
      5. In this case, the saved program counter points just after the
         syscall instruction, so the signal handler won't act on
         cancellation.  This is similar to 4. since the program counter
         is past the syscall instruction.
    
    Another case that needs handling is syscalls that fail with EINTR even
    when the signal handler is non-interrupting. In this case, the syscall
    wrapper code can just check the cancellation flag when the errno result
    is EINTR, and act on cancellation if it's set.
    
    The proposed GLIBC adjustments are:
    
      1. Remove the enable_asynccancel/disable_asynccancel function usage in
         syscall definition and instead make them call a common symbol that
         will check if cancellation is enabled (__syscall_cancel at
         nptl/cancellation.c), call the arch-specific cancellable
         entry-point (__syscall_cancel_arch), and cancel the thread when
         required.
    
      2. Provide an arch-specific generic system call wrapper function
         that contains global markers.  These markers will be used in
         SIGCANCEL handler to check if the interruption has been called in a
         valid syscall and if the syscalls have been completed or not.
    
         A reference implementation sysdeps/unix/sysv/linux/syscall_cancel.c
         is provided.  However, the markers may not be set on correct
         expected places depending on how INTERNAL_SYSCALL_NCS is
         implemented by the architecture and it uses compiler-specific
         construct (asm volatile) to place the required markers.
         It is expected that all architectures add an arch-specific
         implementation.
    
      3. Rewrite SIGCANCEL asynchronous handler to check for both canceling
         type and if current IP from signal handler falls between the global
         markers and act accordingly (sigcancel_handler at
         nptl/pthreaad_cancel.c).
    
      4. Adjust nptl/pthread_cancel.c to send a signal instead of acting
         directly.  This avoids synchronization issues when updating the
         cancellation status and also focuses the logic on the signal
         handler and cancellation syscall code.
    
      5. Adjust pthread code to replace CANCEL_ASYNC/CANCEL_RESET calls to
         appropriated cancelable futex syscalls.
    
      6. Adjust libc code to replace LIBC_CANCEL_ASYNC/LIBC_CANCEL_RESET to
         appropriated cancelable syscalls.
    
      7. Adjust 'lowlevellock-futex.h' arch-specific implementations to
         provide cancelable futex calls (used in libpthread code).
    
    This patch adds the proposed changes to NPTL common code and the following
    patches add the requires arch-specific bits.
    
    It also fixes BZ#14147 since the cancellation type is not changes when
    issueing a cancellation entrypoint.
    
    Checked on x86_64-linux-gnu (the syscall_cancel resulted has the cancel
    marks in the expected places).

Diff:
---
 elf/Makefile                             |   5 +-
 nptl/Makefile                            |   6 +-
 nptl/Versions                            |   3 +-
 nptl/cancellation.c                      |  55 ++++++--------
 nptl/pthreadP.h                          |   4 ++
 nptl/pthread_cancel.c                    |  19 +++--
 nptl/pthread_testcancel.c                |   5 +-
 nptl/tst-cancel29.c                      |  98 +++++++++++++++++++++++++
 nptl/tst-cancel30.c                      |  80 +++++++++++++++++++++
 sysdeps/generic/syscall_types.h          |  25 +++++++
 sysdeps/generic/sysdep-cancel.h          |   2 -
 sysdeps/nptl/cancellation-pc-check.h     |  53 ++++++++++++++
 sysdeps/nptl/lowlevellock-futex.h        |  15 ++--
 sysdeps/unix/sysdep.h                    | 120 ++++++++++++++++++++++---------
 sysdeps/unix/sysv/linux/socketcall.h     |  35 ++++++---
 sysdeps/unix/sysv/linux/syscall_cancel.c |  59 +++++++++++++++
 sysdeps/unix/sysv/linux/sysdep-cancel.h  |  12 ----
 sysdeps/unix/sysv/linux/sysdep.h         |   9 +++
 18 files changed, 491 insertions(+), 114 deletions(-)

diff --git a/elf/Makefile b/elf/Makefile
index e9788d3d4f..9f1022e534 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -529,10 +529,7 @@ $(objpfx)dl-allobjs.os: $(all-rtld-routines:%=$(objpfx)%.os)
 # discovery mechanism is not compatible with the libc implementation
 # when compiled for libc.
 rtld-stubbed-symbols = \
-  __GI___pthread_disable_asynccancel \
-  __GI___pthread_enable_asynccancel \
-  __pthread_disable_asynccancel \
-  __pthread_enable_asynccancel \
+  __GI___pthread_unwind \
   calloc \
   free \
   malloc \
diff --git a/nptl/Makefile b/nptl/Makefile
index 0dd139a53d..1467e18b73 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -200,6 +200,7 @@ routines = \
   sem_timedwait \
   sem_unlink \
   sem_wait \
+  syscall_cancel \
   tpp \
   unwind \
   vars \
@@ -227,6 +228,7 @@ CFLAGS-unwind-forcedunwind.c += -fexceptions -fasynchronous-unwind-tables
 CFLAGS-pthread_cancel.c += -fexceptions -fasynchronous-unwind-tables
 CFLAGS-pthread_setcancelstate.c += -fexceptions -fasynchronous-unwind-tables
 CFLAGS-pthread_setcanceltype.c += -fexceptions -fasynchronous-unwind-tables
+CFLAGS-syscall_cancel.c += -fexceptions -fasynchronous-unwind-tables
 
 # These are internal functions which similar functionality as setcancelstate
 # and setcanceltype.
@@ -308,7 +310,9 @@ tests = tst-attr2 tst-attr3 tst-default-attr \
 	tst-pthread_exit-nothreads \
 	tst-pthread_exit-nothreads-static \
 	tst-thread-setspecific \
-	tst-cleanup5
+	tst-cleanup5 \
+	tst-cancel29 \
+	tst-cancel30
 
 tests-nolibpthread = \
   tst-pthread_exit-nothreads \
diff --git a/nptl/Versions b/nptl/Versions
index d49dba9c15..53d3625d26 100644
--- a/nptl/Versions
+++ b/nptl/Versions
@@ -416,8 +416,6 @@ libc {
     __pthread_cleanup_push;
     __pthread_cleanup_upto;
     __pthread_current_priority;
-    __pthread_disable_asynccancel;
-    __pthread_enable_asynccancel;
     __pthread_force_elision;
     __pthread_get_minstack;
     __pthread_keys;
@@ -428,6 +426,7 @@ libc {
     __pthread_unwind;
     __sched_fifo_max_prio;
     __sched_fifo_min_prio;
+    __syscall_cancel;
   }
 }
 
diff --git a/nptl/cancellation.c b/nptl/cancellation.c
index 6478c029de..c5666949c2 100644
--- a/nptl/cancellation.c
+++ b/nptl/cancellation.c
@@ -16,53 +16,40 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <setjmp.h>
 #include <stdlib.h>
 #include "pthreadP.h"
-#include <futex-internal.h>
 
-
-/* The next two functions are similar to pthread_setcanceltype() but
-   more specialized for the use in the cancelable functions like write().
-   They do not need to check parameters etc.  These functions must be
-   AS-safe, with the exception of the actual cancellation, because they
-   are called by wrappers around AS-safe functions like write().*/
-int
-__pthread_enable_asynccancel (void)
+/* Cancellation function called by all cancellable syscalls.  */
+long int
+__syscall_cancel (__syscall_arg_t nr, __syscall_arg_t a1,
+		  __syscall_arg_t a2, __syscall_arg_t a3,
+		  __syscall_arg_t a4, __syscall_arg_t a5,
+		  __syscall_arg_t a6)
 {
   struct pthread *self = THREAD_SELF;
 
-  int oldval = THREAD_GETMEM (self, canceltype);
-  THREAD_SETMEM (self, canceltype, PTHREAD_CANCEL_ASYNCHRONOUS);
+  /* If cancellation is not enabled, call the syscall directly.  */
+  if (self->cancelstate == PTHREAD_CANCEL_DISABLE)
+    {
+      long int r = INTERNAL_SYSCALL_NCS_CALL (nr, a1, a2, a3, a4, a5, a6);
+      return INTERNAL_SYSCALL_ERROR_P (r) ? -INTERNAL_SYSCALL_ERRNO (r) : r;
+    }
 
-  int ch = THREAD_GETMEM (self, cancelhandling);
+  /* Call the arch-specific entry points that contains the globals markers
+     to be checked by SIGCANCEL handler.  */
+  long int r = __syscall_cancel_arch (&self->cancelhandling, nr, a1, a2, a3,
+				      a4, a5, a6);
 
-  if (self->cancelstate == PTHREAD_CANCEL_ENABLE
-      && (ch & CANCELED_BITMASK)
-      && !(ch & EXITING_BITMASK)
-      && !(ch & TERMINATED_BITMASK))
+  if (r == -EINTR
+      && atomic_load_relaxed (&self->cancelhandling) & CANCELED_BITMASK
+      && self->cancelstate == PTHREAD_CANCEL_ENABLE)
     {
       __do_cancel ();
     }
 
-  return oldval;
-}
-libc_hidden_def (__pthread_enable_asynccancel)
-
-/* See the comment for __pthread_enable_asynccancel regarding
-   the AS-safety of this function.  */
-void
-__pthread_disable_asynccancel (int oldtype)
-{
-  /* If asynchronous cancellation was enabled before we do not have
-     anything to do.  */
-  if (oldtype == PTHREAD_CANCEL_ASYNCHRONOUS)
-    return;
-
-  struct pthread *self = THREAD_SELF;
-  self->canceltype = PTHREAD_CANCEL_DEFERRED;
+  return r;
 }
-libc_hidden_def (__pthread_disable_asynccancel)
+libc_hidden_def (__syscall_cancel)
 
 void
 __do_cancel (void)
diff --git a/nptl/pthreadP.h b/nptl/pthreadP.h
index bfb0e40b44..4d4a3227bb 100644
--- a/nptl/pthreadP.h
+++ b/nptl/pthreadP.h
@@ -292,6 +292,10 @@ __exit_thread (void *value)
    implementation because it might be called by arch-specific asm code.  */
 _Noreturn void __do_cancel (void) attribute_hidden;
 
+extern long int __syscall_cancel_arch (volatile int *, __syscall_arg_t nr,
+     __syscall_arg_t arg1, __syscall_arg_t arg2, __syscall_arg_t arg3,
+     __syscall_arg_t arg4, __syscall_arg_t arg5, __syscall_arg_t arg6);
+libc_hidden_proto (__syscall_cancel)
 
 /* Internal prototypes.  */
 
diff --git a/nptl/pthread_cancel.c b/nptl/pthread_cancel.c
index 577ff6c746..2ea1900d90 100644
--- a/nptl/pthread_cancel.c
+++ b/nptl/pthread_cancel.c
@@ -27,6 +27,7 @@
 #include <stdio.h>
 #include <gnu/lib-names.h>
 #include <sys/single_threaded.h>
+#include <cancellation-pc-check.h>
 
 /* For asynchronous cancellation we use a signal.  */
 static void
@@ -50,10 +51,16 @@ sigcancel_handler (int sig, siginfo_t *si, void *ctx)
       || (ch & EXITING_BITMASK) != 0)
     return;
 
-  /* Set the return value.  */
   THREAD_SETMEM (self, result, PTHREAD_CANCELED);
-  /* Make sure asynchronous cancellation is still enabled.  */
-  if (self->canceltype == PTHREAD_CANCEL_ASYNCHRONOUS)
+
+  /* Check if asynchronous cancellation mode is set or if interrupted
+     instruction pointer falls within the cancellable syscall bridge.  For
+     interruptable syscalls that might generate external side-effects (for
+     instance partial reads or writes), the kernel will set the IP to after
+     '__syscall_cancel_arch_end', thus disabling the cancellation and allowing
+     the process to handle such conditions.  */
+  if (self->canceltype == PTHREAD_CANCEL_ASYNCHRONOUS
+      || cancellation_pc_check (ctx))
     __do_cancel ();
 }
 
@@ -72,7 +79,11 @@ __pthread_cancel (pthread_t th)
     {
       struct sigaction sa;
       sa.sa_sigaction = sigcancel_handler;
-      sa.sa_flags = SA_SIGINFO;
+      /* The signal handle should be non-interruptible to avoid the risk of
+	 spurious EINTR caused by SIGCANCEL sent to process or if
+	 pthread_cancel is called while cancellation is disabled in the target
+	 thread.  */
+      sa.sa_flags = SA_SIGINFO | SA_RESTART;
       __sigemptyset (&sa.sa_mask);
       __libc_sigaction (SIGCANCEL, &sa, NULL);
       atomic_store_relaxed (&init_sigcancel, 1);
diff --git a/nptl/pthread_testcancel.c b/nptl/pthread_testcancel.c
index e6adf12580..4f72f22835 100644
--- a/nptl/pthread_testcancel.c
+++ b/nptl/pthread_testcancel.c
@@ -24,11 +24,8 @@ void
 ___pthread_testcancel (void)
 {
   struct pthread *self = THREAD_SELF;
-  int cancelhandling = THREAD_GETMEM (self, cancelhandling);
   if (self->cancelstate == PTHREAD_CANCEL_ENABLE
-      && (cancelhandling & CANCELED_BITMASK)
-      && !(cancelhandling & EXITING_BITMASK)
-      && !(cancelhandling & TERMINATED_BITMASK))
+      && atomic_load_relaxed (&self->cancelhandling) & CANCELED_BITMASK)
     {
       __do_cancel ();
     }
diff --git a/nptl/tst-cancel29.c b/nptl/tst-cancel29.c
new file mode 100644
index 0000000000..4db1247712
--- /dev/null
+++ b/nptl/tst-cancel29.c
@@ -0,0 +1,98 @@
+/* Check side-effect act for cancellable syscalls (BZ #12683).
+   Copyright (C) 2020 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* This testcase checks if there is resource leakage if the syscall has
+   returned from kernelspace, but before userspace saves the return
+   value.  The 'leaker' thread should be able to close the file descriptor
+   if the resource is already allocated, meaning that if the cancellation
+   signal arrives *after* the open syscal return from kernel, the
+   side-effect should be visible to application.  */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <support/xunistd.h>
+#include <support/xthread.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/support.h>
+#include <support/descriptors.h>
+
+static void *
+writeopener (void *arg)
+{
+  int fd;
+  for (;;)
+    {
+      fd = open (arg, O_WRONLY);
+      xclose (fd);
+    }
+  return NULL;
+}
+
+static void *
+leaker (void *arg)
+{
+  int fd = open (arg, O_RDONLY);
+  TEST_VERIFY_EXIT (fd > 0);
+  pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, 0);
+  xclose (fd);
+  return NULL;
+}
+
+static int
+do_test (void)
+{
+  enum { iter_count = 1000 };
+
+  char *dir = support_create_temp_directory ("tst-cancel28");
+  char *name = xasprintf ("%s/fifo", dir);
+  TEST_COMPARE (mkfifo (name, 0600), 0);
+  add_temp_file (name);
+
+  struct support_descriptors *descrs = support_descriptors_list ();
+
+  srand (1);
+
+  xpthread_create (NULL, writeopener, name);
+  for (int i = 0; i < iter_count; i++)
+    {
+      pthread_t td = xpthread_create (NULL, leaker, name);
+      struct timespec ts =
+	{ .tv_nsec = rand () % 100000, .tv_sec = 0 };
+      nanosleep (&ts, NULL);
+      /* Ignore pthread_cancel result because it might be the
+	 case when pthread_cancel is called when thread is already
+	 exited.  */
+      pthread_cancel (td);
+      xpthread_join (td);
+    }
+
+  support_descriptors_check (descrs);
+
+  support_descriptors_free (descrs);
+
+  free (name);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/nptl/tst-cancel30.c b/nptl/tst-cancel30.c
new file mode 100644
index 0000000000..8b3e6436a7
--- /dev/null
+++ b/nptl/tst-cancel30.c
@@ -0,0 +1,80 @@
+/* Check if cancellation type is not change when executing a cancelation
+   entrypoint (BZ #14147).
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <setjmp.h>
+#include <support/check.h>
+#include <support/xsignal.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+
+static int pipefds[2];
+static pthread_barrier_t b;
+
+static __thread jmp_buf jbuf;
+
+static _Noreturn void
+sigusr1_handler (int sig)
+{
+  longjmp (jbuf, 1);
+}
+
+static void *
+tf (void *arg)
+{
+  xpthread_barrier_wait (&b);
+
+  int r = setjmp (jbuf);
+  if (r == 0)
+    {
+      char c;
+      read (pipefds[0], &c, 1);
+    }
+
+  int type;
+  TEST_VERIFY (pthread_setcanceltype (PTHREAD_CANCEL_DEFERRED, &type) == 0);
+  TEST_COMPARE (type, PTHREAD_CANCEL_DEFERRED);
+
+  return NULL;
+}
+
+static int
+do_test (void)
+{
+  {
+    struct sigaction sa;
+    sa.sa_handler = sigusr1_handler;
+    sa.sa_flags = 0;
+    sigemptyset (&sa.sa_mask);
+    xsigaction (SIGUSR1, &sa, NULL);
+  }
+
+  xpipe (pipefds);
+
+  xpthread_barrier_init (&b, NULL, 2);
+
+  pthread_t th = xpthread_create (NULL, tf, NULL);
+  xpthread_barrier_wait (&b);
+  xpthread_kill (th, SIGUSR1);
+  void *r = xpthread_join (th);
+  TEST_VERIFY (r == NULL);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/generic/syscall_types.h b/sysdeps/generic/syscall_types.h
new file mode 100644
index 0000000000..02eaf10851
--- /dev/null
+++ b/sysdeps/generic/syscall_types.h
@@ -0,0 +1,25 @@
+/* Types and macros used for syscall issuing.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef _SYSCALL_TYPES_H
+#define _SYSCALL_TYPES_H
+
+typedef long int __syscall_arg_t;
+#define __SSC(__x) ((__syscall_arg_t) (__x))
+
+#endif
diff --git a/sysdeps/generic/sysdep-cancel.h b/sysdeps/generic/sysdep-cancel.h
index d22a786536..5c84b4499a 100644
--- a/sysdeps/generic/sysdep-cancel.h
+++ b/sysdeps/generic/sysdep-cancel.h
@@ -3,5 +3,3 @@
 /* No multi-thread handling enabled.  */
 #define SINGLE_THREAD_P (1)
 #define RTLD_SINGLE_THREAD_P (1)
-#define LIBC_CANCEL_ASYNC()	0 /* Just a dummy value.  */
-#define LIBC_CANCEL_RESET(val)	((void)(val)) /* Nothing, but evaluate it.  */
diff --git a/sysdeps/nptl/cancellation-pc-check.h b/sysdeps/nptl/cancellation-pc-check.h
new file mode 100644
index 0000000000..e8c16c2131
--- /dev/null
+++ b/sysdeps/nptl/cancellation-pc-check.h
@@ -0,0 +1,53 @@
+/* Architecture specific code for pthread cancellation handling.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _NPTL_CANCELLATION_PC_CHECK
+#define _NPTL_CANCELLATION_PC_CHECK
+
+#include <sigcontextinfo.h>
+
+/* When a syscall with side-effects is interrupted by a signal, the kernel
+   returns with whatever partial result (e.g. partial I/O operations).  In
+   this case, the saved program counter points just after the syscall
+   instruction and the cancellation handler should not act.
+
+   The __syscall_cancel_arch function, used for all cancellable syscalls,
+   contains two markers: __syscall_cancel_arch_start and
+   __syscall_cancel_arch_end.  The prior points to just before the initial
+   conditional branch that checks if the thread has received a cancellation
+   request, while former points to the instruction after the one responsible
+   to issue the syscall.
+
+   The function checks if the program counter (PC) from ucontext_t CTX is
+   within the start and then end boundary from the __syscall_cancel_arch
+   bridge.  Return TRUE if the PC is within the boundary, meaning the
+   syscall does not have any side effects; or FALSE otherwise.  */
+
+static bool
+cancellation_pc_check (void *ctx)
+{
+  /* Both are defined in syscall_cancel.S.  */
+  extern const char __syscall_cancel_arch_start[1];
+  extern const char __syscall_cancel_arch_end[1];
+
+  uintptr_t pc = sigcontext_get_pc (ctx);
+  return pc >= (uintptr_t) __syscall_cancel_arch_start
+	 && pc < (uintptr_t) __syscall_cancel_arch_end;
+}
+
+#endif
diff --git a/sysdeps/nptl/lowlevellock-futex.h b/sysdeps/nptl/lowlevellock-futex.h
index 66ebfe50f4..740f75bbc2 100644
--- a/sysdeps/nptl/lowlevellock-futex.h
+++ b/sysdeps/nptl/lowlevellock-futex.h
@@ -120,20 +120,13 @@
 
 /* Like lll_futex_wait, but acting as a cancellable entrypoint.  */
 # define lll_futex_wait_cancel(futexp, val, private) \
-  ({                                                                   \
-    int __oldtype = LIBC_CANCEL_ASYNC ();			       \
-    long int __err = lll_futex_wait (futexp, val, LLL_SHARED);	       \
-    LIBC_CANCEL_RESET (__oldtype);				       \
-    __err;							       \
-  })
+  lll_futex_timed_wait_cancel (futexp, val, NULL, private)
 
 /* Like lll_futex_timed_wait, but acting as a cancellable entrypoint.  */
 # define lll_futex_timed_wait_cancel(futexp, val, timeout, private) \
-  ({									   \
-    int __oldtype = LIBC_CANCEL_ASYNC ();			       	   \
-    long int __err = lll_futex_timed_wait (futexp, val, timeout, private); \
-    LIBC_CANCEL_RESET (__oldtype);					   \
-    __err;								   \
+  ({									\
+     int __op = __lll_private_flag (FUTEX_WAIT, private);		\
+     INTERNAL_SYSCALL_CANCEL (futex, futexp, __op, val, timeout);	\
   })
 
 #endif  /* !__ASSEMBLER__  */
diff --git a/sysdeps/unix/sysdep.h b/sysdeps/unix/sysdep.h
index 664d093c05..5fdd7a49eb 100644
--- a/sysdeps/unix/sysdep.h
+++ b/sysdeps/unix/sysdep.h
@@ -15,6 +15,9 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
+#ifndef _SYSDEP_UNIX_H
+#define _SYSDEP_UNIX_H 1
+
 #include <sysdeps/generic/sysdep.h>
 #include <single-thread.h>
 #include <sys/syscall.h>
@@ -24,6 +27,9 @@
 #define	SYSCALL__(name, args)	PSEUDO (__##name, name, args)
 #define	SYSCALL(name, args)	PSEUDO (name, name, args)
 
+#ifndef __ASSEMBLER__
+# include <errno.h>
+
 #define __SYSCALL_CONCAT_X(a,b)     a##b
 #define __SYSCALL_CONCAT(a,b)       __SYSCALL_CONCAT_X (a, b)
 
@@ -57,6 +63,29 @@
 #define INTERNAL_SYSCALL_CALL(...) \
   __INTERNAL_SYSCALL_DISP (__INTERNAL_SYSCALL, __VA_ARGS__)
 
+#define __INTERNAL_SYSCALL_NCS0(name) \
+  INTERNAL_SYSCALL_NCS (name, 0)
+#define __INTERNAL_SYSCALL_NCS1(name, a1) \
+  INTERNAL_SYSCALL_NCS (name, 1, a1)
+#define __INTERNAL_SYSCALL_NCS2(name, a1, a2) \
+  INTERNAL_SYSCALL_NCS (name, 2, a1, a2)
+#define __INTERNAL_SYSCALL_NCS3(name, a1, a2, a3) \
+  INTERNAL_SYSCALL_NCS (name, 3, a1, a2, a3)
+#define __INTERNAL_SYSCALL_NCS4(name, a1, a2, a3, a4) \
+  INTERNAL_SYSCALL_NCS (name, 4, a1, a2, a3, a4)
+#define __INTERNAL_SYSCALL_NCS5(name, a1, a2, a3, a4, a5) \
+  INTERNAL_SYSCALL_NCS (name, 5, a1, a2, a3, a4, a5)
+#define __INTERNAL_SYSCALL_NCS6(name, a1, a2, a3, a4, a5, a6) \
+  INTERNAL_SYSCALL_NCS (name, 6, a1, a2, a3, a4, a5, a6)
+#define __INTERNAL_SYSCALL_NCS7(name, a1, a2, a3, a4, a5, a6, a7) \
+  INTERNAL_SYSCALL_NCS (name, 7, a1, a2, a3, a4, a5, a6, a7)
+
+/* Issue a syscall defined by syscall number plus any other argument required.
+   It is similar to INTERNAL_SYSCALL_NCS macro, but without the need to pass
+   the expected argument number as third parameter.  */
+#define INTERNAL_SYSCALL_NCS_CALL(...) \
+  __INTERNAL_SYSCALL_DISP (__INTERNAL_SYSCALL_NCS, __VA_ARGS__)
+
 #define __INLINE_SYSCALL0(name) \
   INLINE_SYSCALL (name, 0)
 #define __INLINE_SYSCALL1(name, a1) \
@@ -88,42 +117,67 @@
 #define INLINE_SYSCALL_CALL(...) \
   __INLINE_SYSCALL_DISP (__INLINE_SYSCALL, __VA_ARGS__)
 
+/* Cancellation macros.  */
+#include <syscall_types.h>
+
+long int __syscall_cancel (__syscall_arg_t nr, __syscall_arg_t arg1,
+			   __syscall_arg_t arg2, __syscall_arg_t arg3,
+			   __syscall_arg_t arg4, __syscall_arg_t arg5,
+			   __syscall_arg_t arg6);
+libc_hidden_proto (__syscall_cancel);
+
+#define __SYSCALL_CANCEL0(name) \
+  __syscall_cancel (__NR_##name, 0, 0, 0, 0, 0, 0)
+#define __SYSCALL_CANCEL1(name, a1) \
+  __syscall_cancel (__NR_##name, __SSC (a1), 0, 0, 0, 0, 0)
+#define __SYSCALL_CANCEL2(name, a1, a2) \
+  __syscall_cancel (__NR_##name, __SSC (a1), __SSC (a2), 0, 0, 0, 0)
+#define __SYSCALL_CANCEL3(name, a1, a2, a3) \
+  __syscall_cancel (__NR_##name, __SSC (a1), __SSC (a2), __SSC (a3), \
+		    0, 0, 0)
+#define __SYSCALL_CANCEL4(name, a1, a2, a3, a4) \
+  __syscall_cancel (__NR_##name, __SSC (a1), __SSC (a2), __SSC (a3), \
+		    __SSC(a4), 0, 0)
+#define __SYSCALL_CANCEL5(name, a1, a2, a3, a4, a5) \
+  __syscall_cancel (__NR_##name, __SSC (a1), __SSC (a2), __SSC (a3), \
+		    __SSC(a4), __SSC (a5), 0)
+#define __SYSCALL_CANCEL6(name, a1, a2, a3, a4, a5, a6) \
+  __syscall_cancel (__NR_##name, __SSC (a1), __SSC (a2), __SSC (a3), \
+		    __SSC (a4), __SSC (a5), __SSC (a6))
+
+#define __SYSCALL_CANCEL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n
+#define __SYSCALL_CANCEL_NARGS(...) \
+  __SYSCALL_CANCEL_NARGS_X (__VA_ARGS__,7,6,5,4,3,2,1,0,)
+#define __SYSCALL_CANCEL_CONCAT_X(a,b)     a##b
+#define __SYSCALL_CANCEL_CONCAT(a,b)       __SYSCALL_CANCEL_CONCAT_X (a, b)
+#define __SYSCALL_CANCEL_DISP(b,...) \
+  __SYSCALL_CANCEL_CONCAT (b,__SYSCALL_CANCEL_NARGS(__VA_ARGS__))(__VA_ARGS__)
+
+#define __SYSCALL_CANCEL_CALL(...) \
+  __SYSCALL_CANCEL_DISP (__SYSCALL_CANCEL, __VA_ARGS__)
+
+/* Issue a cancellable syscall defined by syscall number NAME plus any other
+   argument required.  If an error occurs its value is returned as an negative
+   number unmodified and errno is not set.  */
+#define INTERNAL_SYSCALL_CANCEL(name, args...) \
+  __SYSCALL_CANCEL_CALL (name, args)
+
+/* Issue a cancellable syscall defined first argument plus any other argument
+   required.  If and error occurs its value, the macro returns -1 and sets
+   errno accordingly.  */
 #if IS_IN (rtld)
-/* All cancellation points are compiled out in the dynamic loader.  */
-# define NO_SYSCALL_CANCEL_CHECKING 1
+/* The loader does not need to handle thread cancellation, use direct
+   syscall instead.  */
+# define SYSCALL_CANCEL(...) INLINE_SYSCALL_CALL (__VA_ARGS__)
 #else
-# define NO_SYSCALL_CANCEL_CHECKING SINGLE_THREAD_P
+# define SYSCALL_CANCEL(...) \
+  ({									\
+    long int sc_ret = __SYSCALL_CANCEL_CALL (__VA_ARGS__);		\
+    SYSCALL_CANCEL_RET ((sc_ret));					\
+   })
 #endif
 
-#define SYSCALL_CANCEL(...) \
-  ({									     \
-    long int sc_ret;							     \
-    if (NO_SYSCALL_CANCEL_CHECKING)					     \
-      sc_ret = INLINE_SYSCALL_CALL (__VA_ARGS__); 			     \
-    else								     \
-      {									     \
-	int sc_cancel_oldtype = LIBC_CANCEL_ASYNC ();			     \
-	sc_ret = INLINE_SYSCALL_CALL (__VA_ARGS__);			     \
-        LIBC_CANCEL_RESET (sc_cancel_oldtype);				     \
-      }									     \
-    sc_ret;								     \
-  })
-
-/* Issue a syscall defined by syscall number plus any other argument
-   required.  Any error will be returned unmodified (including errno).  */
-#define INTERNAL_SYSCALL_CANCEL(...) \
-  ({									     \
-    long int sc_ret;							     \
-    if (NO_SYSCALL_CANCEL_CHECKING) 					     \
-      sc_ret = INTERNAL_SYSCALL_CALL (__VA_ARGS__); 			     \
-    else								     \
-      {									     \
-	int sc_cancel_oldtype = LIBC_CANCEL_ASYNC ();			     \
-	sc_ret = INTERNAL_SYSCALL_CALL (__VA_ARGS__);			     \
-        LIBC_CANCEL_RESET (sc_cancel_oldtype);				     \
-      }									     \
-    sc_ret;								     \
-  })
+#endif /* __ASSEMBLER__  */
 
 /* Machine-dependent sysdep.h files are expected to define the macro
    PSEUDO (function_name, syscall_name) to emit assembly code to define the
@@ -153,3 +207,5 @@
 #ifndef INLINE_SYSCALL
 #define INLINE_SYSCALL(name, nr, args...) __syscall_##name (args)
 #endif
+
+#endif /* _SYSDEP_UNIX_H */
diff --git a/sysdeps/unix/sysv/linux/socketcall.h b/sysdeps/unix/sysv/linux/socketcall.h
index 3084623216..9c317f26df 100644
--- a/sysdeps/unix/sysv/linux/socketcall.h
+++ b/sysdeps/unix/sysv/linux/socketcall.h
@@ -89,13 +89,32 @@
   })
 
 
-#define SOCKETCALL_CANCEL(name, args...)				\
-  ({									\
-    int oldtype = LIBC_CANCEL_ASYNC ();					\
-    long int sc_ret = __SOCKETCALL (SOCKOP_##name, args);		\
-    LIBC_CANCEL_RESET (oldtype);					\
-    sc_ret;								\
-  })
-
+#define __SOCKETCALL_CANCEL1(__name, __a1) \
+  SYSCALL_CANCEL (socketcall, __name, \
+     ((long int [1]) { (long int) __a1 }))
+#define __SOCKETCALL_CANCEL2(__name, __a1, __a2) \
+  SYSCALL_CANCEL (socketcall, __name, \
+     ((long int [2]) { (long int) __a1, (long int) __a2 }))
+#define __SOCKETCALL_CANCEL3(__name, __a1, __a2, __a3) \
+  SYSCALL_CANCEL (socketcall, __name, \
+     ((long int [3]) { (long int) __a1, (long int) __a2, (long int) __a3 }))
+#define __SOCKETCALL_CANCEL4(__name, __a1, __a2, __a3, __a4) \
+  SYSCALL_CANCEL (socketcall, __name, \
+     ((long int [4]) { (long int) __a1, (long int) __a2, (long int) __a3, \
+                       (long int) __a4 }))
+#define __SOCKETCALL_CANCEL5(__name, __a1, __a2, __a3, __a4, __a5) \
+  SYSCALL_CANCEL (socketcall, __name, \
+     ((long int [5]) { (long int) __a1, (long int) __a2, (long int) __a3, \
+                       (long int) __a4, (long int) __a5 }))
+#define __SOCKETCALL_CANCEL6(__name, __a1, __a2, __a3, __a4, __a5, __a6) \
+  SYSCALL_CANCEL (socketcall, __name, \
+     ((long int [6]) { (long int) __a1, (long int) __a2, (long int) __a3, \
+                       (long int) __a4, (long int) __a5, (long int) __a6 }))
+
+#define __SOCKETCALL_CANCEL(...) __SOCKETCALL_DISP (__SOCKETCALL_CANCEL,\
+						    __VA_ARGS__)
+
+#define SOCKETCALL_CANCEL(name, args...) \
+   __SOCKETCALL_CANCEL (SOCKOP_##name, args)
 
 #endif /* sys/socketcall.h */
diff --git a/sysdeps/unix/sysv/linux/syscall_cancel.c b/sysdeps/unix/sysv/linux/syscall_cancel.c
new file mode 100644
index 0000000000..56ae22b505
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/syscall_cancel.c
@@ -0,0 +1,59 @@
+/* Default cancellation syscall bridge.
+   Copyright (C) 2021 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <sysdep.h>
+#include <pthreadP.h>
+
+/* This is the generic version of the cancellable syscall code which
+   adds the label guards (__syscall_cancel_arch_{start,end}) used
+   on SIGCANCEL sigcancel_handler (nptl-init.c) to check if the cancelled
+   syscall have side-effects that need to be signaled to program.
+
+   This implementation should be used a reference one to document the
+   implementation constraints: the __syscall_cancel_arch_end should point
+   to the immediate next instruction after the syscall one.  This is because
+   kernel will signal interrupted syscall with side effects by setting
+   the signal frame program counter (on the ucontext_t third argument from
+   SA_SIGINFO signal handler) right after the syscall instruction.
+
+   If the INTERNAL_SYSCALL_NCS macro use more instructions to get the
+   error condition from kernel (as for powerpc and sparc), uses an
+   out of the line helper (as for ARM thumb), or uses a kernel helper
+   gate (as for i686 or ia64) the architecture should adjust the
+   macro or provide a custom __syscall_cancel_arch implementation.   */
+long int
+__syscall_cancel_arch (volatile int *ch, __syscall_arg_t nr,
+		       __syscall_arg_t a1, __syscall_arg_t a2,
+		       __syscall_arg_t a3, __syscall_arg_t a4,
+		       __syscall_arg_t a5, __syscall_arg_t a6)
+{
+#define ADD_LABEL(__label)		\
+  asm volatile (			\
+    ".global " __label "\t\n"		\
+    __label ":\n");
+
+  ADD_LABEL ("__syscall_cancel_arch_start");
+  if (__glibc_unlikely (*ch & CANCELED_BITMASK))
+    __do_cancel ();
+
+  long int result = INTERNAL_SYSCALL_NCS_CALL (nr, a1, a2, a3, a4, a5, a6);
+  ADD_LABEL ("__syscall_cancel_arch_end");
+  if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (result)))
+    return -INTERNAL_SYSCALL_ERRNO (result);
+  return result;
+}
diff --git a/sysdeps/unix/sysv/linux/sysdep-cancel.h b/sysdeps/unix/sysv/linux/sysdep-cancel.h
index c64cfff37c..6e7483a56d 100644
--- a/sysdeps/unix/sysv/linux/sysdep-cancel.h
+++ b/sysdeps/unix/sysv/linux/sysdep-cancel.h
@@ -21,17 +21,5 @@
 #define _SYSDEP_CANCEL_H
 
 #include <sysdep.h>
-#include <tls.h>
-#include <errno.h>
-
-/* Set cancellation mode to asynchronous.  */
-extern int __pthread_enable_asynccancel (void);
-libc_hidden_proto (__pthread_enable_asynccancel)
-#define LIBC_CANCEL_ASYNC() __pthread_enable_asynccancel ()
-
-/* Reset to previous cancellation mode.  */
-extern void __pthread_disable_asynccancel (int oldtype);
-libc_hidden_proto (__pthread_disable_asynccancel)
-#define LIBC_CANCEL_RESET(oldtype) __pthread_disable_asynccancel (oldtype)
 
 #endif
diff --git a/sysdeps/unix/sysv/linux/sysdep.h b/sysdeps/unix/sysv/linux/sysdep.h
index 3ef72dc805..be3e1b3dc4 100644
--- a/sysdeps/unix/sysv/linux/sysdep.h
+++ b/sysdeps/unix/sysv/linux/sysdep.h
@@ -59,6 +59,15 @@
     -1l;					\
   })
 
+/* The return error from cancellable syscall has the same semantic as non
+   cancellable ones.  */
+#define SYSCALL_CANCEL_RET(__ret)				\
+  ({								\
+    __glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (__ret))		\
+    ? SYSCALL_ERROR_LABEL (INTERNAL_SYSCALL_ERRNO (__ret))	\
+    : __ret;							\
+   })
+
 /* Provide a dummy argument that can be used to force register
    alignment for register pairs if required by the syscall ABI.  */
 #ifdef __ASSUME_ALIGNED_REGISTER_PAIRS


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2021-06-08 20:48 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-08 20:48 [glibc/azanella/pthread-multiple-fixes] nptl: Fix Race conditions in pthread cancellation (BZ#12683, BZ#14147) Adhemerval Zanella

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).