bug-bash
[Top][All Lists]
Advanced

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

Re: Use-After-Free in Bash


From: Corbin Souffrant
Subject: Re: Use-After-Free in Bash
Date: Tue, 30 Oct 2018 16:54:09 -0700
User-agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101 Thunderbird/60.2.1

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@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=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=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.

On 10/30/2018 12:31 PM, Corbin Souffrant wrote:
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






reply via email to

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