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
- Estouro de buffer - gravações fora do limite podem corromper o conteúdo de objetos adjacentes ou dados internos (como informações de contabilidade para o heap ) ouendereços de retorno .
- Buffer over-read - out-of-bound leituras podem revelar dados confidenciais ou ajudar os invasores a contornar a randomização do layout do espaço de endereço .
- Condição de corrida - leituras / gravações simultâneas na memória compartilhada
- Falha de página inválida - acessando um ponteiro fora do espaço da memória virtual. Uma anulação da referência do ponteiro nulo geralmente causa uma exceção ou encerramento do programa na maioria dos ambientes, mas pode causar corrupção nos kernels do sistema operacionalou sistemas sem proteção de memória , ou quando o uso do ponteiro nulo envolve um deslocamento grande ou negativo.
- Use after free - desreferenciar um ponteiro pendente que armazena o endereço de um objeto que foi excluído.
-
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.