Aikido

Perché è importante prevenire gli errori di segmentazione per garantire la sicurezza della memoria in C e C++

Sicurezza

Regola
Prevenire comune segmentazione errore .
Puntatore puntero , overflow ,
e errori di tipo "use-after-free" errori causano arresti anomali e vulnerabilità vulnerabilità di sicurezza.

Linguaggi supportati: C/C++

Introduzione

Gli errori di segmentazione rimangono la causa più comune di arresti anomali e vulnerabilità sfruttabili nelle applicazioni C/C++. Queste violazioni dell'accesso alla memoria si verificano quando il codice tenta di leggere o scrivere in un'area di memoria che non gli appartiene, in genere a causa di dereferenziazioni di puntatori nulli, overflow del buffer, puntatori pendenti o accesso a memoria liberata. Un singolo errore di segmentazione può causare il blocco dei server di produzione, ma, cosa ancora più grave, molti modelli di errore di segmentazione sono sfruttabili per l'esecuzione di codice arbitrario.

Perché è importante

Implicazioni per la sicurezza: gli overflow del buffer e le vulnerabilità di tipo “use-after-free” sono alla base della maggior parte degli exploit che causano la corruzione della memoria. Gli aggressori li sfruttano per sovrascrivere gli indirizzi di ritorno, iniettare shellcode o manipolare il flusso di controllo del programma. La vulnerabilità Heartbleed del 2014 era un caso di lettura eccessiva del buffer. Gli exploit moderni continuano a prendere di mira questi modelli perché garantiscono agli aggressori un accesso diretto alla memoria.

Stabilità del sistema: Gli errori di segmentazione causano l'arresto immediato dell'applicazione senza alcuna degradazione graduale. Nei sistemi di produzione, ciò comporta la perdita di richieste, l'interruzione delle transazioni e il danneggiamento dello stato. A differenza delle eccezioni dei linguaggi di alto livello, che possono essere intercettate, gli errori di segmentazione terminano il processo, rendendo necessarie procedure di riavvio e ripristino.Espansione della superficie di attacco: Ogni dereferenziazione di un puntatore non controllata, strcpy, memcpy, oppure l'accesso agli array senza controllo dei limiti rappresenta un potenziale punto di ingresso per gli attacchi. Gli aggressori concatenano queste vulnerabilità, sfruttandone una per corrompere la memoria in modo tale da consentire lo sfruttamento di un'altra.

Esempi di codice

❌ Non 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
}

Perché non è sicuro: Il strcpy() La chiamata provoca un overflow del buffer se l'input supera i 63 byte, consentendo agli aggressori di sovrascrivere la memoria dello stack. Il get_config_value() La funzione causa una perdita di memoria ad ogni chiamata e comporta il rischio di puntatori pendenti se chi la chiama libera la memoria mentre altro codice continua a farvi riferimento.

✅ Conforme:

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
}

Perché è sicuro: I controlli sui puntatori nulli impediscono i crash dovuti alla dereferenziazione. L'allocazione dinamica elimina i limiti fissi di dimensione del buffer. La copia con controllo dei limiti tramite strncpy() impedisce il traboccamento. Semantica di proprietà chiara in get_config_value() in cui è il chiamante a fornire la memoria, evitando così confusione nell'allocazione e perdite di memoria.

Conclusione

La sicurezza della memoria in C e C++ richiede una programmazione difensiva in ogni operazione sui puntatori e in ogni allocazione di memoria. Gli errori di segmentazione non sono inevitabili, ma possono essere prevenuti attraverso controlli sistematici del valore nullo, la verifica dei limiti e modelli chiari di gestione della memoria. Individuare questi modelli prima della messa in produzione consente di prevenire sia i crash che le vulnerabilità sfruttabili.

Domande frequenti

Hai delle domande?

Quali sono i modelli più comuni di errore di segmentazione?

Le prime cinque sono: dereferenziazioni di puntatori nulli (accesso a ptr->field senza verificare che ptr != NULL), overflow del buffer (strcpy, sprintf, accesso agli array senza controlli dei limiti), use-after-free (accesso alla memoria dopo free()), double-free (chiamata di free() due volte sullo stesso puntatore) e overflow del buffer dello stack (scrittura oltre i limiti dell’array locale). Queste rappresentano oltre l’80% delle vulnerabilità legate alla corruzione della memoria nei codici scritti in C/C++.

Come posso evitare i casi più comuni di segfault?

Controllare sempre i puntatori prima di dereferenziarli (if (!ptr) return;). Utilizzare funzioni con limiti di lunghezza: strncpy() invece di strcpy(), snprintf() invece di sprintf(). Tenere traccia in modo esplicito delle dimensioni dei buffer e verificarle prima delle operazioni di scrittura. In C++, preferire std::string e std::vector, che gestiscono automaticamente i limiti. Inizializzare tutti i puntatori a NULL e impostarli a NULL dopo aver liberato la memoria.

Qual è la differenza tra comportamento indefinito e errori di segmentazione?

Gli errori di segmentazione sono una delle possibili conseguenze di un comportamento indefinito, ma non l'unica. Un comportamento indefinito potrebbe corrompere la memoria in modo silenzioso, produrre risultati errati o sembrare funzionare correttamente durante i test ma fallire in produzione. Gli errori di segmentazione sono "fortunati" perché causano un crash immediato e visibile. La corruzione silenziosa della memoria è peggiore perché passa inosservata mentre compromette lo stato dell'applicazione o crea vulnerabilità di sicurezza.

Qual è il costo in termini di prestazioni dei controlli sui puntatori nulli?

Minimo. Un controllo del puntatore nullo è una singola istruzione di confronto che le CPU moderne eseguono in nanosecondi. La previsione di diramazione è solitamente accurata, poiché la maggior parte dei puntatori è valida. Il costo in termini di prestazioni è trascurabile rispetto al costo di un crash di produzione o di una vulnerabilità di sicurezza. Eseguite un’analisi delle prestazioni prima di ottimizzare e scoprirete che i controlli di nullità compaiono raramente nei percorsi più frequentati.

Metti in sicurezza ora

Metti in sicurezza il tuo codice, il cloud e il runtime in un unico sistema centralizzato.
Trova e risolvi le vulnerabilità rapidamente e automaticamente.

Nessuna carta di credito richiesta | Risultati della scansione in 32 secondi.