Blob Blame History Raw
From 15409324f1974d41c183904ad575da7188058c1c Mon Sep 17 00:00:00 2001
From: Chet Ramey <chet.ramey@case.edu>
Date: Wed, 17 Nov 2021 16:47:24 -0500
Subject: [PATCH] Bash-5.1 patch 12: fix race condition with child processes
 and resetting trapped signals

---
 command.h     |  1 +
 execute_cmd.c |  8 +++++++-
 jobs.c        |  2 ++
 nojobs.c      |  2 ++
 patchlevel.h  |  2 +-
 sig.c         | 10 +++++++++-
 subst.c       |  2 ++
 trap.c        | 26 ++++++++++++++++++++++++++
 8 files changed, 50 insertions(+), 3 deletions(-)

diff --git a/command.h b/command.h
index 914198f9..b8477528 100644
--- a/command.h
+++ b/command.h
@@ -124,6 +124,7 @@ enum command_type { cm_for, cm_case, cm_while, cm_if, cm_simple, cm_select,
 #define SUBSHELL_PROCSUB 0x20	/* subshell caused by <(command) or >(command) */
 #define SUBSHELL_COPROC	0x40	/* subshell from a coproc pipeline */
 #define SUBSHELL_RESETTRAP 0x80	/* subshell needs to reset trap strings on first call to trap */
+#define SUBSHELL_IGNTRAP 0x100  /* subshell should reset trapped signals from trap_handler */
 
 /* A structure which represents a word. */
 typedef struct word_desc {
diff --git a/execute_cmd.c b/execute_cmd.c
index 90129e06..425679a2 100644
--- a/execute_cmd.c
+++ b/execute_cmd.c
@@ -1547,6 +1547,9 @@ execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
   clear_pending_traps ();
   reset_signal_handlers ();
   subshell_environment |= SUBSHELL_RESETTRAP;
+  /* Note that signal handlers have been reset, so we should no longer
+    reset the handler and resend trapped signals to ourselves. */
+  subshell_environment &= ~SUBSHELL_IGNTRAP;
 
   /* We are in a subshell, so forget that we are running a trap handler or
      that the signal handler has changed (we haven't changed it!) */
@@ -4320,7 +4323,8 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
 	  already_forked = 1;
 	  cmdflags |= CMD_NO_FORK;
 
-	  subshell_environment = SUBSHELL_FORK;		/* XXX */
+	  /* We redo some of what make_child() does with SUBSHELL_IGNTRAP */
+	  subshell_environment = SUBSHELL_FORK|SUBSHELL_IGNTRAP;	/* XXX */
 	  if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
 	    subshell_environment |= SUBSHELL_PIPE;
 	  if (async)
@@ -4574,6 +4578,7 @@ run_builtin:
 	     trap strings if we run trap to change a signal disposition. */
 	  reset_signal_handlers ();
 	  subshell_environment |= SUBSHELL_RESETTRAP;
+	  subshell_environment &= ~SUBSHELL_IGNTRAP;
 
 	  if (async)
 	    {
@@ -5514,6 +5519,7 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
       reset_terminating_signals ();	/* XXX */
       /* Cancel traps, in trap.c. */
       restore_original_signals ();
+      subshell_environment &= ~SUBSHELL_IGNTRAP;
 
 #if defined (JOB_CONTROL)
       FREE (p);
diff --git a/jobs.c b/jobs.c
index a581f305..7c3b6e83 100644
--- a/jobs.c
+++ b/jobs.c
@@ -2217,6 +2217,8 @@ make_child (command, flags)
 	 signals to the default state for a new process. */
       pid_t mypid;
 
+      subshell_environment |= SUBSHELL_IGNTRAP;
+
       /* If this ends up being changed to modify or use `command' in the
 	 child process, go back and change callers who free `command' in
 	 the child process when this returns. */
diff --git a/nojobs.c b/nojobs.c
index c5fc83d9..f2563ca0 100644
--- a/nojobs.c
+++ b/nojobs.c
@@ -575,6 +575,8 @@ make_child (command, flags)
 	last_asynchronous_pid = getpid ();
 #endif
 
+      subshell_environment |= SUBSHELL_IGNTRAP;
+
       default_tty_job_signals ();
     }
   else
diff --git a/patchlevel.h b/patchlevel.h
index 8b14f289..eb2aca52 100644
--- a/patchlevel.h
+++ b/patchlevel.h
@@ -25,6 +25,6 @@
    regexp `^#define[ 	]*PATCHLEVEL', since that's what support/mkversion.sh
    looks for to find the patch level (for the sccs version string). */
 
-#define PATCHLEVEL 11
+#define PATCHLEVEL 12
 
 #endif /* _PATCHLEVEL_H_ */
diff --git a/sig.c b/sig.c
index 6964d862..e6537d26 100644
--- a/sig.c
+++ b/sig.c
@@ -55,7 +55,8 @@
 #  include "bashhist.h"
 #endif
 
-extern void initialize_siglist ();
+extern void initialize_siglist PARAMS((void));
+extern void set_original_signal PARAMS((int, SigHandler *));
 
 #if !defined (JOB_CONTROL)
 extern void initialize_job_signals PARAMS((void));
@@ -255,6 +256,13 @@ initialize_terminating_signals ()
       sigaction (XSIG (i), &act, &oact);
       XHANDLER(i) = oact.sa_handler;
       XSAFLAGS(i) = oact.sa_flags;
+
+#if 0
+      set_original_signal (XSIG(i), XHANDLER(i));	/* optimization */
+#else
+      set_original_signal (XSIG(i), act.sa_handler);	/* optimization */
+#endif
+
       /* Don't do anything with signals that are ignored at shell entry
 	 if the shell is not interactive. */
       /* XXX - should we do this for interactive shells, too? */
diff --git a/subst.c b/subst.c
index 462752de..327de083 100644
--- a/subst.c
+++ b/subst.c
@@ -5951,6 +5951,7 @@ process_substitute (string, open_for_read_in_child)
       free_pushed_string_input ();
       /* Cancel traps, in trap.c. */
       restore_original_signals ();	/* XXX - what about special builtins? bash-4.2 */
+      subshell_environment &= ~SUBSHELL_IGNTRAP;
       QUIT;	/* catch any interrupts we got post-fork */
       setup_async_signals ();
 #if 0
@@ -6382,6 +6383,7 @@ command_substitute (string, quoted, flags)
 	}	
       QUIT;	/* catch any interrupts we got post-fork */
       subshell_environment |= SUBSHELL_RESETTRAP;
+      subshell_environment &= ~SUBSHELL_IGNTRAP;
     }
 
 #if defined (JOB_CONTROL)
diff --git a/trap.c b/trap.c
index c7f8ded5..1b27fb3a 100644
--- a/trap.c
+++ b/trap.c
@@ -481,6 +481,32 @@ trap_handler (sig)
       SIGRETURN (0);
     }
 
+  /* This means we're in a subshell, but have not yet reset the handler for
+     trapped signals. We're not supposed to execute the trap in this situation;
+     we should restore the original signal and resend the signal to ourselves
+     to preserve the Posix "signal traps that are not being ignored shall be
+     set to the default action" semantics. */
+  if ((subshell_environment & SUBSHELL_IGNTRAP) && trap_list[sig] != (char *)IGNORE_SIG)
+    {
+      sigset_t mask;
+
+      /* Paranoia */
+      if (original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER)
+	original_signals[sig] = SIG_DFL;
+
+      restore_signal (sig);
+
+      /* Make sure we let the signal we just caught through */
+      sigemptyset (&mask);
+      sigprocmask (SIG_SETMASK, (sigset_t *)NULL, &mask);
+      sigdelset (&mask, sig);
+      sigprocmask (SIG_SETMASK, &mask, (sigset_t *)NULL);
+
+      kill (getpid (), sig);
+
+      SIGRETURN (0);
+    }
+
   if ((sig >= NSIG) ||
       (trap_list[sig] == (char *)DEFAULT_SIG) ||
       (trap_list[sig] == (char *)IGNORE_SIG))
-- 
2.31.1