Skip to main content

Heap Hardening Measures

Glibc provides several “hardening” features designed to make heap exploitation more difficult. Understanding these measures is crucial for both defensive security and for testing whether exploitation techniques work in hardened environments.
Hardening measures add runtime checks and modify heap behavior to detect or prevent exploitation attempts. While they raise the bar for exploitation, many are not foolproof defenses.

Environment Variables

Glibc respects several environment variables that enable heap debugging and hardening features.

MALLOC_CHECK_

Enable consistency checks

MALLOC_PERTURB_

Perturb allocated memory

MALLOC_MMAP_THRESHOLD_

Force mmap allocation

MALLOC_CHECK_

Overview

MALLOC_CHECK_ enables lightweight heap consistency checking. When set, glibc performs additional validation on heap operations.

Values

  • MALLOC_CHECK_=0 - Disabled (default)
  • MALLOC_CHECK_=1 - Print error message to stderr, continue execution
  • MALLOC_CHECK_=2 - Abort immediately on heap corruption
  • MALLOC_CHECK_=3 - Print error and abort (most verbose)

Usage

# Enable with error messages
export MALLOC_CHECK_=1
./your_program

# Enable with immediate abort
export MALLOC_CHECK_=2
./your_program

# One-time usage
MALLOC_CHECK_=2 ./your_program

What It Detects

  • Double free - Freeing already-freed pointers
  • Invalid free - Freeing non-heap pointers
  • Heap corruption - Some metadata corruption
  • Use-after-free - Limited detection
#include <stdlib.h>
#include <stdio.h>

