Discussion:
Use-After-Free in Bash
Corbin Souffrant
2018-10-30 19:31:52 UTC
Permalink
Hello,

I found a reproducible use-after-free in every version of Bash from
4.4-5.0beta, that could potentially be used to escape restricted mode. I
say potentially, because I can get it to crash in restricted mode, but I
haven't gone through the effort of attempting to heap spray to overwrite
function pointers.

I read in previous threads that you don't consider most crashes in Bash to
be security issues, but before I posted something to the public mailing
list, I wanted to be sure that this was the correct place to do so. If not,
who should I email? I have a writeup, with repro and patch that I think
should work. :)

Thanks!
Corbin Souffrant
Eduardo Bustamante
2018-10-31 01:19:34 UTC
Permalink
On Tue, Oct 30, 2018 at 1:03 PM Corbin Souffrant
<***@gmail.com> wrote:
(...)
Post by Corbin Souffrant
I found a reproducible use-after-free in every version of Bash from
4.4-5.0beta, that could potentially be used to escape restricted mode. I
say potentially, because I can get it to crash in restricted mode, but I
haven't gone through the effort of attempting to heap spray to overwrite
function pointers.
Disclaimer: I'm not a maintainer.

Did you check the `devel' branch in the git repository?

I don't think the restricted mode is really advertised as a powerful
security feature, so IMO you should be able to report it here. If
you're worried though, you can always email Chet Ramey directly.
Chet Ramey
2018-10-31 01:47:28 UTC
Permalink
Post by Eduardo Bustamante
On Tue, Oct 30, 2018 at 1:03 PM Corbin Souffrant
(...)
Post by Corbin Souffrant
I found a reproducible use-after-free in every version of Bash from
4.4-5.0beta, that could potentially be used to escape restricted mode. I
say potentially, because I can get it to crash in restricted mode, but I
haven't gone through the effort of attempting to heap spray to overwrite
function pointers.
Disclaimer: I'm not a maintainer.
Did you check the `devel' branch in the git repository?
He did; I just fixed it today.
Post by Eduardo Bustamante
I don't think the restricted mode is really advertised as a powerful
security feature, so IMO you should be able to report it here. If
you're worried though, you can always email Chet Ramey directly.
I looked at it and can't see how to exploit it to execute arbitrary code.
It's also only a problem if you're not using the bash malloc.


Chet
--
``The lyf so short, the craft so long to lerne.'' - Chaucer
``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRU ***@case.edu http://tiswww.cwru.edu/~chet/
Corbin Souffrant
2018-10-30 23:54:09 UTC
Permalink
I emailed with Chet today and got approval to post the writeup here. He
has already applied the patch. Thanks again for the fast response!

-Corbin

Use After Free Writeup:

In bash <3.2 using ^C while in a large brace expansion would slowly eat
memory with no way to ^C, so in bash 3.2 (2006-10-11) they introduced
the ability to ^C. However with this, they introduced a memory leak
(that is noted in the source code). In bash 4.4-beta (2015-10-12), a
patch for this was introduced which caused the following use-after-free
that exists in all versions of the code since.

The following writeup used bash-4.4.18.tar.gz    2018-01-30 16:15   
9.0M from the URL http://ftp.gnu.org/gnu/bash/bash-4.4.18.tar.gz

Version checking:
./bash --version
GNU bash, version 4.4.18(2)-release (x86_64-unknown-linux-gnu)

First run the following and ^C the execution immediately:
[***@loliponi bash-4.4.18]$ for i in {0..7777777}; do echo $i; done
^C[1]    29966 segmentation fault (core dumped)  ./bash

Before jumping into the debug, I will note that I made two modifications
to the build to make the debug easier to follow, but it works without
these changes:
1) Change CFLAGS optimatzation flags to -O0
2) Modify lib/sh/stringvec.c:83 (strvec_flush()) to "volatile register
int i;"

After opening up the coredump in gdb I ran bt to see the current stack
frames.
(gdb) bt
#0  0x000055c5e3eecb07 in internal_free (mem=0xdfdfdfdfdfdfdfdf,
file=0x55c5e3f065a8 "stringvec.c", line=89,
    flags=<optimized out>) at malloc.c:858
