malloc: harden removal from unsorted list

Message ID 9b74b50e-66a6-3321-5668-d89b35a21cd8@google.com
State New
Headers show
Series
  • malloc: harden removal from unsorted list
Related show

Commit Message

Francois Goichon Feb. 23, 2018, 12:17 p.m.
There isn't any linked list check when getting chunks out of the 
unsorted list which allows an attacker to write &av->top 
(unsorted_chunks chunk) almost anywhere (e.g. into a bin of their 
choosing to get it out in a subsequent allocation), with a relatively 
simple corruption:

--
   // Initial state
   long * c = malloc(0x100);
   malloc(0x100);
   free(c);

   // Corruption: point c->bk to bin used in alloc after next
   c[1] += (0x110 >> 4)*8*2 - 8;
   malloc(0x100);
   c = malloc(0x100);  // &av->top
--

Controlling bins is likely to be enough to gain code execution by 
overwriting farther ahead in .data or .bss (e.g. __free_hook).

Tested on i386 and x86_64.


	* malloc/malloc.c (_int_malloc): Added check before removing from
	unsorted list.

---
  malloc/malloc.c | 6 ++++--
  1 file changed, 4 insertions(+), 2 deletions(-)

Comments

Florian Weimer Feb. 25, 2018, 3:15 p.m. | #1
* Francois Goichon:

> -		    malloc_printerr ("malloc(): corrupted unsorted chunks");

> +		    malloc_printerr ("malloc(): corrupted unsorted chunks 2");


I suggest not to change the existing messages, so that we can
interpret them without closely looking at the glibc version.  I think
that's more useful than keeping the messages in source code order.
Francois Goichon Feb. 26, 2018, 10:48 a.m. | #2
On the exploitability of this, the += was just there to explicitly point
out the targeted bin, but an actual exploitation would more likely look
at  overwriting the least significant byte(s), or the full address if in
a position to leak a free or uninitialized chunk beforehand.

I believe this is in line with other attacks that malloc tries to
prevent, i.e. where controlling a single pointer is sufficient to get
significant control over the application's behavior. I agree it's a fine
line though and that it's not malloc's role to stop exploitation of edge
cases where the attacker has an unreasonable amount of control.

Happy to try and put numbers on the performance impact of this change
if it's a concern here - if yes is there a benchmark that would be best
suited to this usecase?

Rolled back the existing messages as suggested:


---
  malloc/malloc.c | 2 ++
  1 file changed, 2 insertions(+)

diff --git a/malloc/malloc.c b/malloc/malloc.c
index 58f9acd4d1..fd1a263e9e 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c
@@ -3775,6 +3775,8 @@ _int_malloc (mstate av, size_t bytes)
              }

            /* remove from unsorted list */
+          if (__glibc_unlikely (bck->fd != victim))
+            malloc_printerr ("malloc(): corrupted unsorted chunks 3");
            unsorted_chunks (av)->bk = bck;
            bck->fd = unsorted_chunks (av);

Patch

diff --git a/malloc/malloc.c b/malloc/malloc.c
index 58f9acd4d1..17d373eca9 100644
--- a/malloc/malloc.c
+++ b/malloc/malloc.c
@@ -3775,6 +3775,8 @@  _int_malloc (mstate av, size_t bytes)
              }

            /* remove from unsorted list */
+          if (__glibc_unlikely (bck->fd != victim))
+            malloc_printerr ("malloc(): corrupted unsorted chunks");
            unsorted_chunks (av)->bk = bck;
            bck->fd = unsorted_chunks (av);

@@ -3941,7 +3943,7 @@  _int_malloc (mstate av, size_t bytes)
                    bck = unsorted_chunks (av);
                    fwd = bck->fd;
  		  if (__glibc_unlikely (fwd->bk != bck))
-		    malloc_printerr ("malloc(): corrupted unsorted chunks");
+		    malloc_printerr ("malloc(): corrupted unsorted chunks 2");
                    remainder->bk = bck;
                    remainder->fd = fwd;
                    bck->fd = remainder;
@@ -4045,7 +4047,7 @@  _int_malloc (mstate av, size_t bytes)
                    bck = unsorted_chunks (av);
                    fwd = bck->fd;
  		  if (__glibc_unlikely (fwd->bk != bck))
-		    malloc_printerr ("malloc(): corrupted unsorted chunks 2");
+		    malloc_printerr ("malloc(): corrupted unsorted chunks 3");
                    remainder->bk = bck;
                    remainder->fd = fwd;
                    bck->fd = remainder;