compgen -W evaluation is leading to security holes

2018-09-14 Thread joey
Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS:  -DPROGRAM='bash' -DCONF_HOSTTYPE='x86_64' 
-DCONF_OSTYPE='linux-gnu' -DCONF_MACHTYPE='x86_64-pc-linux-gnu' 
-DCONF_VENDOR='pc' -DLOCALEDIR='/usr/share/locale' -DPACKAGE='bash' -DSHELL 
-DHAVE_CONFIG_H   -I.  -I../. -I.././include -I.././lib  -Wdate-time 
-D_FORTIFY_SOURCE=2 -g -O2 -fdebug-prefix-map=/build/bash-JkMlAz/bash-4.4.18=. 
-fstack-protector-strong -Wformat -Werror=format-security -Wall 
-Wno-parentheses -Wno-format-security
uname output: Linux darkstar 4.17.0-3-amd64 #1 SMP Debian 4.17.17-1 
(2018-08-18) x86_64 GNU/Linux
Machine Type: x86_64-pc-linux-gnu

Bash Version: 4.4
Patch Level: 23
Release Status: release

Description:

CVE-2018-7738 was caused by a bash completion script using compgen -W
with untrusted input. For some reason compgen -W evals its input:

$ compgen -W '`cat /etc/shadow`'
cat: /etc/shadow: Permission denied

Which makes code like this turn out to be a security hole:

DEVS_MPOINTS="$(mount | awk '{print $1, $3}')"
COMPREPLY=( $(compgen -W "$DEVS_MPOINTS" -- $cur) )

Grimm reviewed several other bash completion scripts for similar security
holes, and while they didn't find any, there were several near misses
where the code was probably only not explitable by accident.
https://blog.grimm-co.com/post/malicious-command-execution-via-bash-completion-cve-2018-7738/

I don't know why compgen -W evals; there may be a good reason. Or it may be
a bug. The documentation for compgen does not seem to mention this
behavior. Even if there's a good reason for it to do that, it's certianly
violating least surprise, because "$foo" is normally safe to use in a shell
script without worrying about the content of the variable being
accidentially evaluated -- unless you're running something like eval 
or bash -c that explicitly does so.

Repeat-By:

compgen -W '`cat`'



Re: error message for missing fi is not helpful

2018-09-14 Thread L A Walsh




On 9/13/2018 1:21 PM, Chet Ramey wrote:


I'm sure you noticed that word_lineno isn't set for every compound command
and that it's limited.
  


   Actually I didn't notice.  Under what circumstances is it not set?
What is it's purpose if it's not to keep track of a stack of nested
conditionals?

   Could it store the line number of each or would that cause other
problems?




Re: error message for missing fi is not helpful

2018-09-14 Thread Manuel Reiter

On 13.09.2018 04:29, L A Walsh wrote:

This isn't *exactly* what you wanted, but this gives the line number
of the last unmatched statement (but doesn't tell you what the statement
was).  The diff was against bash-4.4.23 (4.4 base w/23 patches)


Thank you for taking the time to look into this! There seems to be a 
problem with your code though: if word_top is zero and EOF_Reached is 
true, you set msg to msg0 without initializing msg0 first.


Reviving the code you commented out (your first attempt?) I came to the 
attached result. I also added if/fi to the compound commands tracked by 
word_top. There are probably others that I have missed missed.



Anyway, if you store the word in a separate array where the line #
is stored, you _could_ list the matching word, but I suspect just the
line it started on would be enough for most users.


I totally agree with that.

Thanks and best regards,

  Manuel
diff --git a/parse.y.orig b/parse.y
index f415d2e..76974f0 100644
--- a/parse.y.orig
+++ b/parse.y
@@ -1000,11 +1000,11 @@ coproc:		COPROC shell_command
 	;
 
 if_command:	IF compound_list THEN compound_list FI
