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;