bug-bash
[Top][All Lists]
Advanced

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

Buffer overflow in bash's readline


From: srobertson
Subject: Buffer overflow in bash's readline
Date: Fri, 23 Sep 2022 15:24:51 -0400

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.



reply via email to

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