malloc & sysmalloc

Apoie o HackTricks

Resumo da Ordem de Alocação

(Nenhuma verificação é explicada neste resumo e alguns casos foram omitidos por brevidade)

  1. __libc_malloc tenta obter um bloco do tcache, se não conseguir, chama _int_malloc

  2. _int_malloc :

  3. Tenta gerar a arena se não houver nenhuma

  4. Se houver algum bloco de fast bin do tamanho correto, use-o

  5. Preenche o tcache com outros blocos de fast bin

  6. Se houver algum bloco de small bin do tamanho correto, use-o

  7. Preenche o tcache com outros blocos desse tamanho

  8. Se o tamanho solicitado não for para small bins, consolida fast bin em unsorted bin

  9. Verifica o unsorted bin, usa o primeiro bloco com espaço suficiente

  10. Se o bloco encontrado for maior, divida-o para retornar uma parte e adicione o restante de volta ao unsorted bin

  11. Se um bloco for do mesmo tamanho que o solicitado, use-o para preencher o tcache em vez de retorná-lo (até que o tcache esteja cheio, então retorne o próximo)

  12. Para cada bloco de tamanho menor verificado, coloque-o no seu respectivo small ou large bin

  13. Verifica o large bin no índice do tamanho solicitado

  14. Comece a procurar a partir do primeiro bloco que seja maior que o tamanho solicitado, se encontrar algum, retorne-o e adicione os restos ao small bin

  15. Verifica os large bins dos próximos índices até o final

  16. Do próximo índice maior, verifique se há algum bloco, divida o primeiro bloco encontrado para usá-lo para o tamanho solicitado e adicione o restante ao unsorted bin

  17. Se nada for encontrado nos bins anteriores, obtenha um bloco do bloco superior

  18. Se o bloco superior não for grande o suficiente, aumente-o com sysmalloc

__libc_malloc

A função malloc na verdade chama __libc_malloc. Esta função verificará o tcache para ver se há algum bloco disponível do tamanho desejado. Se houver, ele o usará e, se não houver, verificará se é uma única thread e, nesse caso, chamará _int_malloc na arena principal e, se não, chamará _int_malloc na arena da thread.

Código __libc_malloc

```c // 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; }

</details>

Observe como ele sempre marcará o ponteiro retornado com `tag_new_usable`, a partir do código:
```c
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

Esta é a função que aloca memória usando os outros bins e o bloco superior.

  • Início

Começa definindo algumas variáveis e obtendo o tamanho real que o espaço de memória solicitado precisa ter:

Fast Bin

Se o tamanho necessário estiver dentro dos tamanhos dos Fast Bins, tente usar um pedaço do fast bin. Basicamente, com base no tamanho, ele encontrará o índice do fast bin onde os pedaços válidos devem estar localizados e, se houver algum, retornará um deles. Além disso, se o tcache estiver ativado, ele preencherá o tcache bin desse tamanho com fast bins.

Enquanto realiza essas ações, algumas verificações de segurança são executadas aqui:

  • Se o pedaço estiver desalinhado: malloc(): pedaço fastbin desalinhado detectado 2

  • Se o pedaço à frente estiver desalinhado: malloc(): pedaço fastbin desalinhado detectado

  • Se o pedaço retornado tiver um tamanho incorreto por causa de seu índice no fast bin: malloc(): corrupção de memória (fast)

  • Se algum pedaço usado para preencher o tcache estiver desalinhado: malloc(): pedaço fastbin desalinhado detectado 3

malloc_consolidate

Se não era um pequeno pedaço, é um grande pedaço e, nesse caso, malloc_consolidate é chamado para evitar fragmentação de memória.

Bin não ordenado

É hora de verificar o bin não ordenado em busca de um possível chunk válido para usar.

Início

Isso começa com um grande loop que percorrerá o bin não ordenado na direção bk até chegar ao final (a estrutura da arena) com while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))

Além disso, algumas verificações de segurança são realizadas sempre que um novo chunk é considerado:

  • Se o tamanho do chunk for estranho (muito pequeno ou muito grande): malloc(): tamanho inválido (não ordenado)

  • Se o tamanho do próximo chunk for estranho (muito pequeno ou muito grande): malloc(): tamanho próximo inválido (não ordenado)

  • Se o tamanho anterior indicado pelo próximo chunk difere do tamanho do chunk: malloc(): next->prev_size incompatível (não ordenado)

  • Se não victim->bck->fd == victim ou não victim->fd == av (arena): malloc(): lista duplamente encadeada não ordenada corrompida

  • Como estamos sempre verificando o último, seu fd deve estar sempre apontando para a estrutura da arena.

  • Se o próximo chunk não estiver indicando que o anterior está em uso: malloc(): next->prev_inuse inválido (não ordenado)

Se isso foi bem-sucedido, retorne o chunk e acabou, caso contrário, continue executando a função...

se o tamanho for igual

Continue removendo o chunk do bin, no caso em que o tamanho solicitado é exatamente o mesmo do chunk:

  • Se o tcache não estiver cheio, adicione-o ao tcache e continue indicando que há um chunk de tcache que poderia ser usado

  • Se o tcache estiver cheio, simplesmente use-o retornando-o

Limites do _int_malloc

Neste ponto, se algum bloco foi armazenado no tcache que pode ser usado e o limite é atingido, apenas retorne um bloco tcache.

Além disso, se MAX_ITERS for atingido, saia do loop e obtenha um bloco de uma maneira diferente (bloco superior).

Se return_cached foi definido, apenas retorne um bloco do tcache para evitar buscas maiores.

Se um chunk não for encontrado adequado para isso, continue

Large Bin (próximo maior)

Se na large bin exata não houver nenhum chunk que possa ser usado, comece a percorrer todas as próximas large bins (começando pela imediatamente maior) até que uma seja encontrada (se houver).

O restante do chunk dividido é adicionado na unsorted bin, last_reminder é atualizado e a mesma verificação de segurança é realizada:

  • bck->fd-> bk != bck: malloc(): corrupted unsorted chunks2

sysmalloc

Início do sysmalloc

Se a arena for nula ou o tamanho solicitado for muito grande (e ainda houver mmaps permitidos), use sysmalloc_mmap para alocar espaço e retorná-lo.

sysmalloc não é a arena principal

Primeiro tentará expandir o heap anterior para este heap. Se não for possível, tentará alocar um novo heap e atualizar os ponteiros para poder usá-lo. Por fim, se isso não funcionar, tentará chamar sysmalloc_mmap.

sysmalloc final

Conclua a alocação atualizando as informações da arena

// 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

Last updated