ded59d
From 436a604b7dc741fc76b5a6704c6cd8bb178518e7 Mon Sep 17 00:00:00 2001
ded59d
From: Adam Yi <ayi@janestreet.com>
ded59d
Date: Tue, 7 Mar 2023 07:30:02 -0500
ded59d
Subject: posix: Fix system blocks SIGCHLD erroneously [BZ #30163]
ded59d
ded59d
Fix bug that SIGCHLD is erroneously blocked forever in the following
ded59d
scenario:
ded59d
ded59d
1. Thread A calls system but hasn't returned yet
ded59d
2. Thread B calls another system but returns
ded59d
ded59d
SIGCHLD would be blocked forever in thread B after its system() returns,
ded59d
even after the system() in thread A returns.
ded59d
ded59d
Although POSIX does not require, glibc system implementation aims to be
ded59d
thread and cancellation safe. This bug was introduced in
ded59d
5fb7fc96350575c9adb1316833e48ca11553be49 when we moved reverting signal
ded59d
mask to happen when the last concurrently running system returns,
ded59d
despite that signal mask is per thread. This commit reverts this logic
ded59d
and adds a test.
ded59d
ded59d
Signed-off-by: Adam Yi <ayi@janestreet.com>
ded59d
Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
ded59d
ded59d
[DJ: Edited to use integer sleep() instead of nanosleep() dependency rabbit hole]
ded59d
diff --git a/stdlib/tst-system.c b/stdlib/tst-system.c
ded59d
index 634acfe264..47a0afe6bf 100644
ded59d
--- a/stdlib/tst-system.c
ded59d
+++ b/stdlib/tst-system.c
ded59d
@@ -25,6 +25,7 @@
ded59d
 #include <support/check.h>
ded59d
 #include <support/temp_file.h>
ded59d
 #include <support/support.h>
ded59d
+#include <support/xthread.h>
ded59d
 #include <support/xunistd.h>
ded59d
 
ded59d
 static char *tmpdir;
ded59d
@@ -71,6 +72,20 @@ call_system (void *closure)
ded59d
     }
ded59d
 }
ded59d
 
ded59d
+static void *
ded59d
+sleep_and_check_sigchld (void *closure)
ded59d
+{
ded59d
+  double *seconds = (double *) closure;
ded59d
+  char cmd[namemax];
ded59d
+  sprintf (cmd, "sleep %lf" , *seconds);
ded59d
+  TEST_COMPARE (system (cmd), 0);
ded59d
+
ded59d
+  sigset_t blocked = {0};
ded59d
+  TEST_COMPARE (sigprocmask (SIG_BLOCK, NULL, &blocked), 0);
ded59d
+  TEST_COMPARE (sigismember (&blocked, SIGCHLD), 0);
ded59d
+  return NULL;
ded59d
+}
ded59d
+
ded59d
 static int
ded59d
 do_test (void)
ded59d
 {
ded59d
@@ -154,6 +169,17 @@ do_test (void)
ded59d
     xchmod (_PATH_BSHELL, st.st_mode);
ded59d
   }
ded59d
 
ded59d
+  {
ded59d
+    pthread_t long_sleep_thread = xpthread_create (NULL,
ded59d
+                                                   sleep_and_check_sigchld,
ded59d
+                                                   &(double) { 2 });
ded59d
+    pthread_t short_sleep_thread = xpthread_create (NULL,
ded59d
+                                                    sleep_and_check_sigchld,
ded59d
+                                                    &(double) { 1 });
ded59d
+    xpthread_join (short_sleep_thread);
ded59d
+    xpthread_join (long_sleep_thread);
ded59d
+  }
ded59d
+
ded59d
   TEST_COMPARE (system (""), 0);
ded59d
 
ded59d
   return 0;
ded59d
diff --git a/support/shell-container.c b/support/shell-container.c
ded59d
index ffa3378b5e..b1f9e793c1 100644
ded59d
--- a/support/shell-container.c
ded59d
+++ b/support/shell-container.c
ded59d
@@ -169,6 +170,31 @@ kill_func (char **argv)
ded59d
   return 0;
ded59d
 }
ded59d
 
ded59d
+/* Emulate the "/bin/sleep" command.  No suffix support.  Options are
ded59d
+   ignored.  */
ded59d
+static int
ded59d
+sleep_func (char **argv)
ded59d
+{
ded59d
+  if (argv[0] == NULL)
ded59d
+    {
ded59d
+      fprintf (stderr, "sleep: missing operand\n");
ded59d
+      return 1;
ded59d
+    }
ded59d
+  char *endptr = NULL;
ded59d
+  long sec = strtol (argv[0], &endptr, 0);
ded59d
+  if (endptr == argv[0] || errno == ERANGE || sec < 0)
ded59d
+    {
ded59d
+      fprintf (stderr, "sleep: invalid time interval '%s'\n", argv[0]);
ded59d
+      return 1;
ded59d
+    }
ded59d
+  if (sleep (sec) < 0)
ded59d
+    {
ded59d
+      fprintf (stderr, "sleep: failed to nanosleep\n");
ded59d
+      return 1;
ded59d
+    }
ded59d
+  return 0;
ded59d
+}
ded59d
+
ded59d
 /* This is a list of all the built-in commands we understand.  */
ded59d
 static struct {
ded59d
   const char *name;
ded59d
@@ -179,6 +206,7 @@ static struct {
ded59d
   { "cp", copy_func },
ded59d
   { "exit", exit_func },
ded59d
   { "kill", kill_func },
ded59d
+  { "sleep", sleep_func },
ded59d
   { NULL, NULL }
ded59d
 };
ded59d
 
ded59d
diff --git a/sysdeps/posix/system.c b/sysdeps/posix/system.c
ded59d
index 2335a99184..d77720a625 100644
ded59d
--- a/sysdeps/posix/system.c
ded59d
+++ b/sysdeps/posix/system.c
ded59d
@@ -179,16 +179,16 @@ do_system (const char *line)
ded59d
       as if the shell had terminated using _exit(127).  */
ded59d
    status = W_EXITCODE (127, 0);
ded59d
 
ded59d
+  /* sigaction can not fail with SIGINT/SIGQUIT used with old
ded59d
+     disposition.  Same applies for sigprocmask.  */
ded59d
   DO_LOCK ();
ded59d
   if (SUB_REF () == 0)
ded59d
     {
ded59d
-      /* sigaction can not fail with SIGINT/SIGQUIT used with old
ded59d
-	 disposition.  Same applies for sigprocmask.  */
ded59d
       __sigaction (SIGINT, &intr, NULL);
ded59d
       __sigaction (SIGQUIT, &quit, NULL);
ded59d
-      __sigprocmask (SIG_SETMASK, &omask, NULL);
ded59d
     }
ded59d
   DO_UNLOCK ();
ded59d
+  __sigprocmask (SIG_SETMASK, &omask, NULL);
ded59d
 
ded59d
   if (ret != 0)
ded59d
     __set_errno (ret);