about summary refs log tree commit diff
path: root/sysdeps/unix/sysv
diff options
context:
space:
mode:
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>2019-12-09 14:33:33 -0300
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>2020-02-19 13:46:31 -0300
commit27d83441a2f900059d0421339d0e8c27220ffa36 (patch)
tree22b8a2d04bfc965462d96a99b32da18fc17eb2b5 /sysdeps/unix/sysv
parent8d42bf859a289944749d9f978c076cd318119867 (diff)
downloadglibc-27d83441a2f900059d0421339d0e8c27220ffa36.tar.gz
glibc-27d83441a2f900059d0421339d0e8c27220ffa36.tar.xz
glibc-27d83441a2f900059d0421339d0e8c27220ffa36.zip
Block all signals on timer_create thread (BZ#10815)
The behavior of the signal mask on threads created by timer_create
for SIGEV_THREAD timers are implementation-defined and glibc explicit
unblocks all signals before calling the user-defined function.

This behavior, although not incorrect standard-wise, opens a race if a
program using a blocked rt-signal plus sigwaitinfo (and without an
installed signal handler for the rt-signal) receives a signal while
executing the used-defined function for SIGEV_THREAD.

A better alternative discussed in bug report is to rather block all
signals (besides the internal ones not available to application
usage).

This patch fixes this issue by only unblocking SIGSETXID (used on
set*uid function) and SIGCANCEL (used for thread cancellation).

Checked on x86_64-linux-gnu and i686-linux-gnu.
Diffstat (limited to 'sysdeps/unix/sysv')
-rw-r--r--sysdeps/unix/sysv/linux/internal-signals.h21
-rw-r--r--sysdeps/unix/sysv/linux/timer_routines.c94
2 files changed, 55 insertions, 60 deletions
diff --git a/sysdeps/unix/sysv/linux/internal-signals.h b/sysdeps/unix/sysv/linux/internal-signals.h
index e958522a07..3fbd4807d1 100644
--- a/sysdeps/unix/sysv/linux/internal-signals.h
+++ b/sysdeps/unix/sysv/linux/internal-signals.h
@@ -58,6 +58,11 @@ static const sigset_t sigall_set = {
    .__val = {[0 ...  _SIGSET_NWORDS-1 ] =  -1 }
 };
 
+static const sigset_t sigtimer_set = {
+  .__val = { [0]                      = __sigmask (SIGTIMER),
+             [1 ... _SIGSET_NWORDS-1] = 0 }
+};
+
 /* Block all signals, including internal glibc ones.  */
 static inline void
 __libc_signal_block_all (sigset_t *set)
@@ -76,6 +81,22 @@ __libc_signal_block_app (sigset_t *set)
 			 _NSIG / 8);
 }
 
+/* Block only SIGTIMER and return the previous set on SET.  */
+static inline void
+__libc_signal_block_sigtimer (sigset_t *set)
+{
+  INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_BLOCK, &sigtimer_set, set,
+			 _NSIG / 8);
+}
+
+/* Unblock only SIGTIMER and return the previous set on SET.  */
+static inline void
+__libc_signal_unblock_sigtimer (sigset_t *set)
+{
+  INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_UNBLOCK, &sigtimer_set, set,
+			 _NSIG / 8);
+}
+
 /* Restore current process signal mask.  */
 static inline void
 __libc_signal_restore_set (const sigset_t *set)
diff --git a/sysdeps/unix/sysv/linux/timer_routines.c b/sysdeps/unix/sysv/linux/timer_routines.c
index 00b7e018ba..63083f6f91 100644
--- a/sysdeps/unix/sysv/linux/timer_routines.c
+++ b/sysdeps/unix/sysv/linux/timer_routines.c
@@ -42,15 +42,9 @@ struct thread_start_data
 static void *
 timer_sigev_thread (void *arg)
 {
-  /* The parent thread has all signals blocked.  This is a bit
-     surprising for user code, although valid.  We unblock all
-     signals.  */
-  sigset_t ss;
-  sigemptyset (&ss);
-  INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_SETMASK, &ss, NULL, _NSIG / 8);
+  __libc_signal_unblock_sigtimer (NULL);
 
   struct thread_start_data *td = (struct thread_start_data *) arg;
-
   void (*thrfunc) (sigval_t) = td->thrfunc;
   sigval_t sival = td->sival;
 
