free
Reading time: 12 minutes
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Free Order Summary
(Δεν εξηγούνται έλεγχοι σε αυτή τη σύνοψη και κάποιες περιπτώσεις έχουν παραλειφθεί για συντομία)
- 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_chunk
to consolidate the chunk if needed and add it to the unsorted list
Note: Starting with glibc 2.42, the tcache step can also take chunks up to a much larger size threshold (see “Recent glibc changes” below). This changes when a free lands in tcache vs. unsorted/small/large bins.
__libc_free
Free
calls __libc_free
.
- Αν η διεύθυνση που δόθηκε είναι Null (0), μην κάνετε τίποτα.
- Check pointer tag
- If the chunk is
mmaped
,munmap
it and that all - If not, add the color and call
_int_free
over it
__lib_free code
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)
_int_free
_int_free start
Ξεκινά με μερικούς ελέγχους για να διασφαλίσει:
- ο pointer είναι aligned, αλλιώς προκαλεί το σφάλμα
free(): invalid pointer
- το size δεν είναι μικρότερο από το ελάχιστο και επίσης το size είναι aligned ή αλλιώς προκαλεί το σφάλμα:
free(): invalid size
_int_free start
// 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
Πρώτα θα προσπαθήσει να τοποθετήσει αυτό το chunk στο σχετικό tcache. Ωστόσο, προηγουμένως εκτελούνται κάποιοι έλεγχοι. Θα επαναλάβει όλα τα chunks του tcache στον ίδιο δείκτη με το freed chunk και:
- Αν υπάρχουν περισσότερες εγγραφές από
mp_.tcache_count
:free(): too many chunks detected in tcache
- Αν η εγγραφή δεν είναι ευθυγραμμισμένη: free():
unaligned chunk detected in tcache 2
- Αν το freed chunk είχε ήδη απελευθερωθεί και υπάρχει ως chunk στο tcache:
free(): double free detected in tcache 2
Αν όλα πάνε καλά, το chunk προστίθεται στο tcache και η συνάρτηση επιστρέφει.
_int_free tcache
// 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
_int_free fast bin
Ξεκινήστε ελέγχοντας ότι το μέγεθος είναι κατάλληλο για fast bin και αν είναι δυνατό να το τοποθετήσετε κοντά στο top chunk.
Στη συνέχεια, προσθέστε το freed chunk στην κορυφή του fast bin ενώ εκτελείτε κάποιους ελέγχους:
- Εάν το μέγεθος του chunk είναι μη έγκυρο (πολύ μεγάλο ή μικρό) προκαλείται:
free(): invalid next size (fast)
- Εάν το προστιθέμενο chunk ήταν ήδη το top του fast bin:
double free or corruption (fasttop)
- Εάν το μέγεθος του chunk στην κορυφή έχει διαφορετικό μέγεθος από το chunk που προσθέτουμε:
invalid fastbin entry (free)
_int_free Fast Bin
// 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 τελική φάση
Αν το chunk δεν είχε ακόμη κατανεμηθεί σε κανένα bin, καλέστε _int_free_merge_chunk
_int_free τελική φάση
/*
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);
}
}
_int_free_merge_chunk
Αυτή η συνάρτηση θα προσπαθήσει να συγχωνεύσει το chunk P μεγέθους SIZE bytes με τους γείτονές του. Το προκύπτον chunk τοποθετείται στη λίστα unsorted bin.
Εκτελούνται ορισμένοι έλεγχοι:
- Αν το chunk είναι το top chunk:
double free or corruption (top)
- Αν το επόμενο chunk είναι εκτός των ορίων της arena:
double free or corruption (out)
- Αν το chunk δεν είναι σημειωμένο ως χρησιμοποιημένο (στο
prev_inuse
του επόμενου chunk):double free or corruption (!prev)
- Αν το επόμενο chunk έχει πολύ μικρό ή πολύ μεγάλο μέγεθος:
free(): invalid next size (normal)
- Αν το προηγούμενο chunk δεν είναι σε χρήση, θα προσπαθήσει να συγχωνεύσει. Όμως, αν το
prev_size
διαφέρει από το μέγεθος που δηλώνει το προηγούμενο chunk:corrupted size vs. prev_size while consolidating
_int_free_merge_chunk code
// 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);
}
Σημειώσεις επιτιθέμενου και πρόσφατες αλλαγές (2023–2025)
- Safe-Linking in tcache/fastbins:
free()
stores thefd
pointer of singly-linked lists using the macroPROTECT_PTR(pos, ptr) = ((size_t)pos >> 12) ^ (size_t)ptr
. This means crafting a fake next pointer for tcache poisoning requires the attacker to know a heap address (e.g., leakchunk_addr
, then usechunk_addr >> 12
as the XOR key). See more details and PoCs in the tcache page below. - Tcache double-free detection: Before pushing a chunk into tcache,
free()
checks the per-entrye->key
against the per-threadtcache_key
and walks the bin up tomp_.tcache_count
looking for duplicates, aborting withfree(): double free detected in tcache 2
when found. - Recent glibc change (2.42): The tcache grew to accept much larger chunks, controlled by the new
glibc.malloc.tcache_max_bytes
tunable.free()
will now try to cache freed chunks up to that byte limit (mmapped chunks are not cached). This reduces how often frees fall into unsorted/small/large bins on modern systems.
Γρήγορη κατασκευή safe-linked fd (for 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)
- Για ένα πλήρες tcache poisoning walkthrough (και τα όριά του υπό safe-linking), δείτε:
Αναγκάζοντας frees να καταλήξουν σε unsorted/small bins κατά την έρευνα
Μερικές φορές θέλετε να αποφύγετε τελείως το tcache σε ένα τοπικό εργαστήριο για να παρατηρήσετε τη κλασική συμπεριφορά του _int_free
(unsorted bin consolidation, κτλ.). Μπορείτε να το κάνετε αυτό με 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
Σχετική ανάγνωση στο HackTricks:
- First-fit/unsorted behaviour and overlap tricks:
- Double-free primitives and modern checks:
Προειδοποίηση για hooks: Κλασικές τεχνικές overwrite
__malloc_hook
/__free_hook
δεν είναι εφαρμόσιμες στις σύγχρονες glibc (≥ 2.34). Αν τις βλέπετε ακόμα σε παλιότερες αναφορές, προσαρμόστε σε εναλλακτικούς στόχους (IO_FILE, exit handlers, vtables, κ.λπ.). Για υπόβαθρο, δείτε τη σελίδα για hooks στο HackTricks.
WWW2Exec - __malloc_hook & __free_hook
Αναφορές
- GNU C Library – NEWS για 2.42 (allocator: larger tcache via tcache_max_bytes, mmapped chunks are not cached) https://www.gnu.org/software/libc/NEWS.html#2.42
- Safe-Linking — επεξήγηση και εσωτερικά (Red Hat Developer, 2020) https://developers.redhat.com/articles/2020/05/13/new-security-hardening-gnu-c-library
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.