Configuration Information [Automatically generated, do not change]: Machine: x86_64 OS: linux-gnu Compiler: gcc Compilation CFLAGS: -O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection uname output: Linux skylark 5.19.9-100.fc35.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Sep 15 09:55:09 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux Machine Type: x86_64-redhat-linux-gnu
Bash Version: 5.1 Patch Level: 8 Release Status: release Description: Repeatable buffer overflow core-dump in bash's readline due to rl_forced_update_display trying to zeroize a string that is not NUL terminated. Repeat-By: Create a small window with a new 2x1 bash inside of it. Resize that window. Type a command. Get a memory error. Full annotated debugging session showing the target, smoke, gun, and bullet included below. Fix: There may be a second bug which prevents the buffer from being NUL terminated in the first place, but I urge you to apply this patch no matter what, since the code as written is very dangerous without the bounds check. The bug report was also submitted to the libreadline people since it still appears to show up there. --- display.c.orig 2022-09-23 12:23:36.282214239 -0400 +++ display.c 2022-09-23 12:28:17.028118101 -0400 @@ -2644,11 +2644,13 @@ rl_forced_update_display (void) { register char *temp; + register int templen; if (visible_line) { temp = visible_line; - while (*temp) + templen = vis_lbsize; + while (*temp && templen--) *temp++ = '\0'; } rl_on_new_line (); Debugging information: ---------------------------------------------------------------------- # Bash born in a 2x1 window, attach via gdb (gdb) b bind_variable_internal Breakpoint 1 at 0x55e514726830: file /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c, line 3089. # Look at the previous window size: (gdb) print *entry $1 = {name = 0x55e515863060 "LINES", value = 0x55e515aac0b0 "1", exportstr = 0x0, dynamic_value = 0x0, assign_func = 0x0, attributes = 32769, context = 0} # Skip down until the new window size is set: (gdb) where #0 bind_variable_internal (name=<optimized out>, value=0x7ffd4fa2db35 "56", table=<optimized out>, hflags=<optimized out>, aflags=<optimized out>) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:3239 #1 0x000055e5147278cb in sh_set_lines_and_columns (cols=173, lines=<optimized out>) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:1021 #2 sh_set_lines_and_columns (lines=<optimized out>, cols=173) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:1009 #3 0x000055e5147c068f in _rl_get_screen_size (tty=<optimized out>, ignore_env=ignore_env@entry=1) at lib/readline/terminal.c:328 #4 0x000055e5147c0820 in rl_resize_terminal () at lib/readline/terminal.c:385 #5 0x000055e5147c0a84 in _rl_signal_handler (sig=<optimized out>) at lib/readline/signals.c:149 #6 _rl_signal_handler (sig=<optimized out>) at lib/readline/signals.c:140 #7 0x000055e5147c1fa5 in rl_getc (stream=0x1533a4ffaaa0 <_IO_2_1_stdin_>) at lib/readline/input.c:633 #8 0x000055e5147c1040 in rl_read_key () at lib/readline/input.c:514 #9 0x000055e5147a029c in readline_internal_char () at lib/readline/readline.c:633 #10 0x000055e5147a05bd in readline_internal_charloop () at lib/readline/readline.c:741 #11 readline_internal () at lib/readline/readline.c:753 #12 readline (prompt=<optimized out>) at lib/readline/readline.c:432 #13 0x000055e514702d5f in yy_readline_get () at ./parse.y:1488 #14 0x000055e514705dc9 in yy_getc () at ./parse.y:1422 #15 shell_getc (remove_quoted_newline=remove_quoted_newline@entry=1) at ./parse.y:2358 #16 0x000055e514707b5a in read_token (command=<optimized out>) at ./parse.y:3290 #17 0x000055e51470b6da in read_token (command=0) at ./parse.y:3254 #18 yylex () at ./parse.y:2797 #19 yyparse () at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/y.tab.c:1836 #20 0x000055e51470eb5d in parse_command () at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/eval.c:348 #21 0x000055e51470ed2d in read_command () at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/eval.c:392 #22 0x000055e51470eff5 in reader_loop () at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/eval.c:138 #23 0x000055e51470091e in main (argc=1, argv=0x7ffd4fa2f0b8, env=0x7ffd4fa2f0c8) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/shell.c:821 (gdb) print *entry $2 = {name = 0x55e515863060 "LINES", value = 0x55e515aaee70 "56", exportstr = 0x0, dynamic_value = 0x0, assign_func = 0x0, attributes = 32769, context = 0} # Inspect (and monitor) the glibc ptmalloc header for this new value buffer: (gdb) print *(unsigned int *)(0x55e515aaee70-16) $6 = 1663067251 (gdb) print (char *)(0x55e515aaee70-16) $40 = 0x55e515aaee60 "sd conso!" (gdb) print *(unsigned int *)(0x55e515aaee70-16) $6 = 1663067251 (gdb) print *(unsigned int *)(0x55e515aaee70-8) $3 = 33 (gdb) watch *(unsigned int *)(0x55e515aaee70-8) pect some memory that occurs before this new allocation (gdb) print (char *)(0x55e515aaee70-1040) $39 = 0x55e515aaea60 "skylark seth>> <", '\001' <repeats 1008 times>, "sd conso!" # Continue on and just record what the value of COLUMNS was and will be for posterity (gdb) print *entry $41 = {name = 0x55e515865d40 "COLUMNS", value = 0x55e515895320 "2", exportstr = 0x0, dynamic_value = 0x0, assign_func = 0x0, attributes = 32769, context = 0} (gdb) where #0 bind_variable_internal (name=0x55e5147e98a6 "COLUMNS", value=0x7ffd4fa2db34 "173", table=0x55e515858de0, hflags=0, aflags=0) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:3095 #1 0x000055e5147278f4 in sh_set_lines_and_columns (cols=173, lines=<optimized out>) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:1024 #2 sh_set_lines_and_columns (lines=<optimized out>, cols=173) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:1009 #3 0x000055e5147c068f in _rl_get_screen_size (tty=<optimized out>, ignore_env=ignore_env@entry=1) at lib/readline/terminal.c:328 [...] Same as above # Add another break-point to find the value of a magic pointer (gdb) b rl_forced_update_display Breakpoint 3 at 0x55e5147b6ef0: file lib/readline/display.c, line 2645. # Continue on (gdb) cont Continuing. Breakpoint 3, rl_forced_update_display () at lib/readline/display.c:2645 (gdb) where #0 rl_forced_update_display () at lib/readline/display.c:2651 #1 0x000055e5147c0a84 in _rl_signal_handler (sig=<optimized out>) at lib/readline/signals.c:149 #2 _rl_signal_handler (sig=<optimized out>) at lib/readline/signals.c:140 #3 0x000055e5147c1fa5 in rl_getc (stream=0x1533a4ffaaa0 <_IO_2_1_stdin_>) at lib/readline/input.c:633 # We inspect the value of the magic pointer a few lines after this breakpoint (gdb) print temp $42 = 0x55e515aaea60 "skylark seth>> <", '\001' <repeats 1008 times>, "sd conso!" # We see that this is the same memory we saw before, the first chunk of data prior to the "value" allocation. # We now know that it is the contents of "visible_line", in other words, what appears on the screen, which "happens" to reflect the current value of my $PS1 # (not including the "<" and after). (gdb) cont Continuing. Hardware watchpoint 2: *(unsigned int *)(0x55e515aaee70-8) Old value = 33 New value = 0 rl_forced_update_display () at lib/readline/display.c:2651 2651 while (*temp) 2652 *temp++ = '\0'; # Oops. Yes, the "visible_line" was not null terminated so the buffer clearing code did a buffer overflow and trashed the PTMALLOC2 allocation header # Nothing bad happens...yet. Now try to type a command and hit enter Breakpoint 1, bind_variable_internal (name=0x55e5147ea0d5 "LINES", value=0x7ffd4 fa2ddb5 "56", table=0x55e515858de0, hflags=0, aflags=0) at /usr/src/debug/bash-5 .1.8-2.fc35.x86_64/variables.c:3089 3089 { (gdb) where #0 bind_variable_internal (name=0x55e5147ea0d5 "LINES", value=0x7ffd4fa2ddb5 "56", table=0x55e515858de0, hflags=0, aflags=0) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:3089 #1 0x000055e5147278cb in sh_set_lines_and_columns (cols=173, lines=<optimized out>) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:1021 #2 sh_set_lines_and_columns (lines=<optimized out>, cols=173) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:1009 #3 0x000055e51479de8b in get_new_window_size (from_sig=<optimized out>, rp=0x0, cp=0x0) at lib/sh/winsize.c:88 #4 0x000055e514705fb8 in shell_getc (remove_quoted_newline=1) at ./parse.y:2290 #5 0x000055e514707da3 in read_token_word (character=<optimized out>) at ./parse.y:5326 #6 read_token (command=<optimized out>) at ./parse.y:3484 #7 0x000055e51470b6da in read_token (command=0) at ./parse.y:3254 #8 yylex () at ./parse.y:2797 #9 yyparse () at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/y.tab.c:1836 #10 0x000055e51470eb5d in parse_command () at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/eval.c:348 #11 0x000055e51470ed2d in read_command () at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/eval.c:392 #12 0x000055e51470eff5 in reader_loop () at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/eval.c:138 #13 0x000055e51470091e in main (argc=1, argv=0x7ffd4fa2f0b8, env=0x7ffd4fa2f0c8) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/shell.c:821 # We got a TIOCGWINSZ (the initial breakpoint happened due to a SIGWINCH). No problems. Continue. Program received signal SIGABRT, Aborted. __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44 44 return INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO (ret) : 0; (gdb) where #0 __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44 #1 0x00001533a4ea15d3 in __pthread_kill_internal (signo=6, threadid=<optimized out>) at pthread_kill.c:78 #2 0x00001533a4e54d16 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26 #3 0x00001533a4e287f3 in __GI_abort () at abort.c:79 #4 0x00001533a4e95567 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x1533a4fbb6a8 "%s\n") at ../sysdeps/posix/libc_fatal.c:155 #5 0x00001533a4eab62c in malloc_printerr (str=str@entry=0x1533a4fb91f2 "free(): invalid pointer") at malloc.c:5531 #6 0x00001533a4eacedc in _int_free (av=<optimized out>, p=<optimized out>, have_lock=0) at malloc.c:4322 #7 0x00001533a4eaf985 in __GI___libc_free (mem=<optimized out>) at malloc.c:3274 #8 0x000055e514726b2a in bind_variable_internal (name=0x55e5147ea0d5 "LINES", value=0x7ffd4fa2ddb5 "56", table=<optimized out>, hflags=0, aflags=0) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:3234 #9 0x000055e5147278cb in sh_set_lines_and_columns (cols=173, lines=<optimized out>) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:1021 #10 sh_set_lines_and_columns (lines=<optimized out>, cols=173) at /usr/src/debug/bash-5.1.8-2.fc35.x86_64/variables.c:1009 [...] same as above # The system attempted to free the old "value" of $LINES, but noticed that the PTMALLOC header was overwritten, and aborts.