Aikido

Evitar falhas de segmentação: segurança de memória em C e C++

Segurança

Regra
Prevenir comum segmentação falhas padrões.
Nulo ponteiro desreferências, buffer buffer,
e uso depois de livre erros causam crashes e segurança vulnerabilidades.

Linguagens suportadas: C/C++

Introdução

As falhas de segmentação continuam a ser a fonte mais comum de falhas e vulnerabilidades exploráveis em aplicações C/C++. Estas violações de acesso à memória ocorrem quando o código tenta ler ou escrever memória que não lhe pertence, normalmente através de dereferências de ponteiro nulo, estouros de buffer, ponteiros pendentes ou acesso a memória libertada. Um único segfault pode derrubar servidores de produção, mas pior, muitos padrões de segfault são exploráveis para execução arbitrária de código.

Porque é importante

Implicações de segurança: Os estouros de buffer e as vulnerabilidades use-after-free são a base da maioria das explorações de corrupção de memória. Os atacantes aproveitam-nas para sobrescrever endereços de retorno, injetar shellcode ou manipular o fluxo de controlo do programa. A vulnerabilidade Heartbleed de 2014 foi uma leitura excessiva de um buffer. As explorações modernas continuam a visar estes padrões porque permitem o acesso direto à memória por parte dos atacantes.

Estabilidade do sistema: As falhas de segmentação fazem com que a sua aplicação falhe imediatamente, sem degradação graciosa. Em sistemas de produção, isto significa pedidos perdidos, transacções interrompidas e estado corrompido. Ao contrário das exceções de linguagens de alto nível que podem ser capturadas, as falhas de segmentação encerram o processo, exigindo procedimentos de reinicialização e recuperação.Expansão da superfície de ataque: Cada desreferência de ponteiro não verificada, strcpy, memcpyO acesso a um array sem verificação de limites é um potencial ponto de entrada para exploração. Os atacantes encadeiam estas vulnerabilidades, utilizando uma para corromper a memória de forma a permitir a exploração de outra.

Exemplos de código

Não conforme:

void process_user_data(const char* input) {
    char buffer[64];
    strcpy(buffer, input);  // No bounds checking

    char* token = strtok(buffer, ",");
    while (token != NULL) {
        process_token(token);
        token = strtok(NULL, ",");
    }
}

int* get_config_value(int key) {
    int* value = (int*)malloc(sizeof(int));
    *value = lookup_config(key);
    return value;  // Caller must free, but no documentation
}

Porque é que não é seguro: O strcpy() causa um estouro de buffer se a entrada exceder 63 bytes, permitindo que os atacantes sobrescrevam a memória da pilha. A chamada get_config_value() vaza memória em cada chamada e cria o risco de ponteiro pendente se quem chama libera a memória enquanto outro código ainda faz referência a ela.

Conformidade:

void process_user_data(const char* input) {
    if (!input) return;

    size_t input_len = strlen(input);
    char* buffer = malloc(input_len + 1);
    if (!buffer) return;

    strncpy(buffer, input, input_len);
    buffer[input_len] = '\0';

    char* token = strtok(buffer, ",");
    while (token != NULL) {
        process_token(token);
        token = strtok(NULL, ",");
    }

    free(buffer);
}

int get_config_value(int key, int* out_value) {
    if (!out_value) return -1;

    *out_value = lookup_config(key);
    return 0;  // Caller owns out_value memory
}

Porque é que é seguro: As verificações de ponteiro nulo evitam falhas de desreferência. A alocação dinâmica elimina os limites fixos de tamanho de buffer. A cópia com limites verificados com strncpy() evita o transbordamento. Semântica de propriedade clara em get_config_value() em que o chamador fornece a memória, para evitar confusões e fugas de informação.

Conclusão

A segurança da memória em C e C++ requer uma programação defensiva em cada operação de ponteiro e atribuição de memória. As falhas de segmentação não são inevitáveis, são evitáveis através de verificações nulas consistentes, validação de limites e padrões claros de propriedade de memória. A deteção desses padrões antes da produção evita tanto falhas quanto vulnerabilidades exploráveis.

FAQs

Tem perguntas?

Quais são os padrões de falhas de segmentação mais comuns?

As cinco principais são: desreferências de ponteiro nulo (aceder a ptr->campo sem verificar se ptr != NULL), estouro de buffer (strcpy, sprintf, acesso a array sem verificação de limites), use-after-free (aceder à memória depois de free()), double-free (chamar free() duas vezes no mesmo ponteiro) e estouro de buffer de pilha (escrever para além dos limites do array local). Estas são responsáveis por mais de 80% das vulnerabilidades de corrupção de memória em bases de código C/C++.

Como posso evitar os padrões de falha de segurança mais comuns?

Verificar sempre os ponteiros antes de os desreferenciar (if (!ptr) return;). Utilizar funções de comprimento limitado: strncpy() em vez de strcpy(), snprintf() em vez de sprintf(). Controlar explicitamente os tamanhos dos buffers e validá-los antes das escritas. Em C++, prefira std::string e std::vetor que tratam os limites automaticamente. Inicializar todos os apontadores para NULL e defini-los como NULL após a libertação.

Qual é a diferença entre comportamento indefinido e falhas de segmentação?

As falhas de segmentação são um resultado possível do comportamento indefinido, mas não o único. O comportamento indefinido pode corromper a memória silenciosamente, produzir resultados errados ou parecer funcionar bem nos testes, mas falhar na produção. Segfaults são "sortudos" porque eles travam imediatamente e visivelmente. A corrupção silenciosa da memória é pior porque não é detectada enquanto corrompe o estado da aplicação ou cria vulnerabilidades de segurança.

Qual é o custo de desempenho das verificações de ponteiro nulo?

Mínimo. Uma verificação de ponteiro nulo é uma instrução de comparação única que as CPUs modernas executam em nanossegundos. A previsão de ramificação é normalmente exacta, uma vez que a maioria dos apontadores é válida. O custo de desempenho é insignificante em comparação com o custo de uma falha de produção ou vulnerabilidade de segurança. Faça um perfil antes de otimizar, e verá que as verificações nulas raramente aparecem em caminhos quentes.

Obter segurança gratuitamente

Proteja seu código, nuvem e tempo de execução em um sistema central.
Encontre e corrija vulnerabilidades rapidamente de forma automática.

Não é necessário cartão de crédito | Resultados do scan em 32secs.