malloc & sysmalloc
Reading time: 36 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.
Περίληψη Σειράς Κατανομής
(Δεν εξηγούνται έλεγχοι σε αυτή την περίληψη και ορισμένες περιπτώσεις έχουν παραληφθεί για συντομία)
__libc_malloc
προσπαθεί να πάρει ένα κομμάτι από το tcache, αν όχι καλεί το_int_malloc
_int_malloc
:- Προσπαθεί να δημιουργήσει την αρένα αν δεν υπάρχει
- Αν υπάρχει κάποιο κομμάτι γρήγορης κατηγορίας του σωστού μεγέθους, το χρησιμοποιεί
- Γεμίζει το tcache με άλλα γρήγορα κομμάτια
- Αν υπάρχει κάποιο κομμάτι μικρής κατηγορίας του σωστού μεγέθους, το χρησιμοποιεί
- Γεμίζει το tcache με άλλα κομμάτια αυτού του μεγέθους
- Αν το ζητούμενο μέγεθος δεν είναι για μικρές κατηγορίες, ενοποιεί την γρήγορη κατηγορία σε ακαθόριστη κατηγορία
- Ελέγχει την ακαθόριστη κατηγορία, χρησιμοποιεί το πρώτο κομμάτι με αρκετό χώρο
- Αν το βρεθέν κομμάτι είναι μεγαλύτερο, το διαιρεί για να επιστρέψει ένα μέρος και προσθέτει το υπόλοιπο πίσω στην ακαθόριστη κατηγορία
- Αν ένα κομμάτι είναι του ίδιου μεγέθους με το ζητούμενο μέγεθος, το χρησιμοποιεί για να γεμίσει το tcache αντί να το επιστρέψει (μέχρι το tcache να είναι γεμάτο, τότε επιστρέφει το επόμενο)
- Για κάθε κομμάτι μικρότερου μεγέθους που ελέγχεται, το τοποθετεί στην αντίστοιχη μικρή ή μεγάλη κατηγορία του
- Ελέγχει τη μεγάλη κατηγορία στον δείκτη του ζητούμενου μεγέθους
- Ξεκινά να κοιτά από το πρώτο κομμάτι που είναι μεγαλύτερο από το ζητούμενο μέγεθος, αν βρεθεί κάποιο το επιστρέφει και προσθέτει τα υπόλοιπα στη μικρή κατηγορία
- Ελέγχει τις μεγάλες κατηγορίες από τους επόμενους δείκτες μέχρι το τέλος
- Από τον επόμενο μεγαλύτερο δείκτη ελέγχει για οποιοδήποτε κομμάτι, διαιρεί το πρώτο βρεθέν κομμάτι για να το χρησιμοποιήσει για το ζητούμενο μέγεθος και προσθέτει το υπόλοιπο στην ακαθόριστη κατηγορία
- Αν δεν βρεθεί τίποτα στις προηγούμενες κατηγορίες, παίρνει ένα κομμάτι από το κορυφαίο κομμάτι
- Αν το κορυφαίο κομμάτι δεν ήταν αρκετά μεγάλο, το επεκτείνει με
sysmalloc
__libc_malloc
Η συνάρτηση malloc
στην πραγματικότητα καλεί την __libc_malloc
. Αυτή η συνάρτηση θα ελέγξει το tcache για να δει αν υπάρχει διαθέσιμο κομμάτι του επιθυμητού μεγέθους. Αν υπάρχει, θα το χρησιμοποιήσει και αν όχι θα ελέγξει αν είναι μονός νήμα και σε αυτή την περίπτωση θα καλέσει το _int_malloc
στην κύρια αρένα, και αν όχι θα καλέσει το _int_malloc
στην αρένα του νήματος.
__libc_malloc κώδικας
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c
#if IS_IN (libc)
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
_Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
"PTRDIFF_MAX is not more than half of SIZE_MAX");
if (!__malloc_initialized)
ptmalloc_init ();
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes = checked_request2size (bytes);
if (tbytes == 0)
{
__set_errno (ENOMEM);
return NULL;
}
size_t tc_idx = csize2tidx (tbytes);
MAYBE_INIT_TCACHE ();
DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
&& tcache != NULL
&& tcache->counts[tc_idx] > 0)
{
victim = tcache_get (tc_idx);
return tag_new_usable (victim);
}
DIAG_POP_NEEDS_COMMENT;
#endif
if (SINGLE_THREAD_P)
{
victim = tag_new_usable (_int_malloc (&main_arena, bytes));
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
&main_arena == arena_for_chunk (mem2chunk (victim)));
return victim;
}
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
/* Retry with another arena only if we were able to find a usable arena
before. */
if (!victim && ar_ptr != NULL)
{
LIBC_PROBE (memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
victim = tag_new_usable (victim);
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
return victim;
}
Σημειώστε πώς θα επισημαίνει πάντα τον επιστρεφόμενο δείκτη με tag_new_usable
, από τον κώδικα:
void *tag_new_usable (void *ptr)
Allocate a new random color and use it to color the user region of
a chunk; this may include data from the subsequent chunk's header
if tagging is sufficiently fine grained. Returns PTR suitably
recolored for accessing the memory there.
_int_malloc
Αυτή είναι η συνάρτηση που εκχωρεί μνήμη χρησιμοποιώντας τα άλλα bins και το top chunk.
- Έναρξη
Αρχίζει ορίζοντας μερικές μεταβλητές και αποκτώντας το πραγματικό μέγεθος που χρειάζεται να έχει ο ζητούμενος χώρος μνήμης:
_int_malloc start
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3847
static void *
_int_malloc (mstate av, size_t bytes)
{
INTERNAL_SIZE_T nb; /* normalized request size */
unsigned int idx; /* associated bin index */
mbinptr bin; /* associated bin */
mchunkptr victim; /* inspected/selected chunk */
INTERNAL_SIZE_T size; /* its size */
int victim_index; /* its bin index */
mchunkptr remainder; /* remainder from a split */
unsigned long remainder_size; /* its size */
unsigned int block; /* bit map traverser */
unsigned int bit; /* bit map traverser */
unsigned int map; /* current word of binmap */
mchunkptr fwd; /* misc temp for linking */
mchunkptr bck; /* misc temp for linking */
#if USE_TCACHE
size_t tcache_unsorted_count; /* count of unsorted chunks processed */
#endif
/*
Convert request size to internal form by adding SIZE_SZ bytes
overhead plus possibly more to obtain necessary alignment and/or
to obtain a size of at least MINSIZE, the smallest allocatable
size. Also, checked_request2size returns false for request sizes
that are so large that they wrap around zero when padded and
aligned.
*/
nb = checked_request2size (bytes);
if (nb == 0)
{
__set_errno (ENOMEM);
return NULL;
}
Arena
Σε περίπτωση που δεν υπάρχουν χρησιμοποιήσιμες αρένες, χρησιμοποιεί sysmalloc
για να αποκτήσει ένα κομμάτι από mmap
:
_int_malloc not arena
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3885C3-L3893C6
/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
if (__glibc_unlikely (av == NULL))
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
Fast Bin
Αν το απαιτούμενο μέγεθος είναι μέσα στα μεγέθη των Fast Bins, προσπαθήστε να χρησιμοποιήσετε ένα chunk από το fast bin. Βασικά, με βάση το μέγεθος, θα βρει τον δείκτη του fast bin όπου θα πρέπει να βρίσκονται τα έγκυρα chunks, και αν υπάρχουν, θα επιστρέψει ένα από αυτά.
Επιπλέον, αν το tcache είναι ενεργοποιημένο, θα γεμίσει το tcache bin αυτού του μεγέθους με fast bins.
Κατά την εκτέλεση αυτών των ενεργειών, εκτελούνται μερικοί έλεγχοι ασφαλείας εδώ:
- Αν το chunk είναι μη ευθυγραμμισμένο:
malloc(): unaligned fastbin chunk detected 2
- Αν το επόμενο chunk είναι μη ευθυγραμμισμένο:
malloc(): unaligned fastbin chunk detected
- Αν το επιστρεφόμενο chunk έχει μέγεθος που δεν είναι σωστό λόγω του δείκτη του στο fast bin:
malloc(): memory corruption (fast)
- Αν οποιοδήποτε chunk χρησιμοποιείται για να γεμίσει το tcache είναι μη ευθυγραμμισμένο:
malloc(): unaligned fastbin chunk detected 3
_int_malloc fast bin
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3895C3-L3967C6
/*
If the size qualifies as a fastbin, first check corresponding bin.
This code is safe to execute even if av is not yet initialized, so we
can try it without checking, which saves some time on this fast path.
*/
#define REMOVE_FB(fb, victim, pp) \
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
pp = REVEAL_PTR (victim->fd); \
if (__glibc_unlikely (pp != NULL && misaligned_chunk (pp))) \
malloc_printerr ("malloc(): unaligned fastbin chunk detected"); \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, pp, victim)) \
!= victim); \
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp;
victim = *fb;
if (victim != NULL)
{
if (__glibc_unlikely (misaligned_chunk (victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 2");
if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (victim->fd);
else
REMOVE_FB (fb, pp, victim);
if (__glibc_likely (victim != NULL))
{
size_t victim_idx = fastbin_index (chunksize (victim));
if (__builtin_expect (victim_idx != idx, 0))
malloc_printerr ("malloc(): memory corruption (fast)");
check_remalloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (tc_victim->fd);
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx);
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}
Small Bin
Όπως αναφέρεται σε ένα σχόλιο, οι μικρές δεξαμενές κρατούν ένα μέγεθος ανά δείκτη, επομένως ο έλεγχος αν υπάρχει διαθέσιμο έγκυρο κομμάτι είναι πολύ γρήγορος, οπότε μετά τους γρήγορους δείκτες, ελέγχονται οι μικρές δεξαμενές.
Ο πρώτος έλεγχος είναι να διαπιστωθεί αν το ζητούμενο μέγεθος θα μπορούσε να είναι μέσα σε μια μικρή δεξαμενή. Σε αυτή την περίπτωση, αποκτάτε τον αντίστοιχο δείκτη μέσα στη μικρή δεξαμενή και βλέπετε αν υπάρχει οποιοδήποτε διαθέσιμο κομμάτι.
Στη συνέχεια, εκτελείται ένας έλεγχος ασφαλείας ελέγχοντας:
- αν
victim->bk->fd = victim
. Για να δείτε ότι και τα δύο κομμάτια είναι σωστά συνδεδεμένα.
Σε αυτή την περίπτωση, το κομμάτι λαμβάνει το bit inuse
, η διπλή συνδεδεμένη λίστα διορθώνεται ώστε αυτό το κομμάτι να εξαφανιστεί από αυτήν (καθώς πρόκειται να χρησιμοποιηθεί), και το bit της μη κύριας αρένας ρυθμίζεται αν χρειάζεται.
Τέλος, γεμίστε τον δείκτη tcache του ζητούμενου μεγέθους με άλλα κομμάτια μέσα στη μικρή δεξαμενή (αν υπάρχουν).
_int_malloc small bin
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L3895C3-L3967C6
/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
malloc_consolidate
Αν δεν ήταν ένα μικρό κομμάτι, είναι ένα μεγάλο κομμάτι, και σε αυτή την περίπτωση καλείται malloc_consolidate
για να αποφευχθεί η κατακερματισμένη μνήμη.
κλήση malloc_consolidate
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else
{
idx = largebin_index (nb);
if (atomic_load_relaxed (&av->have_fastchunks))
malloc_consolidate (av);
}
Η συνάρτηση malloc consolidate βασικά αφαιρεί κομμάτια από το γρήγορο bin και τα τοποθετεί στο αταξινόμητο bin. Μετά την επόμενη malloc, αυτά τα κομμάτια θα οργανωθούν στα αντίστοιχα μικρά/γρήγορα bins.
Σημειώστε ότι αν κατά την αφαίρεση αυτών των κομματιών, βρεθούν με προηγούμενα ή επόμενα κομμάτια που δεν χρησιμοποιούνται, θα αποσυνδεθούν και θα συγχωνευθούν πριν τοποθετηθεί το τελικό κομμάτι στο αταξινόμητο bin.
Για κάθε κομμάτι γρήγορου bin εκτελούνται μερικοί έλεγχοι ασφαλείας:
- Αν το κομμάτι είναι μη ευθυγραμμισμένο, ενεργοποιείται:
malloc_consolidate(): unaligned fastbin chunk detected
- Αν το κομμάτι έχει διαφορετικό μέγεθος από αυτό που θα έπρεπε λόγω του δείκτη στον οποίο βρίσκεται:
malloc_consolidate(): invalid chunk size
- Αν το προηγούμενο κομμάτι δεν χρησιμοποιείται και το προηγούμενο κομμάτι έχει μέγεθος διαφορετικό από αυτό που υποδεικνύει το
prev_chunk
:corrupted size vs. prev_size in fastbins
malloc_consolidate function
// https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L4810C1-L4905C2
static void malloc_consolidate(mstate av)
{
mfastbinptr* fb; /* current fastbin being consolidated */
mfastbinptr* maxfb; /* last fastbin (for loop control) */
mchunkptr p; /* current chunk being consolidated */
mchunkptr nextp; /* next chunk to consolidate */
mchunkptr unsorted_bin; /* bin header */
mchunkptr first_unsorted; /* chunk to link to */
/* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int nextinuse;
atomic_store_relaxed (&av->have_fastchunks, false);
unsorted_bin = unsorted_chunks(av);
/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
*/
maxfb = &fastbin (av, NFASTBINS - 1);
fb = &fastbin (av, 0);
do {
p = atomic_exchange_acquire (fb, NULL);
if (p != 0) {
do {
{
if (__glibc_unlikely (misaligned_chunk (p)))
malloc_printerr ("malloc_consolidate(): "
"unaligned fastbin chunk detected");
unsigned int idx = fastbin_index (chunksize (p));
if ((&fastbin (av, idx)) != fb)
malloc_printerr ("malloc_consolidate(): invalid chunk size");
}
check_inuse_chunk(av, p);
nextp = REVEAL_PTR (p->fd);
/* Slightly streamlined version of consolidation code in free() */
size = chunksize (p);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
if (!prev_inuse(p)) {
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 in fastbins");
unlink_chunk (av, p);
}
if (nextchunk != av->top) {
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
if (!nextinuse) {
size += nextsize;
unlink_chunk (av, nextchunk);
} else
clear_inuse_bit_at_offset(nextchunk, 0);
first_unsorted = unsorted_bin->fd;
unsorted_bin->fd = p;
first_unsorted->bk = p;
if (!in_smallbin_range (size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
} while ( (p = nextp) != 0);
}
} while (fb++ != maxfb);
}
Αταξινόμητος κάδος
Ήρθε η ώρα να ελέγξουμε τον αταξινόμητο κάδο για μια πιθανή έγκυρη κομμάτι προς χρήση.
Έναρξη
Αυτό ξεκινά με έναν μεγάλο βρόχο for που θα διασχίζει τον αταξινόμητο κάδο στην κατεύθυνση bk
μέχρι να φτάσει στο τέλος (τη δομή arena) με while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
Επιπλέον, γίνονται κάποιες ελέγχοι ασφαλείας κάθε φορά που εξετάζεται ένα νέο κομμάτι:
- Αν το μέγεθος του κομματιού είναι περίεργο (πολύ μικρό ή πολύ μεγάλο):
malloc(): invalid size (unsorted)
- Αν το μέγεθος του επόμενου κομματιού είναι περίεργο (πολύ μικρό ή πολύ μεγάλο):
malloc(): invalid next size (unsorted)
- Αν το προηγούμενο μέγεθος που υποδεικνύεται από το επόμενο κομμάτι διαφέρει από το μέγεθος του κομματιού:
malloc(): mismatching next->prev_size (unsorted)
- Αν όχι
victim->bck->fd == victim
ή όχιvictim->fd == av
(arena):malloc(): unsorted double linked list corrupted
- Καθώς πάντα ελέγχουμε το τελευταίο, το
fd
του θα πρέπει πάντα να δείχνει στη δομή arena. - Αν το επόμενο κομμάτι δεν υποδεικνύει ότι το προηγούμενο είναι σε χρήση:
malloc(): invalid next->prev_inuse (unsorted)
_int_malloc
αταξινόμητος κάδος έναρξη
/*
Process recently freed or remaindered chunks, taking one only if
it is exact fit, or, if this a small request, the chunk is remainder from
the most recent non-exact fit. Place other traversed chunks in
bins. Note that this step is the only place in any routine where
chunks are placed in bins.
The outer loop here is needed because we might not realize until
near the end of malloc that we should have consolidated, so must
do so and retry. This happens at most once, and only when we would
otherwise need to expand memory to service a "small" request.
*/
#if USE_TCACHE
INTERNAL_SIZE_T tcache_nb = 0;
size_t tc_idx = csize2tidx (nb);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
tcache_nb = nb;
int return_cached = 0;
tcache_unsorted_count = 0;
#endif
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);
if (__glibc_unlikely (size <= CHUNK_HDR_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");
αν in_smallbin_range
Αν το κομμάτι είναι μεγαλύτερο από το ζητούμενο μέγεθος, χρησιμοποίησέ το και τοποθέτησε το υπόλοιπο του κομματιού στη μη ταξινομημένη λίστα και ενημέρωσε το last_remainder
με αυτό.
_int_malloc
μη ταξινομημένη δεξαμενή in_smallbin_range
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4090C11-L4124C14
/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
Αν αυτό ήταν επιτυχές, επιστρέψτε το κομμάτι και τελειώστε, αν όχι, συνεχίστε την εκτέλεση της συνάρτησης...
αν ίσες διαστάσεις
Συνεχίστε να αφαιρείτε το κομμάτι από το bin, σε περίπτωση που το ζητούμενο μέγεθος είναι ακριβώς το ίδιο με αυτό του κομματιού:
- Αν το tcache δεν είναι γεμάτο, προσθέστε το στο tcache και συνεχίστε υποδεικνύοντας ότι υπάρχει ένα κομμάτι tcache που θα μπορούσε να χρησιμοποιηθεί
- Αν το tcache είναι γεμάτο, απλώς χρησιμοποιήστε το επιστρέφοντάς το
_int_malloc
unsorted bin equal size
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4126C11-L4157C14
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* Take now instead of binning if exact fit */
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
#if USE_TCACHE
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb > 0
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
#if USE_TCACHE
}
#endif
}
Αν το chunk δεν επιστραφεί ή δεν προστεθεί στο tcache, συνεχίστε με τον κώδικα...
τοποθέτηση chunk σε ένα bin
Αποθηκεύστε το ελεγμένο chunk στο μικρό bin ή στο μεγάλο bin ανάλογα με το μέγεθος του chunk (διατηρώντας το μεγάλο bin σωστά οργανωμένο).
Γίνονται έλεγχοι ασφαλείας για να διασφαλιστεί ότι και οι δύο διπλές συνδέσεις της λίστας του μεγάλου bin είναι κατεστραμμένες:
- Αν
fwd->bk_nextsize->fd_nextsize != fwd
:malloc(): largebin double linked list corrupted (nextsize)
- Αν
fwd->bk->fd != fwd
:malloc(): largebin double linked list corrupted (bk)
_int_malloc
τοποθέτηση chunk σε ένα bin
/* place chunk in bin */
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
_int_malloc
όρια
Σε αυτό το σημείο, αν κάποιο κομμάτι αποθηκεύτηκε στο tcache που μπορεί να χρησιμοποιηθεί και το όριο έχει επιτευχθεί, απλά επιστρέφει ένα κομμάτι από το tcache.
Επιπλέον, αν MAX_ITERS έχει επιτευχθεί, σπάστε τον βρόχο και αποκτήστε ένα κομμάτι με διαφορετικό τρόπο (top chunk).
Αν το return_cached
έχει οριστεί, απλά επιστρέψτε ένα κομμάτι από το tcache για να αποφύγετε μεγαλύτερες αναζητήσεις.
_int_malloc
όρια
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4227C1-L4250C7
#if USE_TCACHE
/* If we've processed as many chunks as we're allowed while
filling the cache, return one of the cached ones. */
++tcache_unsorted_count;
if (return_cached
&& mp_.tcache_unsorted_limit > 0
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
{
return tcache_get (tc_idx);
}
#endif
#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}
#if USE_TCACHE
/* If all the small chunks we found ended up cached, return one now. */
if (return_cached)
{
return tcache_get (tc_idx);
}
#endif
Αν δεν έχουν φτάσει τα όρια, συνεχίστε με τον κώδικα...
Μεγάλο Κάδος (κατά δείκτη)
Αν το αίτημα είναι μεγάλο (όχι σε μικρό κάδο) και δεν έχουμε επιστρέψει ακόμα κανένα κομμάτι, πάρτε τον δείκτη του ζητούμενου μεγέθους στον μεγάλο κάδο, ελέγξτε αν δεν είναι κενός ή αν το μεγαλύτερο κομμάτι σε αυτόν τον κάδο είναι μεγαλύτερο από το ζητούμενο μέγεθος και σε αυτή την περίπτωση βρείτε το μικρότερο κομμάτι που μπορεί να χρησιμοποιηθεί για το ζητούμενο μέγεθος.
Αν ο υπόλοιπος χώρος από το τελικά χρησιμοποιούμενο κομμάτι μπορεί να είναι ένα νέο κομμάτι, προσθέστε το στον αταξινόμητο κάδο και η lsast_reminder ενημερώνεται.
Ένας έλεγχος ασφαλείας γίνεται κατά την προσθήκη του υπολοίπου στον αταξινόμητο κάδο:
bck->fd-> bk != bck
:malloc(): corrupted unsorted chunks
_int_malloc
Μεγάλος κάδος (κατά δείκτη)
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4252C7-L4317C10
/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin)
&& chunksize_nomask (victim)
== chunksize_nomask (victim->fd))
victim = victim->fd;
remainder_size = size - nb;
unlink_chunk (av, victim);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks");
last_re->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
Αν δεν βρεθεί κατάλληλο chunk για αυτό, συνεχίστε
Large Bin (επόμενο μεγαλύτερο)
Αν στο ακριβές large bin δεν υπήρχε κανένα chunk που να μπορεί να χρησιμοποιηθεί, αρχίστε να επαναλαμβάνετε σε όλα τα επόμενα large bin (ξεκινώντας από το αμέσως μεγαλύτερο) μέχρι να βρεθεί ένα (αν υπάρχει).
Η υπενθύμιση του διαχωρισμένου chunk προστίθεται στο unsorted bin, το last_reminder ενημερώνεται και η ίδια έλεγχος ασφαλείας εκτελείται:
bck->fd-> bk != bck
:malloc(): corrupted unsorted chunks2
_int_malloc
Large bin (επόμενο μεγαλύτερο)
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c#L4319C7-L4425C10
/*
Search for a chunk by scanning bins, starting with next largest
bin. This search is strictly by best-fit; i.e., the smallest
(with ties going to approximately the least recently used) chunk
that fits is selected.
The bitmap avoids needing to check that most blocks are nonempty.
The particular case of skipping all bins during warm-up phases
when no chunks have been returned yet is faster than it might look.
*/
++idx;
bin = bin_at (av, idx);
block = idx2block (idx);
map = av->binmap[block];
bit = idx2bit (idx);
for (;; )
{
/* Skip rest of block if there are no more set bits in this block. */
if (bit > map || bit == 0)
{
do
{
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
}
while ((map = av->binmap[block]) == 0);
bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}
/* Advance to bin with set bit. There must be one. */
while ((bit & map) == 0)
{
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}
/* Inspect the bin. It is likely to be non-empty */
victim = last (bin);
/* If a false alarm (empty bin), clear the bit. */
if (victim == bin)
{
av->binmap[block] = map &= ~bit; /* Write through */
bin = next_bin (bin);
bit <<= 1;
}
else
{
size = chunksize (victim);
/* We know the first chunk in this bin is big enough to use. */
assert ((unsigned long) (size) >= (unsigned long) (nb));
remainder_size = size - nb;
/* unlink */
unlink_chunk (av, victim);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks 2");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* advertise as last remainder */
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
Κορυφαίο Τμήμα
Σε αυτό το σημείο, είναι ώρα να αποκτήσουμε ένα νέο τμήμα από το Κορυφαίο τμήμα (αν είναι αρκετά μεγάλο).
Ξεκινά με έναν έλεγχο ασφαλείας για να διασφαλίσει ότι το μέγεθος του τμήματος δεν είναι πολύ μεγάλο (κατεστραμμένο):
chunksize(av->top) > av->system_mem
:malloc(): corrupted top size
Στη συνέχεια, θα χρησιμοποιήσει τον χώρο του κορυφαίου τμήματος αν είναι αρκετά μεγάλος για να δημιουργήσει ένα τμήμα του ζητούμενου μεγέθους.
Αν όχι, αν υπάρχουν γρήγορα τμήματα, τα ενοποιεί και προσπαθεί ξανά.
Τέλος, αν δεν υπάρχει αρκετός χώρος, χρησιμοποιεί sysmalloc
για να εκχωρήσει αρκετό μέγεθος.
_int_malloc
Κορυφαίο τμήμα
use_top:
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).
We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/
victim = av->top;
size = chunksize (victim);
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (atomic_load_relaxed (&av->have_fastchunks))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}
/*
Otherwise, relay to handle system-dependent cases
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
}
sysmalloc
sysmalloc αρχή
Αν η αρένα είναι null ή το ζητούμενο μέγεθος είναι πολύ μεγάλο (και υπάρχουν mmaps που επιτρέπονται) χρησιμοποίησε sysmalloc_mmap
για να δεσμεύσεις χώρο και να τον επιστρέψεις.
sysmalloc αρχή
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2531
/*
sysmalloc handles malloc cases requiring more memory from the system.
On entry, it is assumed that av->top does not have enough
space to service request for nb bytes, thus requiring that av->top
be extended or replaced.
*/
static void *
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
mchunkptr old_top; /* incoming value of av->top */
INTERNAL_SIZE_T old_size; /* its size */
char *old_end; /* its end address */
long size; /* arg to first MORECORE or mmap call */
char *brk; /* return value from MORECORE */
long correction; /* arg to 2nd MORECORE call */
char *snd_brk; /* 2nd return val */
INTERNAL_SIZE_T front_misalign; /* unusable bytes at front of new space */
INTERNAL_SIZE_T end_misalign; /* partial page left at end of new space */
char *aligned_brk; /* aligned offset into brk */
mchunkptr p; /* the allocated/returned chunk */
mchunkptr remainder; /* remainder from allocation */
unsigned long remainder_size; /* its size */
size_t pagesize = GLRO (dl_pagesize);
bool tried_mmap = false;
/*
If have mmap, and the request size meets the mmap threshold, and
the system supports mmap, and there are few enough currently
allocated mmapped regions, try to directly map this request
rather than expanding top.
*/
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
char *mm;
if (mp_.hp_pagesize > 0 && nb >= mp_.hp_pagesize)
{
/* There is no need to issue the THP madvise call if Huge Pages are
used directly. */
mm = sysmalloc_mmap (nb, mp_.hp_pagesize, mp_.hp_flags, av);
if (mm != MAP_FAILED)
return mm;
}
mm = sysmalloc_mmap (nb, pagesize, 0, av);
if (mm != MAP_FAILED)
return mm;
tried_mmap = true;
}
/* There are no usable arenas and mmap also failed. */
if (av == NULL)
return 0;
sysmalloc checks
Ξεκινάει με την απόκτηση πληροφοριών για το παλιό top chunk και ελέγχει ότι κάποιες από τις παρακάτω συνθήκες είναι αληθείς:
- Το μέγεθος της παλιάς heap είναι 0 (νέα heap)
- Το μέγεθος της προηγούμενης heap είναι μεγαλύτερο από MINSIZE και το παλιό Top είναι σε χρήση
- Η heap είναι ευθυγραμμισμένη με το μέγεθος της σελίδας (0x1000, οπότε τα κατώτερα 12 bits πρέπει να είναι 0)
Στη συνέχεια, ελέγχει επίσης ότι:
- Το παλιό μέγεθος δεν έχει αρκετό χώρο για να δημιουργήσει ένα chunk για το ζητούμενο μέγεθος
sysmalloc checks
/* Record incoming configuration of top */
old_top = av->top;
old_size = chunksize (old_top);
old_end = (char *) (chunk_at_offset (old_top, old_size));
brk = snd_brk = (char *) (MORECORE_FAILURE);
/*
If not the first time through, we require old_size to be
at least MINSIZE and to have prev_inuse set.
*/
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
sysmalloc όχι κύρια αρένα
Θα προσπαθήσει πρώτα να επεκτείνει την προηγούμενη αρένα για αυτή την αρένα. Αν δεν είναι δυνατή, θα προσπαθήσει να κατανείμει μια νέα αρένα και να ενημερώσει τους δείκτες για να μπορέσει να τη χρησιμοποιήσει.
Τέλος, αν αυτό δεν λειτουργήσει, θα προσπαθήσει να καλέσει sysmalloc_mmap
.
sysmalloc όχι κύρια αρένα
if (av != &main_arena)
{
heap_info *old_heap, *heap;
size_t old_heap_size;
/* First try to extend the current heap. */
old_heap = heap_for_ptr (old_top);
old_heap_size = old_heap->size;
if ((long) (MINSIZE + nb - old_size) > 0
&& grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
{
av->system_mem += old_heap->size - old_heap_size;
set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
| PREV_INUSE);
}
else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
{
/* Use a newly allocated heap. */
heap->ar_ptr = av;
heap->prev = old_heap;
av->system_mem += heap->size;
/* Set up the new top. */
top (av) = chunk_at_offset (heap, sizeof (*heap));
set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);
/* Setup fencepost and free the old top chunk with a multiple of
MALLOC_ALIGNMENT in size. */
/* The fencepost takes at least MINSIZE bytes, because it might
become the top chunk again later. Note that a footer is set
up, too, although the chunk is marked in use. */
old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
set_head (chunk_at_offset (old_top, old_size + CHUNK_HDR_SZ),
0 | PREV_INUSE);
if (old_size >= MINSIZE)
{
set_head (chunk_at_offset (old_top, old_size),
CHUNK_HDR_SZ | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), CHUNK_HDR_SZ);
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
_int_free (av, old_top, 1);
}
else
{
set_head (old_top, (old_size + CHUNK_HDR_SZ) | PREV_INUSE);
set_foot (old_top, (old_size + CHUNK_HDR_SZ));
}
}
else if (!tried_mmap)
{
/* We can at least try to use to mmap memory. If new_heap fails
it is unlikely that trying to allocate huge pages will
succeed. */
char *mm = sysmalloc_mmap (nb, pagesize, 0, av);
if (mm != MAP_FAILED)
return mm;
}
}
sysmalloc κύρια αρένα
Αρχίζει να υπολογίζει την ποσότητα μνήμης που χρειάζεται. Θα ξεκινήσει ζητώντας συνεχόμενη μνήμη, οπότε σε αυτή την περίπτωση θα είναι δυνατή η χρήση της παλιάς μνήμης που δεν χρησιμοποιείται. Επίσης, εκτελούνται κάποιες λειτουργίες ευθυγράμμισης.
sysmalloc κύρια αρένα
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2665C1-L2713C10
else /* av == main_arena */
{ /* Request enough space for nb + pad + overhead */
size = nb + mp_.top_pad + MINSIZE;
/*
If contiguous, we can subtract out existing space that we hope to
combine with new space. We add it back later only if
we don't actually get contiguous space.
*/
if (contiguous (av))
size -= old_size;
/*
Round to a multiple of page size or huge page size.
If MORECORE is not contiguous, this ensures that we only call it
with whole-page arguments. And if MORECORE is contiguous and
this is not first time through, this preserves page-alignment of
previous calls. Otherwise, we correct to page-align below.
*/
#ifdef MADV_HUGEPAGE
/* Defined in brk.c. */
extern void *__curbrk;
if (__glibc_unlikely (mp_.thp_pagesize != 0))
{
uintptr_t top = ALIGN_UP ((uintptr_t) __curbrk + size,
mp_.thp_pagesize);
size = top - (uintptr_t) __curbrk;
}
else
#endif
size = ALIGN_UP (size, GLRO(dl_pagesize));
/*
Don't try to call MORECORE if argument is so big as to appear
negative. Note that since mmap takes size_t arg, it may succeed
below even if we cannot call MORECORE.
*/
if (size > 0)
{
brk = (char *) (MORECORE (size));
if (brk != (char *) (MORECORE_FAILURE))
madvise_thp (brk, size);
LIBC_PROBE (memory_sbrk_more, 2, brk, size);
}
sysmalloc κύρια αρένα προηγούμενο σφάλμα 1
Αν η προηγούμενη επιστράφηκε MORECORE_FAILURE
, δοκιμάστε ξανά να εκχωρήσετε μνήμη χρησιμοποιώντας sysmalloc_mmap_fallback
sysmalloc
κύρια αρένα προηγούμενο σφάλμα 1
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2715C7-L2740C10
if (brk == (char *) (MORECORE_FAILURE))
{
/*
If have mmap, try using it as a backup when MORECORE fails or
cannot be used. This is worth doing on systems that have "holes" in
address space, so sbrk cannot extend to give contiguous space, but
space is available elsewhere. Note that we ignore mmap max count
and threshold limits, since the space will not be used as a
segregated mmap region.
*/
char *mbrk = MAP_FAILED;
if (mp_.hp_pagesize > 0)
mbrk = sysmalloc_mmap_fallback (&size, nb, old_size,
mp_.hp_pagesize, mp_.hp_pagesize,
mp_.hp_flags, av);
if (mbrk == MAP_FAILED)
mbrk = sysmalloc_mmap_fallback (&size, nb, old_size, MMAP_AS_MORECORE_SIZE,
pagesize, 0, av);
if (mbrk != MAP_FAILED)
{
/* We do not need, and cannot use, another sbrk call to find end */
brk = mbrk;
snd_brk = brk + size;
}
}
sysmalloc κύρια αρένα συνέχεια
Αν το προηγούμενο δεν επέστρεψε MORECORE_FAILURE
, αν λειτούργησε, δημιουργήστε κάποιες ευθυγραμμίσεις:
sysmalloc κύρια αρένα προηγούμενο σφάλμα 2
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2742
if (brk != (char *) (MORECORE_FAILURE))
{
if (mp_.sbrk_base == 0)
mp_.sbrk_base = brk;
av->system_mem += size;
/*
If MORECORE extends previous space, we can likewise extend top size.
*/
if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE))
set_head (old_top, (size + old_size) | PREV_INUSE);
else if (contiguous (av) && old_size && brk < old_end)
/* Oops! Someone else killed our space.. Can't touch anything. */
malloc_printerr ("break adjusted to free malloc space");
/*
Otherwise, make adjustments:
* If the first time through or noncontiguous, we need to call sbrk
just to find out where the end of memory lies.
* We need to ensure that all returned chunks from malloc will meet
MALLOC_ALIGNMENT
* If there was an intervening foreign sbrk, we need to adjust sbrk
request size to account for fact that we will not be able to
combine new space with existing space in old_top.
* Almost all systems internally allocate whole pages at a time, in
which case we might as well use the whole last page of request.
So we allocate enough more memory to hit a page boundary now,
which in turn causes future contiguous calls to page-align.
*/
else
{
front_misalign = 0;
end_misalign = 0;
correction = 0;
aligned_brk = brk;
/* handle contiguous cases */
if (contiguous (av))
{
/* Count foreign sbrk as system_mem. */
if (old_size)
av->system_mem += brk - old_end;
/* Guarantee alignment of first new chunk made from this space */
front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
if (front_misalign > 0)
{
/*
Skip over some bytes to arrive at an aligned position.
We don't need to specially mark these wasted front bytes.
They will never be accessed anyway because
prev_inuse of av->top (and any chunk created from its start)
is always true after initialization.
*/
correction = MALLOC_ALIGNMENT - front_misalign;
aligned_brk += correction;
}
/*
If this isn't adjacent to existing space, then we will not
be able to merge with old_top space, so must add to 2nd request.
*/
correction += old_size;
/* Extend the end address to hit a page boundary */
end_misalign = (INTERNAL_SIZE_T) (brk + size + correction);
correction += (ALIGN_UP (end_misalign, pagesize)) - end_misalign;
assert (correction >= 0);
snd_brk = (char *) (MORECORE (correction));
/*
If can't allocate correction, try to at least find out current
brk. It might be enough to proceed without failing.
Note that if second sbrk did NOT fail, we assume that space
is contiguous with first sbrk. This is a safe assumption unless
program is multithreaded but doesn't use locks and a foreign sbrk
occurred between our first and second calls.
*/
if (snd_brk == (char *) (MORECORE_FAILURE))
{
correction = 0;
snd_brk = (char *) (MORECORE (0));
}
else
madvise_thp (snd_brk, correction);
}
/* handle non-contiguous cases */
else
{
if (MALLOC_ALIGNMENT == CHUNK_HDR_SZ)
/* MORECORE/mmap must correctly align */
assert (((unsigned long) chunk2mem (brk) & MALLOC_ALIGN_MASK) == 0);
else
{
front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
if (front_misalign > 0)
{
/*
Skip over some bytes to arrive at an aligned position.
We don't need to specially mark these wasted front bytes.
They will never be accessed anyway because
prev_inuse of av->top (and any chunk created from its start)
is always true after initialization.
*/
aligned_brk += MALLOC_ALIGNMENT - front_misalign;
}
}
/* Find out current end of memory */
if (snd_brk == (char *) (MORECORE_FAILURE))
{
snd_brk = (char *) (MORECORE (0));
}
}
/* Adjust top based on results of second sbrk */
if (snd_brk != (char *) (MORECORE_FAILURE))
{
av->top = (mchunkptr) aligned_brk;
set_head (av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE);
av->system_mem += correction;
/*
If not the first time through, we either have a
gap due to foreign sbrk or a non-contiguous region. Insert a
double fencepost at old_top to prevent consolidation with space
we don't own. These fenceposts are artificial chunks that are
marked as inuse and are in any case too small to use. We need
two to make sizes and alignments work out.
*/
if (old_size != 0)
{
/*
Shrink old_top to insert fenceposts, keeping size a
multiple of MALLOC_ALIGNMENT. We know there is at least
enough space in old_top to do this.
*/
old_size = (old_size - 2 * CHUNK_HDR_SZ) & ~MALLOC_ALIGN_MASK;
set_head (old_top, old_size | PREV_INUSE);
/*
Note that the following assignments completely overwrite
old_top when old_size was previously MINSIZE. This is
intentional. We need the fencepost, even if old_top otherwise gets
lost.
*/
set_head (chunk_at_offset (old_top, old_size),
CHUNK_HDR_SZ | PREV_INUSE);
set_head (chunk_at_offset (old_top,
old_size + CHUNK_HDR_SZ),
CHUNK_HDR_SZ | PREV_INUSE);
/* If possible, release the rest. */
if (old_size >= MINSIZE)
{
_int_free (av, old_top, 1);
}
}
}
}
}
} /* if (av != &main_arena) */
sysmalloc finale
Ολοκληρώστε την κατανομή ενημερώνοντας τις πληροφορίες της αρένας.
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2921C3-L2943C12
if ((unsigned long) av->system_mem > (unsigned long) (av->max_system_mem))
av->max_system_mem = av->system_mem;
check_malloc_state (av);
/* finally, do the allocation */
p = av->top;
size = chunksize (p);
/* check that one of the above allocation paths succeeded */
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (p, nb);
av->top = remainder;
set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, p, nb);
return chunk2mem (p);
}
/* catch all failure paths */
__set_errno (ENOMEM);
return 0;
sysmalloc_mmap
κώδικας sysmalloc_mmap
// From https://github.com/bminor/glibc/blob/f942a732d37a96217ef828116ebe64a644db18d7/malloc/malloc.c#L2392C1-L2481C2
static void *
sysmalloc_mmap (INTERNAL_SIZE_T nb, size_t pagesize, int extra_flags, mstate av)
{
long int size;
/*
Round up size to nearest page. For mmapped chunks, the overhead is one
SIZE_SZ unit larger than for normal chunks, because there is no
following chunk whose prev_size field could be used.
See the front_misalign handling below, for glibc there is no need for
further alignments unless we have have high alignment.
*/
if (MALLOC_ALIGNMENT == CHUNK_HDR_SZ)
size = ALIGN_UP (nb + SIZE_SZ, pagesize);
else
size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
/* Don't try if size wraps around 0. */
if ((unsigned long) (size) <= (unsigned long) (nb))
return MAP_FAILED;
char *mm = (char *) MMAP (0, size,
mtag_mmap_flags | PROT_READ | PROT_WRITE,
extra_flags);
if (mm == MAP_FAILED)
return mm;
#ifdef MAP_HUGETLB
if (!(extra_flags & MAP_HUGETLB))
madvise_thp (mm, size);
#endif
__set_vma_name (mm, size, " glibc: malloc");
/*
The offset to the start of the mmapped region is stored in the prev_size
field of the chunk. This allows us to adjust returned start address to
meet alignment requirements here and in memalign(), and still be able to
compute proper address argument for later munmap in free() and realloc().
*/
INTERNAL_SIZE_T front_misalign; /* unusable bytes at front of new space */
if (MALLOC_ALIGNMENT == CHUNK_HDR_SZ)
{
/* For glibc, chunk2mem increases the address by CHUNK_HDR_SZ and
MALLOC_ALIGN_MASK is CHUNK_HDR_SZ-1. Each mmap'ed area is page
aligned and therefore definitely MALLOC_ALIGN_MASK-aligned. */
assert (((INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK) == 0);
front_misalign = 0;
}
else
front_misalign = (INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK;
mchunkptr p; /* the allocated/returned chunk */
if (front_misalign > 0)
{
ptrdiff_t correction = MALLOC_ALIGNMENT - front_misalign;
p = (mchunkptr) (mm + correction);
set_prev_size (p, correction);
set_head (p, (size - correction) | IS_MMAPPED);
}
else
{
p = (mchunkptr) mm;
set_prev_size (p, 0);
set_head (p, size | IS_MMAPPED);
}
/* update statistics */
int new = atomic_fetch_add_relaxed (&mp_.n_mmaps, 1) + 1;
atomic_max (&mp_.max_n_mmaps, new);
unsigned long sum;
sum = atomic_fetch_add_relaxed (&mp_.mmapped_mem, size) + size;
atomic_max (&mp_.max_mmapped_mem, sum);
check_chunk (av, p);
return chunk2mem (p);
}
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.