Dailydave mailing list archives

Re: gcc 4.1 security features


From: Joel Eriksson <je () bitnux com>
Date: Fri, 17 Feb 2006 02:16:17 +0100

Hi Gadi & the rest of DailyDaves,

Regarding glibc/malloc..

On Thu, Feb 16, 2006 at 08:44:54PM +0200, Gadi Evron wrote:
In 4.1? I will have to look. I sent this out to a different mailing list 
a few weeks ago, maybe people here will have some answers. This relates 
to similar changes in gcc/glic instead of kernel level prevention.

-----

In version 2.3.4 of glibc...

if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0) ||
__builtin_expect ((uintptr_t) p & MALLOC_ALIGN_MASK, 0))

^^^ This line was added.

There is also a comment on it:

+  /* Little security check which won't hurt performance: the
+     allocator never wrapps around at the end of the address space.
+     Therefore we can exclude some size values which might appear
+     here by accident or by "design" from some intruder.  */
+  if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
+      || __builtin_expect ((uintptr_t) p & MALLOC_ALIGN_MASK, 0))
+    {
+      errstr = "free(): invalid pointer";
+    errout:
+      malloc_printerr (check_action, errstr, mem);
+      return;
+    }

It was mentioned on http://www.securityfocus.com/columnists/359.

Now, my questions if some of you can be so kind as to try and answer:
In your estimation, how many people actually heard about this (outside
the tight circle of secure coding maniacs)?

In your estimation, how many people actually go through the pain of
upgrading glibc?

This seems effective to me. In retrospect, how effective did it prove to
be over the past year, in your experience? Is it too early to tell?

That patch was actually added in glibc-2.3.3. :)

It's not very effective though, even though it stopped the "unlinkMe-chunk"
technique that had been published in Phrack, that used a negative size-field to
point next_chunk into a fake chunk lower in the heap.

Note that the only thing checked is that p + p->size > p, where p is the chunk
that is free()'d. Not only is this check not enough, both the prev_size-field
and the size-field of nextchunk/prevchunk are still unchecked.

Of course, this check is trivial to bypass when it's possible to include NUL-bytes
in the overflow string, by pointing nextchunk into the chunk itself. But even
when one cannot use NUL-bytes there are at least two possibilities.

Either one can set the size-field with a huge positive number that when added to
the chunk address becomes a valid address on the stack. The distance between the
heap and the stack is so huge that there will be no NUL-bytes in the number then.

This technique can usually not be used reliably for anything but local exploits,
but note that it's not necessary to be able to embed anything in the stack, it's
enough to bruteforce a stack location that does not trigger forward consolidation
(for instance any address that points 4 bytes before a 32-bit 1-value).

Another method is to only partially overwrite the size-field, so it points another
chunk in front of it. Either we can make it point to any chunk that resides after
a busy chunk (so the PREV_INUSE-bit is set and forward consolidation is not
triggered) or into another chunk that we control the contents of. If we point it
into another chunk we control the contents of we might want to get that unlink():ed
too so we can overwrite multiple addresses for each free().

AFAIK these techniques have not been discussed before, but I expect anyone doing
any serious glibc-based heap exploitation to figure them out by themselves just
as I did. :)

Btw, in the original patch to glibc-2.3.3 the address of the invalid pointer was
printed in the error message. This was pretty convenient when exploiting the
cvs Argumentx-bug for instance. ;)

A more relevant fix was added to glibc-2.3.4 during late august 2004 though:

#define unlink(P, BK, FD) {                                            \
  FD = P->fd;                                                          \
  BK = P->bk;                                                          \
  if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                \
    malloc_printf_nc (check_action,                                    \
                      "corrupted double-linked list at %p!\n", P);     \
  FD->bk = BK;                                                         \
  BK->fd = FD;                                                         \

By checking that p->fd->bk == p && p->bk->fd == p before unlinking, basically all
unlink() based attacks are defeated. It's a shame (well, for the glibc people,
not the exploit coders) that it took so long for this patch to get into glibc
actually. Stefan Esser proposed it quite early:

http://www.derkeiler.com/Mailing-Lists/securityfocus/bugtraq/2003-12/0019.html

But.. Of course this is no silver bullet either. It stops the unlink()-based
attacks, but small chunks (called "fastchunks") are not placed in double-linked
lists and thus there are no back-pointers to check. A free()'d fastchunk
contains only one pointer, to the next chunk in the list of free chunks of that
size. Overwriting a fastchunk = Controlling what pointer a later call to
malloc() is going to return. Very convenient. :)

Note that fastchunks are also useful for exploiting certain double free() bugs.

Here's a small challenge I made for demonstrating this, in case anyone wants to
try for themselves. :) Should be pretty easy, since I've basically prepared the
"good" heap layout for you already. It was the 7:th out of the fourteen different
heap-based challenges I used in an exploit coding course I taught to swedish
.gov/.mil people in december 2004. Heap-based bugs were covered during day 4 out
of 6, it was really ground-up. :)

---8<----------------------------------------------------------------------------
/*
 * Copyright (C) Joel Eriksson <je () bitnux com>, Bitnux 2004
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int main(int argc, char **argv)
{
        char *p1, *p2, *p3, *p4, *p5;

        if (argc != 3) {
                fprintf(stderr, "Usage: %s STR1 STR2\n", argv[0]);
                return 1;
        }

        p1 = malloc(16);
        p2 = malloc(16);

        free(p1);
        free(p1);

        p3 = malloc(16);

        strncpy(p3, argv[1], 15);
        p4 = malloc(16);
        p5 = malloc(16);

        strncpy(p5, argv[2], 15);

        return 0;
}
----------------------------------------------------------------------------8<---

-- 
Best Regards,
   Joel Eriksson
-------------------------------------------------
Cellphone: +46-70 228 64 16 Home: +46-18-30 35 55
Security Research & Systems Development at Bitnux
PGP Key Server pgp.mit.edu, PGP Key ID 0x08811B44
DF38 5806 0EFB 196E E4B6 34B5 4C01 73BB 0881 1B44
-------------------------------------------------


Current thread: