[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