Aikido

Perché dovresti prevenire i segmentation fault per garantire la sicurezza della memoria in C e C++

Sicurezza

Regola
Prevenire i pattern comuni di segmentation fault.
La dereferenziazione di puntatori nulli, i buffer overflow
e gli errori di use-after-free causano arresti anomali e vulnerabilità di sicurezza.

Linguaggi supportati: C/C++

Introduzione

I segmentation fault 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 memoria che non possiede, tipicamente attraverso la dereferenziazione di puntatori nulli, buffer overflow, puntatori pendenti (dangling pointers) o l'accesso a memoria liberata. Un singolo segfault può mandare in tilt i server di produzione e, peggio ancora, molti pattern di segfault sono sfruttabili per l'esecuzione di codice arbitrario.

Perché è importante

Implicazioni sulla sicurezza: I buffer overflow e le vulnerabilità use-after-free sono alla base della maggior parte degli exploit di 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 buffer over-read. Gli exploit moderni prendono ancora di mira questi pattern perché offrono agli aggressori un accesso diretto alla memoria.

Stabilità del sistema: I segmentation fault mandano in crash l'applicazione immediatamente, senza alcuna degradazione graduale. Nei sistemi di produzione, ciò significa richieste perse, transazioni interrotte e stato corrotto. A differenza delle eccezioni nei linguaggi di livello superiore che possono essere intercettate, i segfault terminano il processo, richiedendo procedure di riavvio e ripristino.Ampliamento dellasuperficie di attacco: Qualsiasi dereferenziazione di puntatore non controllata, strcpy, memcpy, o accesso a un array senza controllo dei limiti, rappresenta un potenziale punto di ingresso per lo sfruttamento. Gli aggressori concatenano queste vulnerabilità, usandone una per corrompere la memoria in modi che consentono di sfruttarne 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 parametro strcpy() la chiamata causa un buffer overflow se l'input supera i 63 byte, consentendo agli aggressori di sovrascrivere la memoria dello stack. La funzione get_config_value() perde memoria a ogni chiamata e crea il rischio di puntatori pendenti (dangling pointer) se i chiamanti liberano la memoria mentre altro codice vi fa ancora 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 prevengono i crash da dereferenziazione. L'allocazione dinamica elimina i limiti fissi sulla dimensione del buffer. La copia con controllo dei limiti tramite strncpy() previene l'overflow. Chiare semantiche di proprietà in get_config_value() in cui il chiamante fornisce la memoria evitano confusione nell'allocazione e perdite (leak).

Conclusione

La sicurezza della memoria in C e C++ richiede una programmazione difensiva in ogni operazione sui puntatori e in ogni allocazione di memoria. I segmentation fault non sono inevitabili: possono essere prevenuti tramite controlli sui valori nulli coerenti, convalida dei limiti e chiari pattern di proprietà della memoria. Intercettare questi pattern prima della produzione previene sia i crash che le vulnerabilità sfruttabili.

FAQ

Hai domande?

Quali sono i pattern di segmentation fault più comuni?

I primi cinque sono: dereferenziazione di puntatori nulli (accesso a ptr->field senza verificare ptr != NULL), buffer overflow (strcpy, sprintf, accesso ad array senza controlli sui limiti), use-after-free (accesso alla memoria dopo free()), double-free (chiamata a free() due sullo stesso puntatore) e stack buffer overflow (scrittura oltre i limiti dell'array locale). Questi rappresentano oltre l'80% delle vulnerabilità di corruzione della memoria nelle codebase in C/C++.

Come posso prevenire i pattern di segfault più comuni?

Verifica sempre i puntatori prima di dereferenziarli (if (!ptr) return;). Utilizza funzioni con limiti di lunghezza: strncpy() invece di strcpy(), snprintf() invece di sprintf(). Tieni traccia esplicita delle dimensioni dei buffer ed effettua la validazione prima delle scritture. In C++, preferisci std::string e std::vector, che gestiscono i limiti automaticamente. Inizializza tutti i puntatori a NULL e impostali a NULL dopo aver liberato la memoria.

Qual è la differenza tra comportamento indefinito e segmentation fault?

I segmentation fault sono un possibile risultato del comportamento indefinito, ma non l'unico. Il comportamento indefinito potrebbe corrompere la memoria silenziosamente, produrre risultati errati o sembrare funzionare correttamente durante i test per poi fallire in produzione. I segfault sono "afortunati" perché causano un crash immediato e visibile. La corruzione silenziosa della memoria è peggiore poiché passa inosservata mentre corrompe lo stato dell'applicazione o crea vulnerabilità di sicurezza.

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

Minimo. Un controllo sui puntatori nulli è una singola istruzione di confronto che le moderne CPU eseguono in nanosecondi. La predizione dei branch è solitamente accurata poiché la maggior parte dei puntatori è valida. Il costo in termini di prestazioni è trascurabile rispetto al costo di un crash in produzione o di una vulnerabilità di sicurezza. Esegui il profiling prima di ottimizzare e noterai che i controlli sui valori nulli compaiono raramente negli hot path.

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.