BASH recursion segfault, FUNCNEST doesn't help

2022-06-01 Thread Gergely
Hi,


I stumbled upon a recursion overflow crash in BASH. It affects both my
Debian machine (this report), as well as the latest stable built from
source.

There's a slim chance this might be exploitable.


Best,
Gergely Kalman


Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS: -g -O2 -fstack-protector-strong -Wformat
-Werror=format-security -Wall
uname output: Linux test 5.17.0-2-amd64 #1 SMP PREEMPT Debian 5.17.6-1
(2022-05-14) x86_64 GNU/Linux
Machine Type: x86_64-pc-linux-gnu

Bash Version: 5.1
Patch Level: 16
Release Status: release

Description:
     A file repeatedly sourcing itself crashes bash with a segfault.

     I did not have time to investigate, but it is alarming to me that
bash with some depths can survive the recursion, only to crash when I
try to run programs. This suggests to me that there's a very slim chance
of exploitability, but really I saw no point in investigating as at this
point the attacker can pretty much already run code...

     As suggested in the previous report like this
(https://lists.gnu.org/archive/html/bug-bash/2022-05/msg00016.html),
FUNCNEST doesn't help, somewhat unsurprisingly.


Repeat-By:
     Basic code to reproduce:

     echo '. a' > a; bash -c '. a'


     With FUNCNEST:

     export FUNCNEST=1000; echo '. a' > a; bash -c 'echo $FUNCNEST; . a'


     Code to test with N depth:

     -
     if [ $1 -eq 0 ]
     then
     echo done
     #    id
     #    ls
     #    whoami
     else
     source a $(($1-1))
     return
     fi

     id
     ls
     whoami

     -

     bash -c '. a 4280' results in crashing after 'id' is ran, but
before 'ls'. It doesn't seem to matter where I call these programs, bash
crashes all the same, whether I am in the deepest level or the top.

     Varying the parameter I can also crash in various places, like:
bash, libtinfo, or libc:

     [438676.042819] bash[408615]: segfault at 7ffedc6aaff8 ip
55bfc89a7966 sp 7ffedc6ab000 error 6 in bash[55bfc899e000+bb000]
     [438679.191182] bash[408618]: segfault at 7ffd8510 ip
7fc71696dabf sp 7ffd851fffe0 error 6 in
libc-2.33.so[7fc71690b000+158000]
     [438681.979822] bash[408619]: segfault at 7fff8f8a7ff8 ip
7f80a488d0fb sp 7fff8f8a8000 error 6 in
libc-2.33.so[7f80a482a000+158000]
     [438684.104766] bash[408620]: segfault at 7fff416f6eb0 ip
555b17112963 sp 7fff416f6e70 error 6 in bash[555b17106000+bb000]
     [438685.969473] bash[408621]: segfault at 7fffd9e1d418 ip
7f7d49d7ba76 sp 7fffd9e1d410 error 6 in
libtinfo.so.6.3[7f7d49d6d000+11000]


Fix:
     Place a limit on the depth of source-able files, like FUNCNEST.






Re: BASH recursion segfault, FUNCNEST doesn't help

2022-06-02 Thread Gergely
Hi Martin,


>> There's a slim chance this might be exploitable.
> I would really be interested in an example.

I could not produce a scenario in 15 minutes that would indicate that
this corrupts other sections, as there is a considerable gap between the
stack and everything else. This is OS-dependent though and bash has no
control over what happens should this occur.

It's not inconceivable that other OS-es or even (old) Linux in certain
configurations will place the stack close to something else that is
valuable though. However, I'm not forcing the idea that this is a
vulnerability. It technically might be, but I do understand the
hesitation of fixing something that is hard to and pretty much pointless
to exploit.

> There are many ways to exhaust memory (and other) recources, recursion is one 
> them. In your case a variable like SRCNEST (and all the code with its 
> performance impacts needed behind it) might help, but what exactly is the 
> advantage of a "maximum source nesting level exceeded" error over a 
> segmentation fault?
>
> Next we will need MAXARRUSAGE, MAXBRACEEXPAN, ...

Well, the issue is not the fact that this is a resource exhaustion, but
rather the fact that it's entirely OS-dependent and the programmer has
zero control over it. What happens should the situation occur, is not up
to bash or the programmer. The behaviour is not portable and not
recoverable. A programmer might expect a situation like this, but there
is no knob to turn to prevent an abrupt termination, unlike FUNCNEST.

Speaking for myself, I'd find an error a much MUCH more palatable
condition than a segfault in this case. In the case of an error I at
least have a chance to do cleanup or emit a message, as opposed to just
terminating out of the blue. I don't think most bash programs are
written with the expectation that they might seize to run any moment
without any warning.

On top of it, this already works just fine for FUNCNEST and even though
the default behavior is still a segfault, now a careful programmer has a
fighting chance.


Regarding performance:

I don't think many extremely high performance applications are written
in bash, but that might just be my ignorance. In any case I did some
rudimentary testing:

I ran the same recursive function until segfault 10x in a loop and
measured the total execution time. Once without FUNCTEST set and once
with FUNCTEST set to a very large number.

Here are the results:

- with FUNCTEST: 1m2.233s

- without: 1m1.691s

The difference is less than 1% with an insanely unrealistic workload.

With this in mind I suggest that SCRNEST might not be
performance-prohibitive if it can only be ran when sourcing is involved
or if it can be rolled into FUNCNEST. I am not sure how painful that
would be to implement, but I for one would happily sacrifice a bit of
speed for a better coding experience.


With that in mind I am in no position to even consider whether what I'm
suggesting makes any sense and since I am unable to do the work myself I
humbly thank you for yours and wish you a very nice day!


Gergely





Re: BASH recursion segfault, FUNCNEST doesn't help

2022-06-07 Thread Gergely
On 6/6/22 16:14, Chet Ramey wrote:

> On 6/2/22 4:00 PM, Gergely wrote:
>
>> I could not produce a scenario in 15 minutes that would indicate that
>> this corrupts other sections, as there is a considerable gap between the
>> stack and everything else. This is OS-dependent though and bash has no
>> control over what happens should this occur.
>
> Because you haven't forced bash to write outside its own address space or
> corrupt another area on the stack. This is a resource exhaustion issue,
> no more.

I did force it to write out of bounds, hence the segfault.

>> Well, the issue is not the fact that this is a resource exhaustion, but
>> rather the fact that it's entirely OS-dependent and the programmer has
>> zero control over it.
>
> The programmer has complete control over this, at least in the scenario you
> reported.

Not really, a programmer can't know how large the stack is and how many more 
recursions bash can take. This is also kernel/distro/platform dependent. I get 
that it's a hard limit to hit, but to say the programmer has complete control 
is not quite true.

Also this is a point about the "protection" being OS-dependent. In embedded 
devices the stack might very well be next to the heap, in which case this can 
be a legitimate issue. Even if Busybox is preferred in these devices, it's 
something worth considering (at least for IoT maintainers). Busybox is also 
vulnerable to this by the way.

>> What happens should the situation occur, is not up
>> to bash or the programmer. The behaviour is not portable and not
>> recoverable. A programmer might expect a situation like this, but there
>> is no knob to turn to prevent an abrupt termination, unlike FUNCNEST.
>
> If you think it's more valuable, you can build bash with a definition for
> SOURCENEST_MAX that you find acceptable. There's no user-visible variable
> to control that; it's just not something that many people request. But it's
> there if you (or a distro) want to build it in.

Recompiling works perfectly fine, however there is not configure switch, so I 
had to edit the code. This might be why the distributions are not setting this? 
I'm not sure. At least it's there.

This will not help programmers though, who just want something that Just Works.

>> Speaking for myself, I'd find an error a much MUCH more palatable
>> condition than a segfault in this case. In the case of an error I at
>> least have a chance to do cleanup or emit a message, as opposed to just
>> terminating out of the blue. I don't think most bash programs are
>> written with the expectation that they might seize to run any moment
>> without any warning.
>
> I think anyone who codes up an infinite recursion should expect abrupt
> termination. Any other scenario is variable and controlled by resource
> limits.

Sure, for unmitigated disasters of code like infinite recursions, I agree with 
you. This problem is not about that though. It's about a bounded - albeit large 
- number of recursions.

For the sake of example, consider a program with a somewhat slow signal 
handler. This program might be forced to segfault by another program that can 
send it large amounts of signals in quick succession.

Something like this:

# terminal 1

$ cat signal.sh
#!/bin/bash
echo $$
export FUNCNEST=100
trap 'echo TRAP; sleep 0.01' SIGUSR1
while true
do
sleep 1
date
done
$ ./signal.sh
39817
Tue Jun  7 01:35:41 PM UTC 2022
...
TRAP
./signal.sh: line 1: echo: write error: Interrupted system call
Segmentation fault

# terminal 2

$ while :; do kill -SIGUSR1 39817; done
bash: kill: (39817) - No such process
...

Gergely

Re: BASH recursion segfault, FUNCNEST doesn't help

2022-06-07 Thread Gergely
On 6/7/22 15:49, Chet Ramey wrote:

> On 6/7/22 7:57 AM, Gergely wrote:
>
>>> Because you haven't forced bash to write outside its own address space or
>>> corrupt another area on the stack. This is a resource exhaustion issue,
>>> no more.
>>
>> I did force it to write out of bounds, hence the segfault.
>
> That's backwards. You got a SIGSEGV, but it doesn't mean you forced bash to
> write beyond its address space. You get SIGSEGV when you exceed your stack
> or VM resource limits. Given the nature of the original script, it's
> probably the former.

I am not saying the write was successful, but the only reason it wasn't is 
because the kernel doesn't map pages there. Bash not caring about this makes 
it's relying on the kernel behaving "right".

Here's a very trivial example that'll show $rsp containing an address that is 
outside of the stack:

$ gdb --args bash -c 'cat /proc/self/maps; echo ". a" > a; . a'
(gdb) r
Starting program: /usr/bin/bash -c cat\ /proc/self/maps\;\ echo\ \".\ a\"\ \>\ 
a\;\ .\ a
[Detaching after fork from child process 45053]
4000-6000 r--p  fd:00 1573537
/usr/bin/cat
6000-b000 r-xp 2000 fd:00 1573537
/usr/bin/cat
b000-d000 r--p 7000 fd:00 1573537
/usr/bin/cat
e000-f000 r--p 9000 fd:00 1573537
/usr/bin/cat
f000-5556 rw-p a000 fd:00 1573537
/usr/bin/cat
5556-55581000 rw-p  00:00 0  [heap]
77803000-77dbb000 r--p  fd:00 1573144
/usr/lib/locale/locale-archive
77dbb000-77dbe000 rw-p  00:00 0
77dbe000-77de r--p  fd:00 1576105
/usr/lib/x86_64-linux-gnu/libc-2.33.so
77de-77f38000 r-xp 00022000 fd:00 1576105
/usr/lib/x86_64-linux-gnu/libc-2.33.so
77f38000-77f88000 r--p 0017a000 fd:00 1576105
/usr/lib/x86_64-linux-gnu/libc-2.33.so
77f88000-77f8c000 r--p 001c9000 fd:00 1576105
/usr/lib/x86_64-linux-gnu/libc-2.33.so
77f8c000-77f8e000 rw-p 001cd000 fd:00 1576105
/usr/lib/x86_64-linux-gnu/libc-2.33.so
77f8e000-77f97000 rw-p  00:00 0
77fa2000-77fc6000 rw-p  00:00 0
77fc6000-77fca000 r--p  00:00 0  [vvar]
77fca000-77fcc000 r-xp  00:00 0  [vdso]
77fcc000-77fcd000 r--p  fd:00 1576101
/usr/lib/x86_64-linux-gnu/ld-2.33.so
77fcd000-77ff1000 r-xp 1000 fd:00 1576101
/usr/lib/x86_64-linux-gnu/ld-2.33.so
77ff1000-77ffb000 r--p 00025000 fd:00 1576101
/usr/lib/x86_64-linux-gnu/ld-2.33.so
77ffb000-77ffd000 r--p 0002e000 fd:00 1576101
/usr/lib/x86_64-linux-gnu/ld-2.33.so
77ffd000-77fff000 rw-p 0003 fd:00 1576101
/usr/lib/x86_64-linux-gnu/ld-2.33.so
7ffde000-7000 rw-p  00:00 0  [stack]

Program received signal SIGSEGV, Segmentation fault.
0x5558e963 in yyparse () at ./build-bash/y.tab.c:1744
1744./build-bash/y.tab.c: No such file or directory.
(gdb) info frame 0
Stack frame at 0x7f7ffc40:
 rip = 0x5558e963 in yyparse (./build-bash/y.tab.c:1744); saved rip = 
0x55585547
 called by frame at 0x7f7ffc60
 source language c.
 Arglist at 0x7f7fed98, args:
 Locals at 0x7f7fed98, Previous frame's sp is 0x7f7ffc40
 Saved registers:
  rbx at 0x7f7ffc08, rbp at 0x7f7ffc10, r12 at 0x7f7ffc18, r13 at 
0x7f7ffc20, r14 at 0x7f7ffc28, r15 at 0x7f7ffc30, rip at 
0x7f7ffc38
(gdb) disas yyparse
Dump of assembler code for function yyparse:
...
   0x5558e959 <+57>:xor%eax,%eax
   0x5558e95b <+59>:lea0x1d0(%rsp),%rbx
=> 0x5558e963 <+67>:mov%ax,0x40(%rsp)
   0x5558e968 <+72>:mov%r8,%rbp
...
(gdb)

Here $rsp points to an invalid address. In this case reading fails, but it 
might as well be a write operation depending on what the given function does 
with it's local variables.

>> Not really, a programmer can't know how large the stack is and how many
>> more recursions bash can take. This is also kernel/distro/platform
>> dependent. I get that it's a hard limit to hit, but to say the programmer
>> has complete control is not quite true.
>
> True, the programmer can't know the stack size. But in a scenario where you
> really need to recurse hundreds or thousands of times (is there one?), the
> programmer can try t

error message of ${A:?} and ${A?} should be different

2007-10-01 Thread gergely
Configuration Information [Automatically generated, do not change]:
Machine: i686
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS:  -DPROGRAM='bash' -DCONF_HOSTTYPE='i686' 
-DCONF_OSTYPE='linux-gnu' -DCONF_MACHTYPE='i686-pc-linux-gnu' 
-DCONF_VENDOR='pc' -DLOCALEDIR='/usr/local/share/locale' -DPACKAGE='bash' 
-DSHELL -DHAVE_CONFIG_H   -I.  -I. -I./include -I./lib   -g -O2
uname output: Linux machine 2.6.21-1-686 #1 SMP Sat May 26 16:14:59 UTC 2007 
i686 GNU/Linux
Machine Type: i686-pc-linux-gnu

Bash Version: 3.2
Patch Level: 0
Release Status: release

Description:
[EMAIL PROTECTED]:/tmp/x/bash-3.2$ echo ${A?}
bash: A: parameter null or not set
[EMAIL PROTECTED]:/tmp/x/bash-3.2$ echo ${A:?}
bash: A: parameter null or not set
[EMAIL PROTECTED]:/tmp/x/bash-3.2$ A=
[EMAIL PROTECTED]:/tmp/x/bash-3.2$ echo ${A:?}
bash: A: parameter null or not set
[EMAIL PROTECTED]:/tmp/x/bash-3.2$ echo ${A?}


I don't like the first error message.  It should say something like
"bash: A: parameter not set", because I have omitted the colon.

Repeat-By:
See description.

Fix:
--- subst.c.orig2007-10-02 00:28:55.0 +0200
+++ subst.c 2007-10-02 00:30:55.0 +0200
@@ -267,7 +267,7 @@
 static WORD_DESC *parameter_brace_expand_word __P((char *, int, int));
 static WORD_DESC *parameter_brace_expand_indir __P((char *, int, int, int *, 
int *));
 static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, 
int *, int *));
-static void parameter_brace_expand_error __P((char *, char *));
+static void parameter_brace_expand_error __P((char *, char *, int));

 static int valid_length_expression __P((char *));
 static intmax_t parameter_brace_expand_length __P((char *));
@@ -5050,8 +5050,9 @@
used as the error message to print, otherwise a standard message is
printed. */
 static void
-parameter_brace_expand_error (name, value)
+parameter_brace_expand_error (name, value, check_nullness)
  char *name, *value;
+ int check_nullness;
 {
   WORD_LIST *l;
   char *temp;
@@ -5065,7 +5066,12 @@
   dispose_words (l);
 }
   else
-report_error (_("%s: parameter null or not set"), name);
+{
+  if (check_nullness)
+report_error (_("%s: parameter null or not set"), name);
+  else
+report_error (_("%s: parameter not set"), name);
+}

   /* Free the data we have allocated during this expansion, since we
  are about to longjmp out. */
@@ -6259,7 +6265,7 @@
}
  else if (c == '?')
{
- parameter_brace_expand_error (name, value);
+ parameter_brace_expand_error (name, value, check_nullness);
  return (interactive_shell ? &expand_wdesc_error : 
&expand_wdesc_fatal);
}
  else if (c != '+')