int main() {
    char *ptr = malloc(100);
    free(ptr);
    free(ptr);  // Double free!
    return 0;
}
Without MALLOC_CHECK_:
$ ./double_free
# May or may not crash, undefined behavior
With MALLOC_CHECK_:
$ MALLOC_CHECK_=2 ./double_free
*** Error in `./double_free': free(): invalid pointer: 0x0000000000abcd00 ***
Aborted (core dumped)

Testing how2heap Techniques

Many how2heap techniques will be detected by MALLOC_CHECK_:
# Test if technique works with basic hardening
MALLOC_CHECK_=1 ./fastbin_dup

# Strict checking - will abort on corruption
MALLOC_CHECK_=2 ./tcache_poisoning
MALLOC_CHECK_ adds overhead and is meant for debugging, not production use. Some sophisticated exploits can bypass these checks.

Limitations

  • Performance overhead - Slower execution
  • Not comprehensive - Doesn’t catch all corruption
  • Bypassable - Careful exploits can avoid detection
  • Debug only - Not suitable for production

MALLOC_PERTURB_

Overview

MALLOC_PERTURB_ causes malloc to initialize allocated memory and freed memory with specific byte patterns. This helps detect use-after-free and uninitialized memory bugs.

Values

  • MALLOC_PERTURB_=0 - Disabled (default)
  • MALLOC_PERTURB_=<N> - Fill with byte value N (1-255)
Common values:
  • MALLOC_PERTURB_=1 - Fill with 0x01
  • MALLOC_PERTURB_=255 - Fill with 0xFF
  • MALLOC_PERTURB_=170 - Fill with 0xAA (0b10101010)

Behavior

On allocation:
  • New allocations filled with ~N (bitwise NOT of N)
On free:
  • Freed memory filled with N

Usage

# Fill allocated memory with ~1 (0xFE), freed with 0x01
export MALLOC_PERTURB_=1
./your_program

# Fill allocated memory with ~255 (0x00), freed with 0xFF
MALLOC_PERTURB_=255 ./your_program
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
    char *ptr = malloc(100);
    strcpy(ptr, "secret data");
    free(ptr);
    
    // Use after free!
    printf("Data: %s\n", ptr);
    return 0;
}
Without MALLOC_PERTURB_:
$ ./use_after_free
Data: secret data
# Memory still contains original data
With MALLOC_PERTURB_:
$ MALLOC_PERTURB_=255 ./use_after_free
Data: ÿÿÿÿÿÿÿÿÿÿÿÿ
# Freed memory overwritten with 0xFF

Impact on Exploitation

Techniques affected:
  • Use-after-free - Freed objects contain predictable pattern instead of useful data
  • Heap spraying - Allocated memory no longer contains zeros
  • Information leaks - Uninitialized memory filled with pattern
  • Partial overwrites - Known values make detection easier

Testing with how2heap

# Test if technique relies on memory contents
MALLOC_PERTURB_=170 ./house_of_lore

# Test with different patterns
MALLOC_PERTURB_=1 ./technique
MALLOC_PERTURB_=255 ./technique
Many heap exploitation techniques still work with MALLOC_PERTURB_ because they manipulate metadata rather than data contents.

Best Practices

For development:
# Add to your shell profile for all debugging
export MALLOC_PERTURB_=$(($RANDOM % 255 + 1))
For testing:
# Test with different patterns
for i in 1 85 170 255; do
    echo "Testing with MALLOC_PERTURB_=$i"
    MALLOC_PERTURB_=$i ./test_program
done

MALLOC_MMAP_THRESHOLD_

Overview

MALLOC_MMAP_THRESHOLD_ controls the size threshold above which malloc uses mmap() instead of heap allocation.

Default Behavior

  • Default threshold: 128 KB (131072 bytes)
  • Allocations >= threshold use mmap()
  • Allocations < threshold use heap (sbrk/brk)

Values

  • MALLOC_MMAP_THRESHOLD_=<bytes> - Set custom threshold
  • MALLOC_MMAP_THRESHOLD_=0 - Always use heap (never mmap)
  • MALLOC_MMAP_THRESHOLD_=1 - Almost always use mmap

Usage

# Force all allocations to use mmap
export MALLOC_MMAP_THRESHOLD_=1
./your_program

# Disable mmap, use only heap
MALLOC_MMAP_THRESHOLD_=0 ./your_program

# Set custom threshold (1 MB)
MALLOC_MMAP_THRESHOLD_=1048576 ./your_program

Impact on Exploitation

When forcing mmap (threshold=1):
  • No heap chunks - Traditional heap techniques don’t apply
  • Different layout - Memory addresses non-contiguous
  • Different metadata - mmap chunks have different structure
  • Isolated allocations - Can’t overflow between chunks
When disabling mmap (threshold=0):
  • All heap allocated - Large allocations use heap
  • Heap exhaustion possible - Large allocations can exhaust heap
  • Different exploitation surface - Different techniques applicable
# Normal behavior
./overlapping_chunks
# Works - chunks are on heap

# Force mmap
MALLOC_MMAP_THRESHOLD_=1 ./overlapping_chunks
# May fail - chunks are mmap'd separately

# See memory mapping
MALLOC_MMAP_THRESHOLD_=1 ./program &
cat /proc/$!/maps

Testing with how2heap

# Test if technique requires heap allocation
MALLOC_MMAP_THRESHOLD_=1 ./technique

# Test mmap-specific techniques
MALLOC_MMAP_THRESHOLD_=0 ./mmap_overlapping_chunks
Some how2heap examples like mmap_overlapping_chunks specifically target mmap’d allocations.

Relationship to Other Settings

Often combined with other malloc tuning:
# Comprehensive mmap testing
MALLOC_MMAP_THRESHOLD_=1 \
MALLOC_MMAP_MAX_=0 \
./program

Runtime Functions

Glibc provides programmatic APIs for heap debugging and hardening.

mcheck()

Enable heap consistency checks

mallopt()

Configure malloc behavior

mtrace()

Trace memory operations

mcheck()

Overview

mcheck() enables heap consistency checking similar to MALLOC_CHECK_, but controlled programmatically. Documentation: http://www.gnu.org/software/libc/manual/html_node/Heap-Consistency-Checking.html

Function Signature

#include <mcheck.h>

int mcheck(void (*abortfn)(enum mcheck_status));
enum mcheck_status mprobe(void *ptr);

Usage

Basic usage:
#include <mcheck.h>
#include <stdlib.h>

int main() {
    // Must call before any malloc operations
    mcheck(NULL);  // Use default abort function
    
    // Your code here
    char *ptr = malloc(100);
    free(ptr);
    
    return 0;
}
Custom abort handler:
#include <mcheck.h>
#include <stdlib.h>
#include <stdio.h>

void my_abort_handler(enum mcheck_status status) {
    const char *msg;
    switch(status) {
        case MCHECK_OK:
            msg = "No error detected";
            break;
        case MCHECK_DISABLED:
            msg = "mcheck not enabled";
            break;
        case MCHECK_HEAD:
            msg = "Memory before allocation corrupted";
            break;
        case MCHECK_TAIL:
            msg = "Memory after allocation corrupted";
            break;
        case MCHECK_FREE:
            msg = "Block already freed";
            break;
        default:
            msg = "Unknown error";
    }
    fprintf(stderr, "Heap corruption: %s\n", msg);
    abort();
}

int main() {
    mcheck(my_abort_handler);
    // Your code
    return 0;
}

Compilation

# Link with mcheck
gcc -o program program.c -lmcheck

# Or use MALLOC_CHECK_ instead (no recompilation needed)
MALLOC_CHECK_=2 ./program

mprobe() - Manual Checking

#include <mcheck.h>
#include <stdlib.h>

enum mcheck_status status = mprobe(ptr);
if (status != MCHECK_OK) {
    // Handle corruption
}
#include <mcheck.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int main() {
    mcheck(NULL);
    
    char *ptr = malloc(10);
    
    // Overflow - corrupt heap metadata
    memset(ptr, 'A', 100);  // Write beyond allocation!
    
    // This free will detect corruption
    free(ptr);
    
    printf("This won't be reached\n");
    return 0;
}
Output:
*** glibc detected *** ./program: free(): invalid pointer: 0x00007f8a9c0008c0 ***
Aborted (core dumped)

Limitations

  • Must enable early - Before first malloc
  • Performance overhead - Adds checks to every operation
  • Not comprehensive - Won’t catch all corruption
  • Incompatible with custom allocators - Only works with glibc malloc

mallopt()

Overview

mallopt() allows fine-grained control over malloc behavior and parameters. Documentation: http://www.gnu.org/software/libc/manual/html_node/Malloc-Tunable-Parameters.html

Function Signature

#include <malloc.h>

int mallopt(int param, int value);

Key Parameters

M_MMAP_THRESHOLD
// Set mmap threshold to 1MB
mallopt(M_MMAP_THRESHOLD, 1024 * 1024);

// Disable mmap (use only heap)
mallopt(M_MMAP_THRESHOLD, -1);
M_MMAP_MAX
// Limit number of mmap regions to 10
mallopt(M_MMAP_MAX, 10);

// Disable mmap entirely
mallopt(M_MMAP_MAX, 0);
M_TRIM_THRESHOLD
// Set heap trimming threshold
mallopt(M_TRIM_THRESHOLD, 128 * 1024);

// Disable heap trimming
mallopt(M_TRIM_THRESHOLD, -1);
M_TOP_PAD
// Set top padding
mallopt(M_TOP_PAD, 4096);
M_ARENA_MAX / M_ARENA_TEST
// Limit arenas (for multi-threaded programs)
mallopt(M_ARENA_MAX, 2);

Complete Example

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

int main() {
    // Configure malloc before allocations
    
    // Always use heap, never mmap
    mallopt(M_MMAP_MAX, 0);
    
    // Disable heap trimming
    mallopt(M_TRIM_THRESHOLD, -1);
    
    // Set top padding
    mallopt(M_TOP_PAD, 0);
    
    // Now use malloc normally
    char *ptr1 = malloc(1000000);  // Would normally use mmap
    printf("Allocated large chunk on heap\n");
    
    free(ptr1);
    return 0;
}

Testing Techniques with mallopt()

// Test technique without mmap
mallopt(M_MMAP_MAX, 0);
// Run exploitation technique
// Test with minimal arenas (single-threaded behavior)
mallopt(M_ARENA_MAX, 1);
// Run exploitation technique
mallopt() provides more granular control than environment variables but requires source code modification.

mtrace()

Overview

mtrace() enables memory allocation tracing, logging all malloc/free operations to a file for analysis. Man page: http://manpages.ubuntu.com/mtrace

Function Signature

#include <mcheck.h>

void mtrace(void);
void muntrace(void);

Usage

Program code:
#include <mcheck.h>
#include <stdlib.h>

int main() {
    // Set output file via environment variable
    mtrace();  // Start tracing
    
    char *ptr1 = malloc(100);
    char *ptr2 = malloc(200);
    free(ptr1);
    free(ptr2);
    
    muntrace();  // Stop tracing (optional)
    return 0;
}
Compilation:
gcc -o program program.c
Running with trace:
# Set trace output file
export MALLOC_TRACE=trace.log
./program

# Analyze trace
mtrace ./program trace.log

Trace Output

Raw trace.log:
= Start
@ 0x555555559260 0x64
@ 0x5555555592d0 0xc8
- 0x555555559260
- 0x5555555592d0
= End
After mtrace analysis:
Memory not freed:
-----------------
   Address     Size     Caller
0x555555559260   0x64  at /path/to/program.c:10

Analyzing how2heap Techniques

# Trace heap operations
export MALLOC_TRACE=fastbin_dup.log
./fastbin_dup

# Analyze
mtrace ./fastbin_dup fastbin_dup.log
#include <mcheck.h>
#include <stdlib.h>

int main() {
    mtrace();
    
    char *leak = malloc(100);  // Never freed!
    char *ptr = malloc(200);
    free(ptr);
    
    muntrace();
    return 0;
}
Run and analyze:
$ export MALLOC_TRACE=leak.log
$ ./program
$ mtrace ./program leak.log

Memory not freed:
-----------------
   Address     Size     Caller
0x555555559260   0x64  at program.c:6

# Shows the leaked allocation

Advantages

  • Complete trace - Records all allocations/frees
  • Post-mortem analysis - Analyze after execution
  • Leak detection - Identifies memory leaks
  • Debugging aid - Understand allocation patterns

Limitations

  • Performance impact - Significant overhead
  • Large output - Trace files can be huge
  • Requires instrumentation - Must call mtrace() in code

Additional Tracing Tools

malloc_stats()

Man page: http://manpages.ubuntu.com/malloc_stats
#include <malloc.h>

void malloc_stats(void);
Usage:
#include <malloc.h>
#include <stdlib.h>

int main() {
    char *p1 = malloc(1000);
    char *p2 = malloc(2000);
    
    malloc_stats();  // Print statistics to stderr
    
    free(p1);
    free(p2);
    return 0;
}
Output:
Arena 0:
system bytes     =     135168
in use bytes     =       3104
Total (incl. mmap):
system bytes     =     135168
in use bytes     =       3104
max mmap regions =          0
max mmap bytes   =          0

malloc_info()

Man page: http://manpages.ubuntu.com/malloc_info
#include <malloc.h>

int malloc_info(int options, FILE *stream);
Usage:
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>

int main() {
    char *p = malloc(1000);
    
    // Dump detailed XML info
    malloc_info(0, stdout);
    
    free(p);
    return 0;
}
Output: XML format with detailed heap information

memusage

Man page: http://manpages.ubuntu.com/memusage
# Trace memory usage
memusage ./program

# Generate graphical output
memusage --png=graph.png ./program

# Show detailed statistics
memusage --data=usage.dat ./program
memusagestat usage.dat graph.png
memusage is useful for understanding overall memory usage patterns and peak consumption.

Testing Techniques Against Hardening

Systematic Testing Approach

1. Baseline Test

# Verify technique works without hardening
./technique
echo $?  # Should exit 0 (success)

2. Environment Variable Testing

# Test with each hardening measure
MALLOC_CHECK_=2 ./technique
MALLOC_PERTURB_=255 ./technique
MALLOC_MMAP_THRESHOLD_=1 ./technique

# Combined testing
MALLOC_CHECK_=2 MALLOC_PERTURB_=170 ./technique

3. Programmatic Testing

Modify technique source to include:
#include <mcheck.h>
#include <malloc.h>

int main() {
    // Enable hardening
    mcheck(NULL);
    mallopt(M_MMAP_MAX, 0);
    
    // Run technique
    // ...
}

4. Results Analysis

TechniqueMALLOC_CHECK_MALLOC_PERTURB_mcheck()Still Works?
fastbin_dupPartially
tcache_poisoningPartially
unsafe_unlinkNo

Automated Testing Script

#!/bin/bash
# test_hardening.sh

TECHNIQUE=$1

if [ -z "$TECHNIQUE" ]; then
    echo "Usage: $0 <technique_binary>"
    exit 1
fi

echo "Testing $TECHNIQUE against hardening measures..."
echo

# Test 1: Baseline
echo "[+] Baseline (no hardening):"
if ./$TECHNIQUE > /dev/null 2>&1; then
    echo "    ✓ SUCCESS"
else
    echo "    ✗ FAIL"
fi

# Test 2: MALLOC_CHECK_
echo "[+] MALLOC_CHECK_=2:"
if MALLOC_CHECK_=2 ./$TECHNIQUE > /dev/null 2>&1; then
    echo "    ✓ SUCCESS (bypassed)"
else
    echo "    ✗ DETECTED"
fi

# Test 3: MALLOC_PERTURB_
echo "[+] MALLOC_PERTURB_=255:"
if MALLOC_PERTURB_=255 ./$TECHNIQUE > /dev/null 2>&1; then
    echo "    ✓ SUCCESS (bypassed)"
else
    echo "    ✗ DETECTED"
fi

# Test 4: MALLOC_MMAP_THRESHOLD_
echo "[+] MALLOC_MMAP_THRESHOLD_=1:"
if MALLOC_MMAP_THRESHOLD_=1 ./$TECHNIQUE > /dev/null 2>&1; then
    echo "    ✓ SUCCESS (adapted)"
else
    echo "    ✗ INCOMPATIBLE"
fi

# Test 5: Combined
echo "[+] All hardening combined:"
if MALLOC_CHECK_=2 MALLOC_PERTURB_=255 ./$TECHNIQUE > /dev/null 2>&1; then
    echo "    ✓ SUCCESS (fully bypassed)"
else
    echo "    ✗ DETECTED"
fi
Usage:
chmod +x test_hardening.sh
./test_hardening.sh fastbin_dup

Understanding Results

Reason: Technique manipulates metadata in a way that violates consistency checks.Options:
  1. Refine technique to maintain consistency
  2. Find alternative primitive that doesn’t trigger checks
  3. Exploit before checks are enabled
  4. Disable checks (if you control environment)
Reason: Technique exploits logic flaws rather than memory corruption, or corruption is subtle enough to bypass checks.Implication: Technique is more robust and likely to work in real-world scenarios.
Reason: Technique relies on heap allocation but target is mmap’d.Solution: Adapt technique for mmap allocations (see mmap_overlapping_chunks).

Defensive Recommendations

For Development

# Add to development environment
export MALLOC_CHECK_=1
export MALLOC_PERTURB_=$((1 + $RANDOM % 255))

For Production

Production hardening should use compiler and kernel features rather than malloc debugging features due to performance implications.
Better alternatives:
  • Compiler: -D_FORTIFY_SOURCE=2, stack canaries, PIE
  • Kernel: ASLR, DEP/NX
  • Modern allocators: tcmalloc, jemalloc (with hardening enabled)
  • Sanitizers (development only): AddressSanitizer, MemorySanitizer

Testing vs Production

FeatureTestingProduction
MALLOC_CHECK_
MALLOC_PERTURB_
mcheck()
mtrace()
AddressSanitizer
_FORTIFY_SOURCE
PIE/ASLR
Stack Canaries

Next Steps

Techniques

Test how2heap techniques against hardening

Debugging Tools

Use GDB to analyze hardening detection

Build docs developers (and LLMs) love