]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
fix for potential read builtin crash if bash gets a signal before read reads any...
authorChet Ramey <chet.ramey@case.edu>
Thu, 18 Sep 2025 21:54:55 +0000 (17:54 -0400)
committerChet Ramey <chet.ramey@case.edu>
Thu, 18 Sep 2025 21:54:55 +0000 (17:54 -0400)
15 files changed:
CWRU/CWRU.chlog
MANIFEST
builtins/read.def
builtins/wait.def
command.h
doc/bash.1
doc/bashref.texi
execute_cmd.c
jobs.c
jobs.h
tests/cond.right
tests/trap.right
tests/trap.tests
tests/trap10.sub [new file with mode: 0644]
tests/trap2.sub

index 8d453e0c08a9727b07eada3cf61ed3e12c32ae0e..ae46e35021cdc37cb20b0f53e9314a0ba777ad56 100644 (file)
@@ -11768,3 +11768,30 @@ lib/sh/strtrans.c
          pointer when encountering an invalid wide character by using our
          own mbstate_t and reinitializing it on EILSEQ.
          Report and patch from Grisha Levit <grishalevit@gmail.com>
+
+                                  9/17
+                                  ----
+builtins/read.def
+       - read_builtin: make sure i is >= 0 after a timeout longjmp before
+         trying to terminate input_string
+         From a report from Duncan Roe <duncan_roe@optusnet.com.au>
+
+jobs.c,jobs.h
+       - wait_for_background_pids: now takes a new first argument, WFLAGS.
+         Used by wait builtin to pass through JWAIT_FORCE
+
+builtins/wait.def
+       - wait_builtin: if wait doesn't have any pid or job arguments, make
+         sure to pass JWAIT_FORCE through to wait_for_background_pids so
+         we make sure to wait for all background jobs to terminate
+         Inspired by report from Robert Elz <kre@munnari.oz.au>
+
+doc/bash.1,doc/bashref.texi
+       - wait: update description of -f to include wait with no arguments
+
+                                  9/18
+                                  ----
+execute_cmd.c
+       - execute_function: save and restore the_printed_command_except_trap
+         (reflected in $BASH_COMMAND) around shell function calls
+         From a report by Mike Jonkmans <bashbug@jonkmans.nl>
index 5c084e57726da1191e992c3e47a4c4e6e808553c..dd42fce767c6dce05087246a338f80bd3fbcb707 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -1616,6 +1616,7 @@ tests/trap6.sub           f
 tests/trap7.sub                f
 tests/trap8.sub                f
 tests/trap9.sub                f
+tests/trap10.sub       f
 tests/type.tests       f
 tests/type.right       f
 tests/type1.sub                f
index 5135e2b11ba0ccebe7585c635c90699c78ddd1a5..04366ec96f38adf394f2a561dacddc8e9db81c4d 100644 (file)
@@ -538,7 +538,8 @@ read_builtin (WORD_LIST *list)
             so we have to save input_string temporarily, run the unwind-
             protects, then restore input_string so we can use it later */
          orig_input_string = 0;
