From: Nathan Mills <the.true.nathan.mi...@gmail.com>
To: bug-bash@gnu.org
Subject: Bash 5.2.21 segfaults when I feed it garbage

Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: clang-15.0.7
Compilation CFLAGS: -g -O2
uname output: Linux MSI 5.15.133.1-microsoft-standard-WSL2 #1 SMP Thu Oct 5
21:02:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
Machine Type: x86_64-pc-linux-gnu

Bash Version: 5.2
Patch Level: bash-5.2-22-g4b0f8ba2
Release Status: master

Description:

Bash 5.2 segfaults when I run the following crashing input (See
Repeat-By:). It seems to be a use-after-free bug because Bash fills freed
memory with the byte 0xcf and the value of t->expander is
0xcfcfcfcfcfcfcfcf. t->expander should be either a valid pointer value or
NULL. Watching t->expander and then reverse-continuing twice shows that
that structure was freed by free_string_list, called by reset_parser. I
fixed the bug by

This is a parser save/restore state bug where it doesn't properly restore
the state of the parser after yyerror. yyerror calls reset_parser which
frees the pushed_string_list, but ps->pushed_strings still holds the old
linked list with the second item in the linked list being a dangling
pointer near the end of restore_parser_state in parse.y:6691.

This bug was found by compiling Bash 5.2 g4b0f8ba2 with AFL++ 4.09c in
Clang LTO mode (which was itself compiled with Clang 15.0.7), and running
afl-fuzz in Docker Desktop 24.0.6 in Windows 10. Then I double-checked to
make sure that the bug still exists when compiled without AFL++
instrumentation, and it does. The bug disappears when I apply the included
patch.

Reproducible every time with unpatched Bash, and never with the patch
applied.

