free
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Riepilogo dell’ordine di Free
(Nessun controllo è spiegato in questo riepilogo e alcuni casi sono stati omessi per brevità)
- If the address is null don’t do anything
- If the chunk was mmaped, munmap it and finish
- Call
_int_free: - If possible, add the chunk to the tcache
- If possible, add the chunk to the fast bin
- Call
_int_free_merge_chunkto consolidate the chunk is needed and add it to the unsorted list
Nota: A partire da glibc 2.42, il passo della tcache può anche accettare chunk fino a una soglia di dimensione molto maggiore (vedi “Recent glibc changes” below). Questo cambia quando un free lands in tcache vs. unsorted/small/large bins.
__libc_free
Free chiama __libc_free.
- Se l’indirizzo passato è Null (0), non fare nulla.
- Check pointer tag
- Se il chunk è
mmaped,munmapit e basta. - Se no, aggiungi il color e chiama
_int_freesu di esso
__lib_free code
```c void __libc_free (void *mem) { mstate ar_ptr; mchunkptr p; /* chunk corresponding to mem */if (mem == 0) /* free(0) has no effect */ return;
/* Quickly check that the freed pointer matches the tag for the memory. This gives a useful double-free detection. */ if (__glibc_unlikely (mtag_enabled)) *(volatile char *)mem;
int err = errno;
p = mem2chunk (mem);
if (chunk_is_mmapped (p)) /* release mmapped memory. / { / See if the dynamic brk/mmap threshold needs adjusting. Dumped fake mmapped chunks do not affect the threshold. */ if (!mp_.no_dyn_threshold && chunksize_nomask (p) > mp_.mmap_threshold && chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX) { mp_.mmap_threshold = chunksize (p); mp_.trim_threshold = 2 * mp_.mmap_threshold; LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2, mp_.mmap_threshold, mp_.trim_threshold); } munmap_chunk (p); } else { MAYBE_INIT_TCACHE ();
/* Mark the chunk as belonging to the library again. */ (void)tag_region (chunk2mem (p), memsize (p));
ar_ptr = arena_for_chunk (p); _int_free (ar_ptr, p, 0); }
__set_errno (err); } libc_hidden_def (__libc_free)
</details>
## _int_free <a href="#int_free" id="int_free"></a>
### _int_free start <a href="#int_free" id="int_free"></a>
Inizia con alcuni controlli per assicurarsi che:
- il **puntatore** sia **allineato**, altrimenti viene generato l'errore `free(): invalid pointer`
- la **dimensione** non sia inferiore al minimo e che la **dimensione** sia anche **allineata**, altrimenti viene generato l'errore: `free(): invalid size`
<details>
<summary>_int_free start</summary>
```c
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4493C1-L4513C28
#define aligned_OK(m) (((unsigned long) (m) &MALLOC_ALIGN_MASK) == 0)
static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
INTERNAL_SIZE_T size; /* its size */
mfastbinptr *fb; /* associated fastbin */
size = chunksize (p);
/* Little security check which won't hurt performance: the
allocator never wraps around at the end of the address space.
Therefore we can exclude some size values which might appear
here by accident or by "design" from some intruder. */
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
malloc_printerr ("free(): invalid pointer");
/* We know that each chunk is at least MINSIZE bytes in size or a
multiple of MALLOC_ALIGNMENT. */
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
malloc_printerr ("free(): invalid size");
check_inuse_chunk(av, p);
_int_free tcache
Proverà prima ad allocare questo chunk nel tcache corrispondente. Tuttavia, vengono eseguiti alcuni controlli precedenti. Scorrerà tutti i chunk del tcache nello stesso indice del chunk liberato e:
- Se ci sono più voci di
mp_.tcache_count:free(): too many chunks detected in tcache - Se l’entry non è allineata:
free(): unaligned chunk detected in tcache 2 - Se il chunk liberato era già stato liberato ed è presente come chunk nel tcache:
free(): double free detected in tcache 2
Se tutto va bene, il chunk viene aggiunto al tcache e la funzione ritorna.
_int_free tcache
```c // From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4515C1-L4554C7 #if USE_TCACHE { size_t tc_idx = csize2tidx (size); if (tcache != NULL && tc_idx < mp_.tcache_bins) { /* Check to see if it's already in the tcache. */ tcache_entry *e = (tcache_entry *) chunk2mem (p);/* This test succeeds on double free. However, we don’t 100% trust it (it also matches random payload data at a 1 in 2^<size_t> chance), so verify it’s not an unlikely coincidence before aborting. / if (_glibc_unlikely (e->key == tcache_key)) { tcache_entry *tmp; size_t cnt = 0; LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx); for (tmp = tcache->entries[tc_idx]; tmp; tmp = REVEAL_PTR (tmp->next), ++cnt) { if (cnt >= mp.tcache_count) malloc_printerr (“free(): too many chunks detected in tcache”); if (__glibc_unlikely (!aligned_OK (tmp))) malloc_printerr (“free(): unaligned chunk detected in tcache 2”); if (tmp == e) malloc_printerr (“free(): double free detected in tcache 2”); / If we get here, it was a coincidence. We’ve wasted a few cycles, but don’t abort. */ } }
if (tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (p, tc_idx); return; } } } #endif
</details>
### _int_free fast bin <a href="#int_free" id="int_free"></a>
Inizia controllando che la dimensione sia adatta per il fast bin e verifica se è possibile posizionarla vicino al top chunk.
Poi, aggiungi il chunk liberato in cima al fast bin eseguendo alcuni controlli:
- Se la dimensione del chunk è invalida (troppo grande o troppo piccola) viene generato: `free(): invalid next size (fast)`
- Se il chunk aggiunto era già il top del fast bin: `double free or corruption (fasttop)`
- Se la dimensione del chunk in cima è diversa dalla dimensione del chunk che stiamo aggiungendo: `invalid fastbin entry (free)`
<details>
<summary>_int_free Fast Bin</summary>
```c
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4556C2-L4631C4
/*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*/
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
#if TRIM_FASTBINS
/*
If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins
*/
&& (chunk_at_offset(p, size) != av->top)
#endif
) {
if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))
<= CHUNK_HDR_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{
bool fail = true;
/* We might not have a lock at this point and concurrent modifications
of system_mem might result in a false positive. Redo the test after
getting the lock. */
if (!have_lock)
{
__libc_lock_lock (av->mutex);
fail = (chunksize_nomask (chunk_at_offset (p, size)) <= CHUNK_HDR_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem);
__libc_lock_unlock (av->mutex);
}
if (fail)
malloc_printerr ("free(): invalid next size (fast)");
}
free_perturb (chunk2mem(p), size - CHUNK_HDR_SZ);
atomic_store_relaxed (&av->have_fastchunks, true);
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
/* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */
mchunkptr old = *fb, old2;
if (SINGLE_THREAD_P)
{
/* Check that the top of the bin is not the record we are going to
add (i.e., double free). */
if (__builtin_expect (old == p, 0))
malloc_printerr ("double free or corruption (fasttop)");
p->fd = PROTECT_PTR (&p->fd, old);
*fb = p;
}
else
do
{
/* Check that the top of the bin is not the record we are going to
add (i.e., double free). */
if (__builtin_expect (old == p, 0))
malloc_printerr ("double free or corruption (fasttop)");
old2 = old;
p->fd = PROTECT_PTR (&p->fd, old);
}
while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2))
!= old2);
/* Check that size of fastbin chunk at the top is the same as
size of the chunk that we are adding. We can dereference OLD
only if we have the lock, otherwise it might have already been
allocated again. */
if (have_lock && old != NULL
&& __builtin_expect (fastbin_index (chunksize (old)) != idx, 0))
malloc_printerr ("invalid fastbin entry (free)");
}
_int_free finale
Se il chunk non è ancora stato allocato in nessun bin, chiama _int_free_merge_chunk
_int_free finale
```c /* Consolidate other non-mmapped chunks as they arrive. */else if (!chunk_is_mmapped(p)) {
/* If we’re single-threaded, don’t lock the arena. */ if (SINGLE_THREAD_P) have_lock = true;
if (!have_lock) __libc_lock_lock (av->mutex);
_int_free_merge_chunk (av, p, size);
if (!have_lock) __libc_lock_unlock (av->mutex); } /* If the chunk was allocated via mmap, release via munmap(). */
else { munmap_chunk (p); } }
</details>
## _int_free_merge_chunk
Questa funzione proverà a unire il chunk P di SIZE byte con i suoi vicini. Inserisce il chunk risultante nella lista unsorted bin.
Vengono eseguiti alcuni controlli:
- Se il chunk è il top chunk: `double free or corruption (top)`
- Se il chunk successivo è al di fuori dei confini dell'arena: `double free or corruption (out)`
- Se il chunk non è marcato come in uso (nel `prev_inuse` del chunk seguente): `double free or corruption (!prev)`
- Se il chunk successivo ha una dimensione troppo piccola o troppo grande: `free(): invalid next size (normal)`
- Se il chunk precedente non è in uso, tenterà di consolidare. Ma, se il prev_size differisce dalla dimensione indicata nel chunk precedente: `corrupted size vs. prev_size while consolidating`
<details>
<summary>_int_free_merge_chunk code</summary>
```c
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4660C1-L4702C2
/* Try to merge chunk P of SIZE bytes with its neighbors. Put the
resulting chunk on the appropriate bin list. P must not be on a
bin list yet, and it can be in use. */
static void
_int_free_merge_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size)
{
mchunkptr nextchunk = chunk_at_offset(p, size);
/* Lightweight tests: check whether the block is already the
top block. */
if (__glibc_unlikely (p == av->top))
malloc_printerr ("double free or corruption (top)");
/* Or whether the next chunk is beyond the boundaries of the arena. */
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
malloc_printerr ("double free or corruption (out)");
/* Or whether the block is actually not marked used. */
if (__glibc_unlikely (!prev_inuse(nextchunk)))
malloc_printerr ("double free or corruption (!prev)");
INTERNAL_SIZE_T nextsize = chunksize(nextchunk);
if (__builtin_expect (chunksize_nomask (nextchunk) <= CHUNK_HDR_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0))
malloc_printerr ("free(): invalid next size (normal)");
free_perturb (chunk2mem(p), size - CHUNK_HDR_SZ);
/* Consolidate backward. */
if (!prev_inuse(p))
{
INTERNAL_SIZE_T prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}
/* Write the chunk header, maybe after merging with the following chunk. */
size = _int_free_create_chunk (av, p, size, nextchunk, nextsize);
_int_free_maybe_consolidate (av, size);
}
Note dell’attaccante e modifiche recenti (2023–2025)
- Safe-Linking in tcache/fastbins:
free()memorizza il puntatorefddelle liste singly-linked usando la macroPROTECT_PTR(pos, ptr) = ((size_t)pos >> 12) ^ (size_t)ptr. Questo significa che costruire un fake next pointer per tcache poisoning richiede all’attaccante di conoscere un indirizzo heap (es. leak dichunk_addr, poi usarechunk_addr >> 12come chiave XOR). Vedi maggiori dettagli e PoCs nella pagina tcache qui sotto. - Rilevamento double-free in tcache: Prima di inserire un chunk nella tcache,
free()confronta il per-entrye->keycon il per-threadtcache_keye scorre il bin fino amp_.tcache_countcontrollando duplicati, abortendo confree(): double free detected in tcache 2se ne trova. - Modifica recente di glibc (2.42): la tcache è stata estesa per accettare chunk molto più grandi, controllata dal nuovo tunable
glibc.malloc.tcache_max_bytes.free()ora tenterà di mettere in cache i chunk freed fino a quel limite in byte (i chunk mmapped non vengono messi in cache). Questo riduce la frequenza con cui i free finiscono negli unsorted/small/large bins sui sistemi moderni.
Creazione rapida di un safe-linked fd (per tcache poisoning)
# Given a leaked heap pointer to an entry located at &entry->next == POS
# compute the protected fd that points to TARGET
protected_fd = TARGET ^ (POS >> 12)
- Per una guida completa su tcache poisoning (e i suoi limiti con safe-linking), vedi:
Forzare free a colpire unsorted/small bins durante la ricerca
A volte si vuole evitare completamente tcache in un laboratorio locale per osservare il comportamento classico di _int_free (unsorted bin consolidation, ecc.). Puoi farlo con GLIBC_TUNABLES:
# Disable tcache completely
GLIBC_TUNABLES=glibc.malloc.tcache_count=0 ./vuln
# Pre-2.42: shrink the maximum cached request size to 0
GLIBC_TUNABLES=glibc.malloc.tcache_max=0 ./vuln
# 2.42+: cap the new large-cache threshold (bytes)
GLIBC_TUNABLES=glibc.malloc.tcache_max_bytes=0 ./vuln
Related reading within HackTricks:
- Comportamento First-fit/unsorted e trucchi di overlap:
- Double-free primitive e controlli moderni:
Nota sui hooks: Le classiche tecniche di overwrite
__malloc_hook/__free_hooknon sono praticabili con le glibc moderne (≥ 2.34). Se le trovi ancora in write-ups più vecchi, adattati a target alternativi (IO_FILE, exit handlers, vtables, ecc.). Per il contesto, consulta la pagina sui hooks in HackTricks.
WWW2Exec - __malloc_hook & __free_hook
Riferimenti
- GNU C Library – NEWS per 2.42 (allocator: tcache più grande tramite tcache_max_bytes, i chunk mmappati non vengono messi in cache) https://www.gnu.org/software/libc/NEWS.html#2.42
- Spiegazione e dettagli interni di Safe-Linking (Red Hat Developer, 2020) https://developers.redhat.com/articles/2020/05/13/new-security-hardening-gnu-c-library
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
HackTricks

