Fee
Fee

Reputation: 861

understanding of double free attack

I got a small piece of code from here https://github.com/shellphish/how2heap/blob/master/fastbin_dup.c

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

int main()
{
    printf("This file demonstrates a simple double-free attack with fastbins.\n");

    printf("Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    printf("1st malloc(8): %p\n", a);
    printf("2nd malloc(8): %p\n", b);
    printf("3rd malloc(8): %p\n", c);

    printf("Freeing the first one...\n");
    free(a);

    printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    printf("So, instead, we'll free %p.\n", b);
    free(b);

    printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
    printf("1st malloc(8): %p\n", malloc(8));
    printf("2nd malloc(8): %p\n", malloc(8));
    printf("3rd malloc(8): %p\n", malloc(8));
}
  1. Why can I not free A twice in a row?
  2. Why can I free A again after freeing B?
  3. Why get I back the same chunk twice if I malloc again?

Thanks!

Upvotes: 1

Views: 5433

Answers (3)

deanj
deanj

Reputation: 11

The previous answers don't actually explain. Here:

Why can I not free A twice in a row?

Because the glibc implementation contains basic checks to make sure that when a chunk is added to the freelist, it's not already on that list (you end up with a self-referential pointer). It's not a security defense as such, but more a fortunate protection against an obvious bug

Why can I free A again after freeing B?

because A won't be adjacent to A on the freelist. The freelist will look like:

-->A-->B-->A

So there won't be any self-referential pointers

Why get I back the same chunk twice if I malloc again?

When you request the first chunk, you get A, then B, then A. It's simply "popping" the next item from the freelist.

Upvotes: 1

orbitcowboy
orbitcowboy

Reputation: 1548

As recommended by DevNull, static code analysis can assist you be detecting potential coding issues. Here is the output of cppcheck when your code is scanned:

$ cppcheck --enable=all test.cpp 
  Checking test.cpp...
  [test.cpp:27]: (error) Memory pointed to by 'a' is freed twice.
  [test.cpp:33]: (error) Memory leak: c
  [test.cpp:30]: (error) Allocation with malloc, printf doesn't release it.
  [test.cpp:27]: (error) Deallocating a deallocated pointer: a

Upvotes: 0

Cloud
Cloud

Reputation: 19333

This is a compiler specific "exploit". First, let's call out the elephant in the room:

  1. You're intentionally using undefined behavior (UB), so from that point forward, all bets are off.
  2. You're not using any static code analysis tools like cppcheck or lint, nor any debugging tools like valgrind to catch this. In production systems, you would use such tools to at least attempt to catch these bugs.
  3. You can always pull the latest glibc source and discover this for your yourself :)

Now, to the question. First, this exploit only really works on GCC with "fastbins" enabled. If you just add the following to your code:

#include <malloc.h>
// ...
mallopt(M_MXFAST, 0);

Then it will crash much sooner:

This file demonstrates a simple double-free attack with fastbins.
Allocating 3 buffers.
1st malloc(8): 0x556f373b1010
2nd malloc(8): 0x556f373b1030
3rd malloc(8): 0x556f373b1050
Freeing the first one...
If we free 0x556f373b1010 again, things will crash because 0x556f373b1010 is at the top of the free list.
So, instead, we'll free 0x556f373b1030.
Now, we can free 0x556f373b1010 again, since it's not the head of the free list.
*** Error in `./a.out': double free or corruption (!prev): 0x0000556f373b1010 ***
Aborted (core dumped)

This is due to how the "fastbins" work:

M_MXFAST (since glibc 2.3) Set the upper limit for memory allocation requests that are satisfied using "fastbins". (The measurement unit for this parameter is bytes.) Fastbins are storage areas that hold deallocated blocks of memory of the same size without merging adjacent free blocks. Subsequent reallocation of blocks of the same size can be handled very quickly by allocating from the fastbin, although memory fragmentation and the overall memory footprint of the program can increase. The default value for this parameter is 64*sizeof(size_t)/4 (i.e., 64 on 32-bit architectures). The range for this parameter is 0 to 80*sizeof(size_t)/4. Setting M_MXFAST to 0 disables the use of fastbins.

The free call doesn't immediately release the memory, and just marks it as being available for future malloc() calls. If you immediately try to issue the free() call against the same chunk of memory twice in a row, an internal pointer check will catch it, but the damage has already been done (UB invoked), yet the same check won't handle the example case you presented.

As for the final 3 malloc() calls generating the same address twice: UB has been invoked, and has corrupted the free list.

Upvotes: 1

Related Questions