Segurança de memória - Memory safety

A segurança da memória é o estado de proteção contra vários bugs de software e vulnerabilidades de segurança ao lidar com o acesso à memória , como estouros de buffer e ponteiros pendentes . Por exemplo, Java é considerado seguro para a memória porque sua detecção de erro em tempo de execução verifica limites de array e desreferências de ponteiro. Em contraste, C e C ++ permitem aritmética arbitrária de ponteiros com ponteiros implementados como endereços diretos de memória sem provisão para verificação de limites e, portanto, são potencialmente inseguros para a memória .

História

Os erros de memória foram considerados pela primeira vez no contexto de gerenciamento de recursos e sistemas de compartilhamento de tempo , em um esforço para evitar problemas como fork bombs . Os desenvolvimentos eram principalmente teóricos até o worm Morris , que explorou um estouro de buffer no fingerd . O campo da segurança do computador desenvolveu-se rapidamente depois disso, aumentando com uma infinidade de novos ataques , como o ataque de retorno à libc e técnicas de defesa, como a pilha não executável e a randomização do layout do espaço de endereço . A randomização evita a maioria dos ataques de estouro de buffer e exige que o invasor use heap spraying ou outros métodos dependentes de aplicativos para obter endereços, embora sua adoção tenha sido lenta. No entanto, as implantações da tecnologia são normalmente limitadas a bibliotecas aleatórias e à localização da pilha.

Abordagens

DieHard, seu novo design DieHarder e a Allinea Distributed Debugging Tool são alocadores de heap especiais que alocam objetos em sua própria página de memória virtual aleatória, permitindo que leituras e gravações inválidas sejam interrompidas e depuradas na instrução exata que as causa. A proteção depende da proteção da memória do hardware e, portanto, a sobrecarga normalmente não é substancial, embora possa aumentar significativamente se o programa fizer uso intenso da alocação. A randomização fornece apenas proteção probabilística contra erros de memória, mas muitas vezes pode ser facilmente implementada no software existente, vinculando novamente o binário.

A ferramenta memcheck do Valgrind usa um simulador de conjunto de instruções e executa o programa compilado em uma máquina virtual de verificação de memória, fornecendo detecção garantida de um subconjunto de erros de memória em tempo de execução. No entanto, isso normalmente retarda o programa em um fator de 40 e, além disso, deve ser explicitamente informado sobre os alocadores de memória personalizados.

Com acesso ao código-fonte, existem bibliotecas que coletam e rastreiam valores legítimos para ponteiros ("metadados") e verificam cada acesso de ponteiro em relação aos metadados para validade, como o coletor de lixo Boehm . Em geral, a segurança da memória pode ser garantida com segurança usando o rastreamento da coleta de lixo e a inserção de verificações de tempo de execução em cada acesso à memória; esta abordagem tem sobrecarga, mas menos do que a do Valgrind. Todas as linguagens com coleta de lixo seguem essa abordagem. Para C e C ++, existem muitas ferramentas que realizam uma transformação de tempo de compilação do código para fazer verificações de segurança de memória em tempo de execução, como CheckPointer e AddressSanitizer, que impõe um fator de desaceleração médio de 2.

Outra abordagem usa a análise estática do programa e a prova automatizada de teoremas para garantir que o programa esteja livre de erros de memória. Por exemplo, a linguagem de programação Rust implementa um verificador de empréstimo para garantir a segurança da memória. Ferramentas como Coverity oferecem análise de memória estática para os ponteiros inteligentes do C. C ++ são uma forma limitada dessa abordagem.

Tipos de erros de memória

Muitos tipos diferentes de erros de memória podem ocorrer:

  • Erros de acesso : leitura / gravação inválida de um ponteiro
  • Variáveis ​​não inicializadas - é usada uma variável à qual não foi atribuído um valor. Ele pode conter um valor indesejado ou, em alguns idiomas, um valor corrompido.
    • Null ponteiro dereference - dereferencing um ponteiro inválido ou um ponteiro para a memória que não foi alocado
    • Os ponteiros selvagens surgem quando um ponteiro é usado antes da inicialização para algum estado conhecido. Eles mostram o mesmo comportamento errático que ponteiros pendurados, embora sejam menos propensos a permanecerem não detectados.
  • Vazamento de memória - quando o uso de memória não é rastreado ou é rastreado incorretamente
    • Esgotamento da pilha - ocorre quando um programa fica sem espaço na pilha, normalmente devido a uma recursão muito profunda. Uma página de proteção normalmente interrompe o programa, evitando a corrupção da memória, mas as funções com quadros de pilha grandespodem ignorar a página.
    • Esgotamento de heap - o programa tenta alocar mais memória do que a quantidade disponível. Em alguns idiomas, essa condição deve ser verificada manualmente após cada alocação.
    • Double free - chamadas repetidas para o free podem liberar prematuramente um novo objeto no mesmo endereço. Se o endereço exato não tiver sido reutilizado, outros danos podem ocorrer, especialmente em alocadores que usam listas gratuitas .
    • Liberação inválida - passar um endereço inválido para a liberação pode corromper o heap .
    • Livre incompatível - quando vários alocadores estão em uso, tentando liberar memória com uma função de desalocação de um alocador diferente
    • Aliasing indesejado - quando o mesmo local de memória é alocado e modificado duas vezes para fins não relacionados.

Referências