#1  0x000055c5e3ea2d31 in sh_xfree (string=0xdfdfdfdfdfdfdfdf,
file=0x55c5e3f065a8 "stringvec.c", line=89)
    at xmalloc.c:221
#2  0x000055c5e3ec5657 in strvec_flush
(array=***@entry=0x55c5e5d2d008) at stringvec.c:89
#3  0x000055c5e3ec569e in strvec_dispose (array=0x55c5e5d2d008) at
stringvec.c:99
#4  0x000055c5e3e8e5f6 in mkseq (start=0, end=7777777, incr=1, type=1,
width=0) at braces.c:439

The interesting behavior happens because strvec_flush called free() with
a bad value, so lets find out why...
(gdb) f 2
#2  0x000055c5e3ec5657 in strvec_flush
(array=***@entry=0x55c5e5d2d008) at stringvec.c:89
89        free (array[i]);

The currrent iteration in the loop when I hit ^C
(gdb) info local
i = 5297567

(5297567 * 8) + 0x55c5e5d2d008 = 55C5E8597D00

The contents of the array. Note that at exactly the expected offset, we
see a corresponding set of 0xdfdfdfdf where the crash occured. This appears
to be some kind of guard page or zero'd out memory. On some systems, we
are not seeing this memory being set to zero'd value, leading to a potential
compromise with a use after free vulnerability, due to the fact that an
arbitrary address left in stale memory via a heap spray would lead to a free
that could be controlled to point at a function pointer that the
attacker would allocate over with a new malicious address.

(gdb) x/8wx 0x55c5e5d2d008
0x55c5e5d2d008:    0xe5cb42d8    0x000055c5    0xe5cb42e8 0x000055c5
0x55c5e5d2d018:    0xe5cb42f8    0x000055c5    0xe5cb4308 0x000055c5
[...]
(gdb) x/8wx 0x55c5e8597cf0
0x55c5e8597cf0:    0xf3ed2fe8    0x000055c5    0xf3ed3008 0x000055c5
0x55c5e8597d00:    0xdfdfdfdf    0xdfdfdfdf    0xdfdfdfdf 0xdfdfdfdf


Code segments for clarity:

braces.c contains the function mkseq which calls strvec_dispose on char
**result if an interrupt is triggered while it is building the array.
static char **
mkseq (start, end, incr, type, width)
     intmax_t start, end, incr;
     int type, width;
{
  intmax_t n, prevn;
  int i, j, nelem;
  char **result, *t;
[...]
do
{
#if defined (SHELL)
      if (ISINTERRUPT)
{
          strvec_dispose (result);
          result = (char **)NULL;
}
QUIT;
#endif
[...]
}
  while (1);
[...]
}

In lib/sh/stringvec.c, strvec_dispose() calls strvec_flush() in the same
file.
void
strvec_dispose (array)
     char **array;
{
  if (array == 0)
return;

  strvec_flush (array);
  free (array);
}

Finally strvec_flush() iterates through all the values in the array and
falsely assumes that unallocated entries will be set to 0, leading to
the bug.
void
strvec_flush (array)
     char **array;
{
  register int i;

  if (array == 0)
return;

  for (i = 0; array[i]; i++)
    free (array[i]);
}

A patch for this bug would involve either zero'ing out allocated memory
when it is retrieved or marking where the last entry was written.

This is an example of a patch that would fix the issue. I wasn't sure if
I should diff it with 5.0 alpha or 4.4.18, so I diff'd it with 4.4.18
and here is where the code changes.
braces.c:L439
result[i] = (char *)0; // Signal to free where the current position is.
strvec_dispose (result);

$ diff braces.c braces.c.new
438a439
           result[i] = (char *)0; // Signal to free where the current
position is.
Hello,
I found a reproducible use-after-free in every version of Bash from
4.4-5.0beta, that could potentially be used to escape restricted mode. I
say potentially, because I can get it to crash in restricted mode, but I
haven't gone through the effort of attempting to heap spray to overwrite
function pointers.
I read in previous threads that you don't consider most crashes in Bash to
be security issues, but before I posted something to the public mailing
list, I wanted to be sure that this was the correct place to do so. If not,
who should I email? I have a writeup, with repro and patch that I think
should work. :)
Thanks!
Corbin Souffrant
Continue reading on narkive:
Loading...