-			{ $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
+			{ $$ = make_if_command ($2, $4, (COMMAND *)NULL); if (word_top > 0) word_top--; }
 	|	IF compound_list THEN compound_list ELSE compound_list FI
-			{ $$ = make_if_command ($2, $4, $6); }
+			{ $$ = make_if_command ($2, $4, $6); if (word_top > 0) word_top--; }
 	|	IF compound_list THEN compound_list elif_clause FI
-			{ $$ = make_if_command ($2, $4, $5); }
+			{ $$ = make_if_command ($2, $4, $5); if (word_top > 0) word_top--; }
 	;
 
 
@@ -5142,6 +5142,7 @@ got_token:
 case CASE:
 case SELECT:
 case FOR:
+case IF:
   if (word_top < MAX_CASE_NEST)
 	word_top++;
   word_lineno[word_top] = line_number;
@@ -6020,9 +6021,13 @@ report_syntax_error (message)
 print_offending_line ();
 }
   else
-{
-  msg = EOF_Reached ? _("syntax error: unexpected end of file") : _("syntax error");
-  parser_error (line_number, "%s", msg);
+{ 
+  if (EOF_Reached && word_top>=0) {
+parser_error(line_number, _("syntax error: unexpected end of file from line %d."), word_lineno[word_top] );
+  } else {
+msg = EOF_Reached ? _("syntax error: unexpected end of file") : _("syntax error");
+parser_error (line_number, "%s", msg);
+  }
   /* When the shell is interactive, this file uses EOF_Reached
 	 only for error reporting.  Other mechanisms are used to
 	 decide whether or not to exit. */


expand_prompt_string segmentation faults

2018-09-14 Thread Eduardo A . Bustamante López
Found the following two cases by fuzzing with AFL:

# Case #1: array_expand_index
bash <<'EOF'
x='${p[--b[?]]'; echo ${x@P}
EOF

# Case #1 backtrace
: <<'EOF'
Program received signal SIGSEGV, Segmentation fault.
0x0080e0d3 in __strchr_sse2 ()
#0  0x0080e0d3 in __strchr_sse2 ()
#1  0x006d954b in mbschr (s=0x0, c=91) at mbschr.c:90
#2  0x0058acdf in valid_array_reference (name=0x0, flags=0) at 
arrayfunc.c:899
#3  0x0049c4e9 in bind_int_variable (lhs=0x0, rhs=0xbb5228 "-1", 
flags=0) at variables.c:3371
#4  0x004c632c in expr_bind_variable (lhs=0x0, rhs=) at 
expr.c:333
#5  exp0 () at expr.c:1015
#6  exp1 () at expr.c:983
#7  0x004c54ae in exppower () at expr.c:938
#8  0x004c4cf8 in exp2 () at expr.c:863
#9  0x004c4695 in exp3 () at expr.c:837
#10 expshift () at expr.c:813
#11 0x004c3d95 in exp4 () at expr.c:783
#12 exp5 () at expr.c:761
#13 0x004c3a61 in expband () at expr.c:743
#14 expbxor () at expr.c:724
#15 0x004c3621 in expbor () at expr.c:705
#16 expland () at expr.c:678
#17 0x004c2e01 in explor () at expr.c:650
#18 expcond () at expr.c:603
#19 0x004c1f2b in expassign () at expr.c:488
#20 0x004be48e in expcomma () at expr.c:472
#21 subexpr (expr=0xbc9a48 "--b[?]") at expr.c:450
#22 0x004bdba0 in evalexp (expr=0xbc9a48 "--b[?]", flags=, validp=0x7fffce14) at expr.c:415
#23 0x00589d81 in array_expand_index (var=, s=, len=, flags=) at arrayfunc.c:952
#24 0x0058b7f5 in array_value_internal (s=0xbc9a08 "p[--b[?]]", 
quoted=, flags=1, rtype=0x7fffce9c, indp=) at 
arrayfunc.c:1133
#25 0x0053eed1 in parameter_brace_expand_word (name=0xbc9a08 
"p[--b[?]]", var_is_special=0, quoted=1, pflags=, 
indp=0x7fffcf40) at subst.c:6584
#26 0x00536c7b in parameter_brace_expand (string=, 
quoted=, pflags=, contains_dollar_at=, indexp=, quoted_dollar_atp=) at subst.c:8702
#27 param_expand (string=0xbc5fe8 "${p[--b[?]]", sindex=, 
quoted=, expanded_something=, 
contains_dollar_at=, quoted_dollar_at_p=, 
had_quoted_null_p=0x0, pflags=) at subst.c:9316
#28 0x00510893 in expand_word_internal (word=0x7fffd0b0, 
quoted=, isexp=, contains_dollar_at=, expanded_something=) at subst.c:9887
#29 0x0050f595 in expand_prompt_string (string=0xbc7ec8 "${p[--b[?]]", 
quoted=1, wflags=) at subst.c:3804
#30 0x00420e71 in decode_prompt_string (string=) at 
./parse.y:6065
#31 0x0055059c in string_transform (xc=, v=0xbc7dc8, 
s=0xbc5fc8 "${p[--b[?]]") at subst.c:7468
#32 0x0054a2b5 in parameter_brace_transform (varname=, 
value=, ind=, xform=, rtype=0, 
quoted=, pflags=0, flags=) at subst.c:7616
#33 0x0053bb17 in parameter_brace_expand (string=, 
quoted=, pflags=, contains_dollar_at=, indexp=, quoted_dollar_atp=) at subst.c:8884
#34 param_expand (string=0xbc7e68 "${REPLY@P}", sindex=, 
quoted=, expanded_something=, 
contains_dollar_at=, quoted_dollar_at_p=, 
had_quoted_null_p=, pflags=) at subst.c:9316
#35 0x00510893 in expand_word_internal (word=0xbc7828, 
quoted=, isexp=, contains_dollar_at=, expanded_something=) at subst.c:9887
#36 0x00529560 in shell_expand_word_list (tlist=, 
eflags=0) at subst.c:11233
#37 expand_word_list_internal (list=, eflags=) at 
subst.c:11357
#38 0x0046f341 in execute_simple_command (simple_command=, pipe_in=-1, pipe_out=-1, async=, fds_to_close=) at execute_cmd.c:4278
#39 execute_command_internal (command=, asynchronous=, pipe_in=, pipe_out=, 
fds_to_close=) at execute_cmd.c:840
#40 0x0046b5cb in execute_connection (command=, 
asynchronous=, pipe_in=, pipe_out=, fds_to_close=) at execute_cmd.c:2689
#41 execute_command_internal (command=0xbc5e48, asynchronous=, 
pipe_in=, pipe_out=, fds_to_close=) at execute_cmd.c:1013
#42 0x00605bcc in parse_and_execute (string=, 
from_file=, flags=4) at evalstring.c:436
#43 0x00409a8c in run_one_command (command=) at 
shell.c:1416
#44 0x004063a7 in main (argc=, argv=, 
env=) at shell.c:735
EOF

# Case #2
bash <<'EOF'
x='$[++K[+]]/'; echo ${x@P}
EOF

# Case #2 backtrace
: <<'EOF'
Program received signal SIGSEGV, Segmentation fault.
0x0080e0d3 in __strchr_sse2 ()
#0  0x0080e0d3 in __strchr_sse2 ()
#1  0x006d954b in mbschr (s=0x0, c=91) at mbschr.c:90
#2  0x0058acdf in valid_array_reference (name=0x0, flags=0) at 
arrayfunc.c:899
#3  0x0049c4e9 in bind_int_variable (lhs=0x0, rhs=0xbb5248 "1", 
flags=0) at variables.c:3371
#4  0x004c632c in expr_bind_variable (lhs=0x0, rhs=) at 
expr.c:333
#5  exp0 () at expr.c:1015
#6  exp1 () at expr.c:983
#7  0x004c54ae in exppower () at expr.c:938
#8  0x004c4cf8 in exp2 () at expr.c:863
#9  0x004c4695 in exp3 () at expr.c:837
#10 expshift () at expr.c:813
#11 0x004c3d95 in exp4 () at expr.c:783
#12 exp5 () at expr.c:761
#13 0x004c3a61 in expband () at expr.c:743
#14 expbxor () at