@@ -68,65 +62,49 @@ timer_sigev_thread (void *arg)
 static void *
 timer_helper_thread (void *arg)
 {
-  /* Wait for the SIGTIMER signal, allowing the setXid signal, and
-     none else.  */
-  sigset_t ss;
-  sigemptyset (&ss);
-  __sigaddset (&ss, SIGTIMER);
-
   /* Endless loop of waiting for signals.  The loop is only ended when
      the thread is canceled.  */
   while (1)
     {
       siginfo_t si;
 
-      /* sigwaitinfo cannot be used here, since it deletes
-	 SIGCANCEL == SIGTIMER from the set.  */
-
-      /* XXX The size argument hopefully will have to be changed to the
-	 real size of the user-level sigset_t.  */
-      int result = SYSCALL_CANCEL (rt_sigtimedwait, &ss, &si, NULL, _NSIG / 8);
-
-      if (result > 0)
+      while (sigwaitinfo (&sigtimer_set, &si) < 0);
+      if (si.si_code == SI_TIMER)
 	{
-	  if (si.si_code == SI_TIMER)
-	    {
-	      struct timer *tk = (struct timer *) si.si_ptr;
+	  struct timer *tk = (struct timer *) si.si_ptr;
+
+	  /* Check the timer is still used and will not go away
+	     while we are reading the values here.  */
+	  pthread_mutex_lock (&__active_timer_sigev_thread_lock);
 
-	      /* Check the timer is still used and will not go away
-		 while we are reading the values here.  */
-	      pthread_mutex_lock (&__active_timer_sigev_thread_lock);
+	  struct timer *runp = __active_timer_sigev_thread;
+	  while (runp != NULL)
+	    if (runp == tk)
+	      break;
+	  else
+	    runp = runp->next;
 
-	      struct timer *runp = __active_timer_sigev_thread;
-	      while (runp != NULL)
-		if (runp == tk)
-		  break;
-		else
-		  runp = runp->next;
+	  if (runp != NULL)
+	    {
+	      struct thread_start_data *td = malloc (sizeof (*td));
 
-	      if (runp != NULL)
+	      /* There is not much we can do if the allocation fails.  */
+	      if (td != NULL)
 		{
-		  struct thread_start_data *td = malloc (sizeof (*td));
-
-		  /* There is not much we can do if the allocation fails.  */
-		  if (td != NULL)
-		    {
-		      /* This is the signal we are waiting for.  */
-		      td->thrfunc = tk->thrfunc;
-		      td->sival = tk->sival;
-
-		      pthread_t th;
-		      (void) pthread_create (&th, &tk->attr,
-					     timer_sigev_thread, td);
-		    }
-		}
+		  /* This is the signal we are waiting for.  */
+		  td->thrfunc = tk->thrfunc;
+		  td->sival = tk->sival;
 
-	      pthread_mutex_unlock (&__active_timer_sigev_thread_lock);
+		  pthread_t th;
+		  pthread_create (&th, &tk->attr, timer_sigev_thread, td);
+		}
 	    }
-	  else if (si.si_code == SI_TKILL)
-	    /* The thread is canceled.  */
-	    pthread_exit (NULL);
+
+	  pthread_mutex_unlock (&__active_timer_sigev_thread_lock);
 	}
+      else if (si.si_code == SI_TKILL)
+	/* The thread is canceled.  */
+	pthread_exit (NULL);
     }
 }
 
@@ -160,14 +138,10 @@ __start_helper_thread (void)
 
   /* Block all signals in the helper thread but SIGSETXID.  To do this
      thoroughly we temporarily have to block all signals here.  The
-     helper can lose wakeups if SIGCANCEL is not blocked throughout,
-     but sigfillset omits it SIGSETXID.  So, we add SIGCANCEL back
-     explicitly here.  */
+     helper can lose wakeups if SIGTIMER is not blocked throughout.  */
   sigset_t ss;
-  sigset_t oss;
-  sigfillset (&ss);
-  __sigaddset (&ss, SIGCANCEL);
-  INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_SETMASK, &ss, &oss, _NSIG / 8);
+  __libc_signal_block_app (&ss);
+  __libc_signal_block_sigtimer (NULL);
 
   /* Create the helper thread for this timer.  */
   pthread_t th;
@@ -177,7 +151,7 @@ __start_helper_thread (void)
     __helper_tid = ((struct pthread *) th)->tid;
 
   /* Restore the signal mask.  */
-  INTERNAL_SYSCALL_CALL (rt_sigprocmask, SIG_SETMASK, &oss, NULL, _NSIG / 8);
+  __libc_signal_restore_set (&ss);
 
   /* No need for the attribute anymore.  */
   (void) pthread_attr_destroy (&attr);