Issue
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));
}
- Why can I not free A twice in a row?
- Why can I free A again after freeing B?
- Why get I back the same chunk twice if I malloc again?
Thanks!
Solution
This is a compiler specific "exploit". First, let's call out the elephant in the room:
- You're intentionally using undefined behavior (UB), so from that point forward, all bets are off.
- 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. - 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.
Answered By - Cloud