-         input_string[i] = '\0';       /* make sure it's terminated */
+         if (i >= 0)
+           input_string[i] = '\0';     /* make sure it's terminated */
          if (i == 0)
            {
              t = (char *)xmalloc (1);
index c8c735aa3ba2958841d1b952445e3c97cf25ef11..34d9315142c9997f9189632b9c783e3f339da0de 100644 (file)
@@ -251,7 +251,7 @@ wait_builtin (WORD_LIST *list)
      currently active background processes. */
   if (list == 0)
     {
-      opt = wait_for_background_pids (&pstat);
+      opt = wait_for_background_pids (wflags, &pstat);
 #if 0
       /* Compatibility with NetBSD sh: don't set VNAME since it doesn't
         correspond to the return status. */
index 189b00def13c36c5ecedeffdf2c35babc7b19967..f8196b5feaa7a6d57614f971c2c2402d9414d852 100644 (file)
--- a/command.h
+++ b/command.h
@@ -192,6 +192,7 @@ typedef struct element {
 #define CMD_LASTPIPE       0x2000
 #define CMD_STDPATH        0x4000      /* use standard path for command lookup */
 #define CMD_TRY_OPTIMIZING  0x8000     /* try to optimize this simple command */
+#define CMD_WANT_ERR_TRAP      0x10000
 
 /* What a command looks like. */
 typedef struct command {
index 75bd2ba3d9282df766bef4455d8c3d0cdefe0075..8724835d4a764b5fdf5b3ce56f1aa605b8541933 100644 (file)
@@ -13038,6 +13038,8 @@ This is useful only when used with the \fB\-n\fP option.
 Supplying the \fB\-f\fP option, when job control is enabled,
 forces \fBwait\fP to wait for each \fIid\fP to terminate before
 returning its status, instead of returning when it changes status.
+If there are no \fIid\fP arguments,
+\fBwait\fP waits until all background processes have terminated.
 .IP
 If none of the \fIid\fPs specify one of the shell's active child
 processes, the return status is 127.
index d43e192d2671eb6fc3e6f4ac7bf6e69a396b1f98..41dfd35e308d8a875159dcb330aa8cab558336bb 100644 (file)
@@ -10198,6 +10198,8 @@ This is useful only when used with the @option{-n} option.
 Supplying the @option{-f} option, when job control is enabled,
 forces @code{wait} to wait for each @var{id} to terminate before
 returning its status, instead of returning when it changes status.
+If there are no @var{id} arguments,
+@code{wait} waits until all background processes have terminated.
 
 If none of the @var{id}s specify one of the shell's an active child
 processes, the return status is 127.
index 0709f6dfcf81dae8dab0d6c10ce60b133e4fe716..006312d368821e3972baa5562dd89fafa8aedee8 100644 (file)
@@ -335,6 +335,8 @@ do { \
        } \
       do { } while (0)
 
+#define ERROR_TRAP_SET() (signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0)
+
 /* A sort of function nesting level counter */
 int funcnest = 0;
 int funcnest_max = 0;
@@ -639,7 +641,8 @@ async_redirect_stdin (void)
 int
 execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int pipe_out, struct fd_bitmap *fds_to_close)
 {
-  int exec_result, user_subshell, invert, ignore_return, was_error_trap, fork_flags;
+  int exec_result, user_subshell, invert, ignore_return, fork_flags;
+  int was_error_trap, want_to_run_error_trap;
   REDIRECT *my_undo_list, *exec_undo_list;
   char *tcmd;
   volatile int save_line_number;
@@ -769,7 +772,7 @@ execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int p
 
          if (asynchronous == 0)
            {
-             was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+             was_error_trap = ERROR_TRAP_SET ();
              invert = (command->flags & CMD_INVERT_RETURN) != 0;
              ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
 
@@ -867,7 +870,7 @@ execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int p
 
   /* Handle WHILE FOR CASE etc. with redirections.  (Also '&' input
      redirection.)  */
-  was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+  was_error_trap = ERROR_TRAP_SET ();
   ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
 
   if (do_redirections (command->redirects, RX_ACTIVE|RX_UNDOABLE) != 0)
@@ -930,13 +933,19 @@ execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int p
 #if defined (RECYCLES_PIDS)
        last_made_pid = NO_PID;
 #endif
-       was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+       was_error_trap = ERROR_TRAP_SET ();
+       want_to_run_error_trap = ignore_return == 0 && invert == 0 &&
+                   pipe_in == NO_PIPE && pipe_out == NO_PIPE &&
+                   (command->value.Simple->flags & CMD_COMMAND_BUILTIN) == 0;
 
        if ((ignore_return || invert) && command->value.Simple)
          command->value.Simple->flags |= CMD_IGNORE_RETURN;
        if (command->flags & CMD_STDIN_REDIR)
          command->value.Simple->flags |= CMD_STDIN_REDIR;
 
+       if (want_to_run_error_trap)
+         command->value.Simple->flags |= CMD_WANT_ERR_TRAP;
+
        begin_unwind_frame ("simple_lineno");
        add_unwind_protect (uw_restore_lineno, (void *) (intptr_t) save_line_number);
 
@@ -1009,10 +1018,10 @@ execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int p
         trap if the command run by the `command' builtin fails; we want to
         defer that until the command builtin itself returns failure. */
       /* 2020/07/14 -- this changes with how the command builtin is handled */ 
-      if (was_error_trap && ignore_return == 0 && invert == 0 &&
-           pipe_in == NO_PIPE && pipe_out == NO_PIPE &&
-           (command->value.Simple->flags & CMD_COMMAND_BUILTIN) == 0 &&
-           exec_result != EXECUTION_SUCCESS)
+      /* XXX - what happens if a function is called that sets the ERR trap
+        then returns a non-zero exit status? Have to check here using
+        ERROR_TRAP_SET() instead of relying on was_error_trap */
+      if (was_error_trap && want_to_run_error_trap && exec_result != EXECUTION_SUCCESS)
        {
          last_command_exit_value = exec_result;
          line_number = line_number_for_err_trap;
@@ -1147,7 +1156,7 @@ execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int p
     case cm_cond:
 #endif
     case cm_function_def:
-      was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+      was_error_trap = ERROR_TRAP_SET ();
 #if defined (DPAREN_ARITHMETIC)
       if (ignore_return && command->type == cm_arith)
        command->value.Arith->flags |= CMD_IGNORE_RETURN;
@@ -2920,7 +2929,7 @@ execute_connection (COMMAND *command, int asynchronous, int pipe_in, int pipe_ou
       break;
 
     case '|':
-      was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+      was_error_trap = ERROR_TRAP_SET ();
       SET_LINE_NUMBER (line_number);   /* XXX - save value? */
       exec_result = execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close);
 
@@ -5144,6 +5153,16 @@ uw_restore_funcarray_state (void *fa)
 }
 #endif
 
+static void
+restore_bash_command (void *oldcmd)
+{
+  if (the_printed_command_except_trap != (char *)oldcmd)
+    {
+      FREE (the_printed_command_except_trap);
+      the_printed_command_except_trap = oldcmd;
+    }
+}
+
 static void
 function_misc_cleanup (void)
 {
@@ -5168,6 +5187,9 @@ execute_function (SHELL_VAR *var, WORD_LIST *words, int flags, struct fd_bitmap
   int return_val, result, lineno;
   COMMAND *tc, *fc, *save_current;
   char *debug_trap, *error_trap, *return_trap;
+#if 0
+  int have_error_trap, want_to_run_error_trap;
+#endif
 #if defined (ARRAY_VARS)
   SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v;
   ARRAY *funcname_a;
@@ -5200,6 +5222,11 @@ execute_function (SHELL_VAR *var, WORD_LIST *words, int flags, struct fd_bitmap
   if (tc && (flags & CMD_IGNORE_RETURN))
     tc->flags |= CMD_IGNORE_RETURN;
 
+#if 0
+  have_error_trap = ERROR_TRAP_SET () && error_trace_mode;
+  want_to_run_error_trap = flags & CMD_WANT_ERR_TRAP;
+#endif
+
   /* A limited attempt at optimization: shell functions at the end of command
      substitutions that are already marked NO_FORK. */
   if (tc && (flags & CMD_NO_FORK) && (subshell_environment & SUBSHELL_COMSUB))
@@ -5232,6 +5259,7 @@ execute_function (SHELL_VAR *var, WORD_LIST *words, int flags, struct fd_bitmap
       unwind_protect_pointer (this_shell_function);
       unwind_protect_int (funcnest);
       unwind_protect_int (loop_level);
+      add_unwind_protect (restore_bash_command, savestring (the_printed_command_except_trap));
     }
   else
     push_context (var->name, subshell, temporary_env); /* don't unwind-protect for subshells */
@@ -5361,6 +5389,11 @@ execute_function (SHELL_VAR *var, WORD_LIST *words, int flags, struct fd_bitmap
       save_current = currently_executing_command;
       if (from_return_trap == 0)
        run_return_trap ();
+#if 0
+      /* An ERR trap on a return <non-zero> won't be run anywhere else */
+      if (result != EXECUTION_SUCCESS && have_error_trap && want_to_run_error_trap)
+       run_error_trap ();
+#endif
       currently_executing_command = save_current;
     }
   else
diff --git a/jobs.c b/jobs.c
index 0b77be162dbfb1322a02cb05b14197e044d99d4f..dc59635c35da5181212f2a83bf4a83d9eeae221e 100644 (file)
--- a/jobs.c
+++ b/jobs.c
@@ -2096,8 +2096,6 @@ print_pipeline (PROCESS *p, int job_index, int format, FILE *stream)
              if (es == 0)
                es = 2; /* strlen ("| ") */
              name_padding = LONGEST_SIGNAL_DESC - es;
-             if (name_padding <= 0)
-               name_padding = 1;
 
              fprintf (stream, "%*s", name_padding, "");
 
@@ -2795,7 +2793,7 @@ no_child:
 
 /* Wait for all of the background processes started by this shell to finish. */
 int
-wait_for_background_pids (struct procstat *ps)
+wait_for_background_pids (int wflags, struct procstat *ps)
 {
   register int i, r;
   int any_stopped, check_async, njobs;
@@ -2835,7 +2833,7 @@ wait_for_background_pids (struct procstat *ps)
       UNBLOCK_CHILD (oset);
       QUIT;
       errno = 0;               /* XXX */
-      r = wait_for_single_pid (pid, JWAIT_PERROR);
+      r = wait_for_single_pid (pid, JWAIT_PERROR|wflags);
       if (ps)
        {
          ps->pid = pid;
diff --git a/jobs.h b/jobs.h
index 64509e2b63509c17a9c311c84e05af16d79f3d5e..23bf206063bc6351949b4d88eeca4ee22bed7846 100644 (file)
--- a/jobs.h
+++ b/jobs.h
@@ -305,7 +305,7 @@ extern int job_exit_signal (int);
 extern int process_exit_status (WAIT);
 
 extern int wait_for_single_pid (pid_t, int);
-extern int wait_for_background_pids (struct procstat *);
+extern int wait_for_background_pids (int, struct procstat *);
 extern int wait_for (pid_t, int);
 extern int wait_for_job (int, int, struct procstat *);
 extern int wait_for_any_job (int, struct procstat *);
index a658b6b3cbce565795bd6b43dc5b062ed913deeb..48aba422408b426de8d5a6bbb484ad11d68723e7 100644 (file)
@@ -180,7 +180,7 @@ bash: -c: line 1: unexpected argument `<' to conditional unary operator
 bash: -c: line 1: syntax error near `<'
 bash: -c: line 1: `[[ -n < ]]'
 ERR: 22: -[[ -n $unset ]]- failed
-ERR: 28: -[[ -z nonempty ]]- failed
+ERR: 28: -func- failed
 + [[ -t X ]]
 ./cond-xtrace1.sub: line 6: [[: X: integer expected
 + [[ '' > 7 ]]
index 40c51dd61e5bbe6d7720899024699c0aaec52944..83fc341481ceffe9b1c90f90bfb166362246ee79 100644 (file)
@@ -120,15 +120,20 @@ exit=0
 A
 In trap-argument: last command preceding the trap action
 In a function call: last command in the trap action
+ERR: 23: -func- failed
+ERR: 20: -[[ -z nonempty ]]- failed
+ERR: 27: -func- failed
+ERR: 35: -func2- failed
+ERR: 39: -func2- failed
 caught a child death
 caught a child death
 caught a child death
 trap -- 'echo caught a child death' SIGCHLD
 echo caught a child death
-./trap.tests: line 118: trap: cannot specify both -p and -P
-./trap.tests: line 119: trap: -P requires at least one signal name
+./trap.tests: line 121: trap: cannot specify both -p and -P
+./trap.tests: line 122: trap: -P requires at least one signal name
 trap: usage: trap [-Plp] [[action] signal_spec ...]
-./trap.tests: line 121: trap: -x: invalid option
+./trap.tests: line 124: trap: -x: invalid option
 trap: usage: trap [-Plp] [[action] signal_spec ...]
 trap -- 'echo exiting' EXIT
 trap -- 'echo aborting' SIGABRT
index 8e38cb359448573ee163dcb7512eeb175f2f995c..6c7706b9d326aa3fb4f708f03d84523537e6acf3 100644 (file)
@@ -99,6 +99,9 @@ ${THIS_SH} ./trap8.sub
 # return without argument in trap string 
 ${THIS_SH} ./trap9.sub
 
+# ERR trap and functions
+${THIS_SH} ./trap10.sub
+
 #
 # show that setting a trap on SIGCHLD is not disastrous.
 #
diff --git a/tests/trap10.sub b/tests/trap10.sub
new file mode 100644 (file)
index 0000000..15012d7
--- /dev/null
@@ -0,0 +1,40 @@
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   This program 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 General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+trap 'echo ERR: $LINENO: -$BASH_COMMAND- failed' ERR
+
+# the conditional command will fail, then func will fail
+func()
+{
+        [[ -z nonempty ]]
+}
+# func will fail
+func
+
+set -E
+# the conditional command will fail, then func will fail
+func
+set +E
+
+func2()
+{
+       return 1
+}
+# func2 will fail
+func2
+
+# if we change so that `return 1' triffers the ERR trap, this will change
+set -E
+func2
+set +E
index f5567db0a2a2cd415c9dcdce72135ca6fd2d4009..4637d2a39500507545b40116c94a68942977cf5d 100755 (executable)
@@ -50,13 +50,8 @@ exit 42 | command false
 command command command false
 
 unset FALSE
-if [ -x /bin/false ]; then
-        FALSE=/bin/false
-elif [ -x /usr/bin/false ]; then
-        FALSE=/usr/bin/false
-elif [ -x /usr/local/bin/false ]; then
-       FALSE=/usr/local/bin/false
-else
+FALSE=$(type -P false)
+if [ -z "$FALSE" ]; then
         FALSE='command false'
 fi