From: Chet Ramey Date: Thu, 18 Sep 2025 21:54:55 +0000 (-0400) Subject: fix for potential read builtin crash if bash gets a signal before read reads any... X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cf8a2518c8b94f75b330d398f5daa0ee21417e1b;p=thirdparty%2Fbash.git fix for potential read builtin crash if bash gets a signal before read reads any input; make wait -f work if there are no pid or job arguments and update documentation; save and restore variable reflected in $BASH_COMMAND around shell function calls --- diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 8d453e0c..ae46e350 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -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 + + 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 + +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 + +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 diff --git a/MANIFEST b/MANIFEST index 5c084e57..dd42fce7 100644 --- 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 diff --git a/builtins/read.def b/builtins/read.def index 5135e2b1..04366ec9 100644 --- a/builtins/read.def +++ b/builtins/read.def @@ -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); diff --git a/builtins/wait.def b/builtins/wait.def index c8c735aa..34d93151 100644 --- a/builtins/wait.def +++ b/builtins/wait.def @@ -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. */ diff --git a/command.h b/command.h index 189b00de..f8196b5f 100644 --- 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 { diff --git a/doc/bash.1 b/doc/bash.1 index 75bd2ba3..8724835d 100644 --- a/doc/bash.1 +++ b/doc/bash.1 @@ -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. diff --git a/doc/bashref.texi b/doc/bashref.texi index d43e192d..41dfd35e 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -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. diff --git a/execute_cmd.c b/execute_cmd.c index 0709f6df..006312d3 100644 --- a/execute_cmd.c +++ b/execute_cmd.c @@ -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 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 0b77be16..dc59635c 100644 --- 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 64509e2b..23bf2060 100644 --- 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 *); diff --git a/tests/cond.right b/tests/cond.right index a658b6b3..48aba422 100644 --- a/tests/cond.right +++ b/tests/cond.right @@ -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 ]] diff --git a/tests/trap.right b/tests/trap.right index 40c51dd6..83fc3414 100644 --- a/tests/trap.right +++ b/tests/trap.right @@ -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 diff --git a/tests/trap.tests b/tests/trap.tests index 8e38cb35..6c7706b9 100644 --- a/tests/trap.tests +++ b/tests/trap.tests @@ -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 index 00000000..15012d7a --- /dev/null +++ b/tests/trap10.sub @@ -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 . +# + +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 diff --git a/tests/trap2.sub b/tests/trap2.sub index f5567db0..4637d2a3 100755 --- a/tests/trap2.sub +++ b/tests/trap2.sub @@ -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