0x00007fdc78ce7550 in _start () from /lib64/ld-linux-x86-64.so.2
Missing separate debuginfos, use: zypper install
glibc-debuginfo-2.31-150300.63.1.x86_64
(rr) c
Continuing.
output_5.2-22-g4b0f8ba2/default/crashes/id:000000,sig:11,src:005994+005483,time:12318097,execs:712991,op:splice,rep:8:
line 3: syntax error near unexpected token `|'
output_5.2-22-g4b0f8ba2/default/crashes/id:000000,sig:11,src:005994+005483,time:12318097,execs:712991,op:splice,rep:8:
line 3: `(((F)
+=G=A9*F)
Y=([)]=�cw�l|YK?E<< �pt{{$[[<H)l'

Program received signal SIGSEGV, Segmentation fault.
0x00005586164edfcc in pop_string () at
/usr/local/src/chet/src/bash/src/parse.y:1988
1988        t->expander->flags &= ~AL_BEINGEXPANDED;
Missing separate debuginfos, use: zypper install
libncurses6-debuginfo-6.1-150000.5.20.1.x86_64
rr-debuginfo-5.6.0-bp155.3.8.x86_64
(rr) p t->expander
$1 = (alias_t *) 0xcfcfcfcfcfcfcfcf

(rr) bt
#0  0x00005586164edfcc in pop_string () at
/usr/local/src/chet/src/bash/src/parse.y:1988
#1  0x00005586164fb865 in shell_getc
(remove_quoted_newline=remove_quoted_newline@entry=1)
    at /usr/local/src/chet/src/bash/src/parse.y:2667
#2  0x00005586164f49b3 in read_token (command=0) at
/usr/local/src/chet/src/bash/src/parse.y:3418
#3  0x00005586164e8fcf in yylex () at
/usr/local/src/chet/src/bash/src/parse.y:2905
#4  yyparse () at y.tab.c:1854
#5  0x00005586164e89d4 in parse_command () at eval.c:348
#6  0x00005586164e82f7 in read_command () at eval.c:392
#7  0x00005586164e7d2b in reader_loop () at eval.c:139
#8  0x00005586164e5307 in main (argc=2, argv=0x7fff657b6c38, env=<optimized
out>) at shell.c:833
(rr) watch -l t->expander
Hardware watchpoint 1: -location t->expander
(rr) reverse-continue
Continuing.

Program received signal SIGSEGV, Segmentation fault.
pop_string () at /usr/local/src/chet/src/bash/src/parse.y:1988
1988        t->expander->flags &= ~AL_BEINGEXPANDED;
(rr) reverse-continue\

Continuing.

Hardware watchpoint 1: -location t->expander

Old value = (alias_t *) 0xcfcfcfcfcfcfcfcf
New value = (alias_t *) 0x0
0x00007f8fa6cd5422 in __memset_avx2_unaligned_erms () from /lib64/libc.so.6
(rr) bt
#0  0x00007f8fa6cd5422 in __memset_avx2_unaligned_erms () from
/lib64/libc.so.6
#1  0x000055a21abba0cd in internal_free (mem=0x55a21c97c910,
    file=0x55a21abbada6 "/usr/local/src/chet/src/bash/src/parse.y",
line=2009, flags=<optimized out>) at malloc.c:1048
#2  0x000055a21ab01f20 in free_string_list () at
/usr/local/src/chet/src/bash/src/parse.y:2009
#3  reset_parser () at /usr/local/src/chet/src/bash/src/parse.y:3333
#4  0x000055a21ab098c4 in yyerror (msg=<optimized out>) at
/usr/local/src/chet/src/bash/src/parse.y:6127
#5  parse_compound_assignment (retlenp=retlenp@entry=0x7ffc7ff88f7c) at
/usr/local/src/chet/src/bash/src/parse.y:6526
#6  0x000055a21ab0546c in read_token_word (character=61) at
/usr/local/src/chet/src/bash/src/parse.y:5168
#7  read_token (command=0) at /usr/local/src/chet/src/bash/src/parse.y:3610
#8  0x000055a21aafeaff in yylex () at
/usr/local/src/chet/src/bash/src/parse.y:2905
#9  yyparse () at y.tab.c:1854
#10 0x000055a21aafe78f in parse_command () at eval.c:348
#11 0x000055a21aafe4d3 in read_command () at eval.c:392
#12 0x000055a21aafe2b4 in reader_loop () at eval.c:139
#13 0x000055a21aafcd20 in main (argc=2, argv=0x7ffc7ff8a198, env=<optimized
out>) at shell.c:833

Repeat-By:

Run the following with Bash 5.2 after base64 decoding it:

KCgoKEYpCis9Rz1BGRkZORkZGQAZKhlGKQpZPShbKV09g2N3hmx8WUs/RTw8IL1wdHt7JFtbPEgp
bGxdXUFJbCnxKU5VTUVSSUNaLGU9PT09RypbcjhkIj0gAGQiPSAAPA==

Fix:

Apply the following patch:

diff --git a/parse.y b/parse.y
index 8fd24a1c..abea0a2b 100644
--- a/parse.y
+++ b/parse.y
@@ -167,6 +167,7 @@ static void discard_until PARAMS((int));
 static void push_string PARAMS((char *, int, alias_t *));
 static void pop_string PARAMS((void));
 static void free_string_list PARAMS((void));
+static void *copy_string_list PARAMS((void));

 static char *read_a_line PARAMS((int));

@@ -363,6 +364,8 @@ static FILE *yyerrstream;
 %token LESS_LESS_MINUS AND_GREATER AND_GREATER_GREATER LESS_GREATER
 %token GREATER_BAR BAR_AND

+%token YYEOF
+
 /* Special; never created by yylex; only set by parse_comsub and
xparse_dolparen */
 %token DOLPAREN

@@ -2012,6 +2015,31 @@ free_string_list ()
   pushed_string_list = (STRING_SAVER *)NULL;
 }

+static void*
+copy_string_list ()
+{
+  STRING_SAVER *copy, *t, *t1, *temp;
+  if (!pushed_string_list)
+    return NULL;
+  copy = (STRING_SAVER*)xmalloc (sizeof (STRING_SAVER));
+  temp = copy;
+  for (t = pushed_string_list; t; )
+    {
+      t1 = t->next;
+      *temp = *t;
+      temp->saved_line = savestring (t->saved_line);
+      if (t1)
+        {
+          temp->next = (STRING_SAVER*)xmalloc (sizeof(STRING_SAVER));
+          temp = temp->next;
+        }
+      else
+        temp->next = NULL;
+      t = t1;
+    }
+  return copy;
+}
+
 void
 free_pushed_string_input ()
 {
@@ -6617,7 +6645,7 @@ save_parser_state (ps)
     memcpy (ps->redir_stack, redir_stack, sizeof (redir_stack[0]) *
HEREDOC_MAX);

 #if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
-  ps->pushed_strings = pushed_string_list;
+  ps->pushed_strings = copy_string_list ();
 #endif

   ps->eof_token = shell_eof_token;

Reply via email to