(Δεν εξηγούνται έλεγχοι σε αυτήν την περίληψη και ορισμένες περιπτώσεις έχουν παραληφθεί για συντομία)
Αν η διεύθυνση είναι null μην κάνεις τίποτα
Αν το chunk ήταν mmaped, mummap το και τελείωσε
Κάλεσε το _int_free:
Αν είναι δυνατόν, πρόσθεσε το chunk στο tcache
Αν είναι δυνατόν, πρόσθεσε το chunk στο fast bin
Κάλεσε το _int_free_merge_chunk για να ενοποιήσεις το chunk αν χρειάζεται και πρόσθεσέ το στη μη ταξινομημένη λίστα
__libc_free
Free calls __libc_free.
Αν η διεύθυνση που δόθηκε είναι Null (0) μην κάνεις τίποτα.
Έλεγξε την ετικέτα του δείκτη
Αν το chunk είναι mmaped, mummap το και αυτό είναι όλο
Αν όχι, πρόσθεσε το χρώμα και κάλεσε το _int_free πάνω του
__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));
</details>
## \_int\_free <a href="#int_free" id="int_free"></a>
### \_int\_free start <a href="#int_free" id="int_free"></a>
Ξεκινά με κάποιους ελέγχους για να διασφαλίσει ότι:
* ο **δείκτης** είναι **ευθυγραμμισμένος,** ή να προκαλέσει σφάλμα `free(): invalid pointer`
* το **μέγεθος** δεν είναι μικρότερο από το ελάχιστο και ότι το **μέγεθος** είναι επίσης **ευθυγραμμισμένο** ή να προκαλέσει σφάλμα: `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
Αρχικά θα προσπαθήσει να εκχωρήσει αυτό το κομμάτι στη σχετική tcache. Ωστόσο, γίνονται κάποιες έλεγχοι προηγουμένως. Θα επαναλάβει όλα τα κομμάτια της tcache στον ίδιο δείκτη με το ελευθερωμένο κομμάτι και:
Αν υπάρχουν περισσότερες καταχωρήσεις από mp_.tcache_count: free(): too many chunks detected in tcache
Αν η καταχώρηση δεν είναι ευθυγραμμισμένη: free(): unaligned chunk detected in tcache 2
αν το ελευθερωμένο κομμάτι είχε ήδη ελευθερωθεί και είναι παρόν ως κομμάτι στην tcache: free(): double free detected in tcache 2
Αν όλα πάνε καλά, το κομμάτι προστίθεται στην tcache και η συνάρτηση επιστρέφει.
_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. */ } }
</details>
### \_int\_free fast bin <a href="#int_free" id="int_free"></a>
Ξεκινήστε ελέγχοντας ότι το μέγεθος είναι κατάλληλο για fast bin και ελέγξτε αν είναι δυνατόν να το ρυθμίσετε κοντά στο top chunk.
Στη συνέχεια, προσθέστε το ελευθερωμένο chunk στην κορυφή του fast bin ενώ εκτελείτε κάποιους ελέγχους:
* Αν το μέγεθος του chunk είναι μη έγκυρο (πολύ μεγάλο ή πολύ μικρό) ενεργοποιήστε: `free(): invalid next size (fast)`
* Αν το προστιθέμενο chunk ήταν ήδη η κορυφή του fast bin: `double free or corruption (fasttop)`
* Αν το μέγεθος του chunk στην κορυφή έχει διαφορετικό μέγεθος από το chunk που προσθέτουμε: `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
Αν το κομμάτι δεν είχε ακόμη κατανεμηθεί σε κανένα bin, καλέστε το _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
Αυτή η συνάρτηση θα προσπαθήσει να συγχωνεύσει το κομμάτι P με μέγεθος SIZE bytes με τους γείτονές του. Το αποτέλεσμα θα τοποθετηθεί στη λίστα των αταξινόμητων κομματιών.
Γίνονται ορισμένοι έλεγχοι:
* Αν το κομμάτι είναι το κορυφαίο κομμάτι: `double free or corruption (top)`
* Αν το επόμενο κομμάτι είναι εκτός των ορίων της αρένας: `double free or corruption (out)`
* Αν το κομμάτι δεν είναι σημειωμένο ως χρησιμοποιούμενο (στο `prev_inuse` του επόμενου κομματιού): `double free or corruption (!prev)`
* Αν το επόμενο κομμάτι έχει πολύ μικρό ή πολύ μεγάλο μέγεθος: `free(): invalid next size (normal)`
* αν το προηγούμενο κομμάτι δεν είναι σε χρήση, θα προσπαθήσει να συγχωνεύσει. Αλλά, αν το prev\_size διαφέρει από το μέγεθος που υποδεικνύεται στο προηγούμενο κομμάτι: `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);
}