The first-fit algorithm is the core allocation strategy used by glibc malloc when selecting a free chunk to satisfy a memory request. When malloc needs to allocate memory, it searches through the appropriate bin and returns the first chunk that is large enough, rather than searching for the best-fitting chunk.
This seemingly simple design decision has significant implications for heap exploitation, particularly in use-after-free scenarios.
The following program demonstrates first-fit allocation behavior:
first_fit.c
#include <stdio.h>#include <stdlib.h>#include <string.h>int main(){ fprintf(stderr, "This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.\n"); fprintf(stderr, "glibc uses a first-fit algorithm to select a free chunk.\n"); fprintf(stderr, "If a chunk is free and large enough, malloc will select this chunk.\n"); fprintf(stderr, "This can be exploited in a use-after-free situation.\n"); fprintf(stderr, "Allocating 2 buffers. They can be large, don't have to be fastbin.\n"); char* a = malloc(0x512); char* b = malloc(0x256); char* c; fprintf(stderr, "1st malloc(0x512): %p\n", a); fprintf(stderr, "2nd malloc(0x256): %p\n", b); fprintf(stderr, "we could continue mallocing here...\n"); fprintf(stderr, "now let's put a string at a that we can read later \"this is A!\"\n"); strcpy(a, "this is A!"); fprintf(stderr, "first allocation %p points to %s\n", a, a); fprintf(stderr, "Freeing the first one...\n"); free(a); fprintf(stderr, "We don't need to free anything again. As long as we allocate smaller than 0x512, it will end up at %p\n", a); fprintf(stderr, "So, let's allocate 0x500 bytes\n"); c = malloc(0x500); fprintf(stderr, "3rd malloc(0x500): %p\n", c); fprintf(stderr, "And put a different string here, \"this is C!\"\n"); strcpy(c, "this is C!"); fprintf(stderr, "3rd allocation %p points to %s\n", c, c); fprintf(stderr, "first allocation %p points to %s\n", a, a); fprintf(stderr, "If we reuse the first allocation, it now holds the data from the third allocation.\n");}
char* a = malloc(0x512); // Gets address 0x555555559260char* b = malloc(0x256); // Gets address 0x555555559780strcpy(a, "this is A!");
Two chunks are allocated. Pointer a contains “this is A!”.
2
Free first chunk
free(a); // Returns chunk to free list
The 0x512-byte chunk is freed and added to the appropriate bin (likely unsorted bin or tcache).
3
Allocate smaller chunk
c = malloc(0x500); // Gets address 0x555555559260strcpy(c, "this is C!");
Because 0x500 < 0x512, the freed chunk at a’s address is large enough. First-fit returns this chunk immediately.
4
Use-after-free
fprintf(stderr, "first allocation %p points to %s\n", a, a);// Prints: first allocation 0x555555559260 points to this is C!
Now a and c point to the same memory. Reading from a shows data written via c.
Critical Security Implication: The old pointer a still points to valid memory, but that memory now belongs to a new allocation c. This is a use-after-free vulnerability.
struct User { char name[64]; void (*print)(struct User*);};// Allocate and free a User objectstruct User* user = malloc(sizeof(struct User));user->print = print_user;free(user);// Attacker can allocate same-sized chunkchar* attacker_data = malloc(sizeof(struct User));strcpy(attacker_data, "\x90\x90...\x41\x41\x41\x41...");// Now user->print points to attacker-controlled data!user->print(user); // Can hijack control flow