Memória mal gerenciada III

David García    24 junio, 2020
Memória mal gerenciada III

Se tivéssemos que escolher uma vulnerabilidade potencialmente perigosa, provavelmente seria aquela que permite a execução de código arbitrário, ainda mais se ela pudesse ser explorada remotamente. No primeiro post, apresentamos os problemas que uma memória mal gerenciada pode provocar; em seguida, falamos do double free. Agora, veremos mais exemplos.

Dangling pointers ou hanging pointers

O gerenciamento manual de memória é complexo e deve-se prestar atenção à ordem das operações, onde os recursos são obtidos e onde deixamos de usá-los para poder liberá-los em boas condições.

Também é necessário acompanhar cópias de ponteiros ou referências que, se forem liberadas antes do tempo, fazem com os ponteiros fiquem “soltos” ou, em sua versão em inglês, os Dangling Pointers. Ou seja, fazer uso de um recurso que já foi liberado. Um exemplo:

Executamos:

Isso nos deixa com um ponteiro que aponta para uma área de memória inválida (heap) (observe que não é respondido nada depois de “(p2) apunta a …”). Além disso, não há como saber se um recurso que copiou sua direção continua sendo válido, da mesma forma que não é possível recuperar um leak dememória se sua referência for perdida (veremos isso mais adiante).

Para sinalizar que um ponteiro é inválido, atribuímos NULL a esse ponteiro (em C ++ “moderno” atribuiríamos nullptr) para identificar de alguma maneira que ele não aponta para nada. Mas se esse NULL não for verificado, não será útil para nós. Portanto, para cada ponteiro que utilize um recurso, deve-se verificar se ele “não é NULLo”.

A boa prática é, portanto: quando liberamos memória, atribuímos NULL ou nullptr (em C ++) para sinalizar que esse ponteiro não aponta mais para algo válido. Além disso, antes de usá-lo, tanto para copiá-lo quanto para remover sua referência, verificamos sua validade.

Memory leaks ou vazamentos de memória

O oposto de usar uma área de memória que não é mais válida é fazer com que nenhum ponteiro aponte para uma área de memória válida. Depois que a referência é perdida, não poderemos mais liberar a reserva de memória, sendo que este espaço ficará ocupado indefinidamente até o término do programa. É um grande problema quando o programa não termina, por exemplo, em um servidor, onde normalmente fica em execução até que a máquina seja desligada ou que ocorra outra interrupção inevitável.

Um exemplo (se você deseja replicá-lo, faça-o em um sistema virtualizado de teste):

O código mostrado à direita está consumindo partes da memória até que toda ela seja usada. Isso faz com que o sistema esgote a memória RAM, comece a fazer swapping e finalmente inicie o OOM-killer para interromper o processo por ultrapassar o consumo.

O que é o OOM-killerÉ um procedimento especial do kernel (em sistemas Linux) para eliminar processos na memória para que o sistema não fique instável. Na captura de tela, podemos ver a saída do comando ‘dmesg’, que reflete o kill do nosso processo devido ao sobrecarga de recursos que ele representa para o sistema.

Se analisarmos o código, veremos que inserimos um loop infinito no qual a memória está sendo reservada e o mesmo ponteiro é reatribuído para novos blocos dessa memória. As referências anteriores não são liberadas e perdidas, resultando em um vazamento de memória (exatamente como um cano quebrado) que termina drasticamente.

Obviamente, isso é uma dramatização do que acontece em um programa real, mas a realidade é que ocorre assim. O problema é que as reservas de memória não são controladas em um ponto, as referências perdidas se acumulam e acabam sendo um problema. É possível que, em aplicativos com vazamentos de memória que usamos apenas por algumas horas, tenhamos uma desaceleração (isso era mais evidente nos tempos em que a memória RAM era mais limitada) ou um acúmulo de memória. Mas nos servidores é comum que isso leve a uma queda no serviço.

No próximo post, veremos o uso de memória não inicializada.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *