bug-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Fix segfault with self-modifying array PROMPT_COMMAND


From: Koichi Murase
Subject: [PATCH] Fix segfault with self-modifying array PROMPT_COMMAND
Date: Mon, 31 Aug 2020 10:36:33 +0900

Hi, I hit a segmentation fault with the array PROMPT_COMMAND.  Here is
the report and a patch. There is also another trivial patch.

Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS: -march=native -O3
uname output: Linux chatoyancy 5.6.13-100.fc30.x86_64 #1 SMP Fri May
15 00:36:06 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Machine Type: x86_64-pc-linux-gnu

Bash Version: 5.1
Patch Level: 0
Release Status: release

Description:

  In the devel branch (e4d38c2d: commit bash-20200819 snapshot), a
  segmentation fault occurs when a prompt command stored in the array
  version of PROMPT_COMMAND unsets the corresponding element in
  PROMPT_COMMAND.

  The same happens for Bash 5.1-alpha with PROMPT_COMMANDS.

  This is caused because the original array element data is free'd in
  the evaluation process of the command string.  The array scan and
  the copy of all the command strings in PROMPT_COMMAND need to be
  finished before executing the command strings.

Repeat-By:

  For devel branch (PROMPT_COMMAND),

    $ cat test19-devel.bashrc
    my-prompt-command() { unset 'PROMPT_COMMAND[0]'; }
    PROMPT_COMMAND=(my-prompt-command)
    $ bash-dev --rcfile test19-devel.bashrc
    Segmentation fault (core dumped)

  For 5.1-alpha (PROMPT_COMMANDS),

    $ cat test19-5.1.bashrc
    my-prompt-command() { unset 'PROMPT_COMMANDS[0]'; }
    PROMPT_COMMANDS=(my-prompt-command)
    $ bash-5.1-alpha --rcfile test19-5.1.bashrc
    Segmentation fault (core dumped)

  As a related behavior, when one of the prompt commands assigns new
  elements to the array PROMPT_COMMAND, the subsequent prompt commands
  will not be executed.  With the devel branch,

    $ cat test19b.bashrc
    function unregister-prompt_command {
      local -a new=() cmd
      for cmd in "${PROMPT_COMMAND[@]}"; do
        [[ $cmd != "$1" ]] && new+=("$cmd")
      done
      PROMPT_COMMAND=("${new[@]}")
    }
    function my-prompt_command {
      echo "$FUNCNAME"

      # remove itself from PROMPT_COMMAND
      unregister-prompt_command "$FUNCNAME"
    }
    PROMPT_COMMAND+=('echo test1')
    PROMPT_COMMAND+=(my-prompt_command)
    PROMPT_COMMAND+=('echo test2')
    $ bash-dev --rcfile test19b.bashrc
    test1
    my-prompt_command       # <-- test2 is expected but missing
    bash-dev$
    test1
    test2                   # <-- test2 is correctly called for the
    bash-dev$               #     next execution of PROMPT_COMMAND

Fix:

  There is no problem with the scalar PROMPT_COMMAND.  For the scalar
  PROMPT_COMMAND, it uses the function `execute_variable_command'
  (parse.y) which first copies the command string using
  `savestring(cmd)' and pass it to `parse_and_execute'.  This process
  can be illustrated in the following schematic code.

    {
      Copy the value of PROMPT_COMMAND;
      Execute the copy; /* this can modify the original variable */
      Free the copy;
    }

  In this way, it doesn't cause problems even when the variable
  PROMPT_COMMAND is rewritten in the processing of the command string
  because the executed string is a copy of the original
  PROMPT_COMMAND.

  In the case of the array PROMPT_COMMAND, it uses the utility
  `execute_variable_command' for each element, so the schematic code
  would be:

    for (PROMPT_COMMAND array_elements)
      {
        Copy the element;
        Execute the copy; /* this can modify the array */
        Free the copy;
      }

  This is robust for the case that the executed command modifies just
  the string of the corresponding element, but vulnerable for the case
  that the array structure is changed.  This should be modified in the
  following way:

    {
      Copy all the elements of PROMPT_COMMAND.
      for (copied strings)
        Execute the command strings.
      Free the copied strings.
    }

  In the patch
  `0001-Fix-a-segmentation-fault-of-array-PROPMT_COMMAND.patch', I
  have added a function `execute_array_command' which is the array
  version of `execute_variable_command'.

  ----

  By the way, I suspect there is a memory leak in `bashline.c'.

    bashline.c:4343:  r = parse_and_execute (savestring (cmd),
"bash_execute_unix_command", SEVAL_NOHIST|SEVAL_NOFREE);

  It appears to me that SEVAL_NOFREE shouldn't be specified here [see
  the patch `0002-Fix-memleak-in-bash_execute_unix_command.patch'], or
  otherwise, the string allocated by `savestring' will not be free'd.
  In fact, I can observe that the memory use of the process is
  monotonically increasing with the following operation:

    $ bash --norc
    $ arr=({1..100000})
    $ bind -x "\"\C-t\":: '${arr[*]}"
    (hit C-t many times)

  Actually I have sent a related patch two years ago at

    https://lists.gnu.org/archive/html/bug-bash/2018-05/msg00020.html

  In the patch at that time, I added `savestring' and removed
  `SEVAL_NOFREE' as well.

  -  r = parse_and_execute (cmd, "bash_execute_unix_command",
SEVAL_NOHIST|SEVAL_NOFREE);
  +  r = parse_and_execute (savestring (cmd),
"bash_execute_unix_command", SEVAL_NOHIST);

  However, it seems that only the change to `savestring' was picked up
  at that time, and the change of SEVAL_NOFREE was dropped in applying
  the patch to the devel branch.

  --- dfc21851b commit bash-20090723 snapshot
  +++ 96b7e2687 commit bash-20180504 snapshot
  -  r = parse_and_execute (cmd, "bash_execute_unix_command",
SEVAL_NOHIST|SEVAL_NOFREE);
  +  r = parse_and_execute (savestring (cmd),
"bash_execute_unix_command", SEVAL_NOHIST|SEVAL_NOFREE);

--
Koichi



reply via email to

[Prev in Thread] Current Thread [Next in Thread]