Aikido

Prévenir les erreurs de segmentation : la sécurité de la mémoire en C et C++

Sécurité

Règle
Empêcher commun segmentation de segmentation modèles.
Pointeur pointeur déréférences, débordements débordements de mémoire tampon,
et erreurs de type "use-after-free erreurs provoquent plantages et sécurité sécurité.

Langues prises en charge : C/C++

Introduction

Les fautes de segmentation restent la source la plus courante de pannes et de vulnérabilités exploitables dans les applications C/C++. Ces violations d'accès à la mémoire se produisent lorsque le code tente de lire ou d'écrire de la mémoire qu'il ne possède pas, généralement par le biais de déréférencements de pointeurs nuls, de débordements de mémoire tampon, de pointeurs pendants ou d'accès à de la mémoire libérée. Un seul défaut de ségrégation peut entraîner l'arrêt des serveurs de production, mais pire encore, de nombreux modèles de défauts de ségrégation peuvent être exploités pour l'exécution de codes arbitraires.

Pourquoi c'est important

Implications en matière de sécurité : Les débordements de mémoire tampon et les vulnérabilités de type "use-after-free" sont à la base de la plupart des exploits de corruption de mémoire. Les attaquants les exploitent pour écraser les adresses de retour, injecter du shellcode ou manipuler le flux de contrôle du programme. La vulnérabilité Heartbleed de 2014 était une lecture excessive de la mémoire tampon. Les exploits modernes ciblent toujours ces modèles parce qu'ils permettent aux attaquants d'accéder directement à la mémoire.

Stabilité du système : Les erreurs de segmentation provoquent un crash immédiat de votre application, sans dégradation gracieuse. Dans les systèmes de production, cela se traduit par des requêtes abandonnées, des transactions interrompues et des états corrompus. Contrairement aux exceptions des langages de plus haut niveau qui peuvent être rattrapées, les erreurs de segmentation mettent fin au processus, ce qui nécessite des procédures de redémarrage et de récupération.Extension de la surface d'attaque : Chaque déréférencement de pointeur non vérifié, strcpy, memcpyou l'accès à un tableau sans vérification des limites est un point d'entrée potentiel pour l'exploitation. Les attaquants enchaînent ces vulnérabilités, en utilisant l'une d'entre elles pour corrompre la mémoire de manière à pouvoir en exploiter une autre.

Exemples de code

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

Pourquoi ce n'est pas sûr : Le strcpy() provoque un débordement de tampon si l'entrée dépasse 63 octets, ce qui permet aux attaquants d'écraser la mémoire de la pile. L'appel get_config_value() laisse échapper de la mémoire à chaque appel et crée un risque de dangling pointer si les appelants libèrent la mémoire alors que d'autres codes y font encore référence.

✅ 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
}

Pourquoi c'est sûr : La vérification des pointeurs nuls permet d'éviter les accidents dus au déréférencement. L'allocation dynamique élimine les limites de taille des tampons fixes. La copie avec contrôle des limites avec strncpy() évite les débordements. La sémantique de propriété claire dans get_config_value() où l'appelant fournit la mémoire afin d'éviter les confusions et les fuites d'allocation.

Conclusion

La sécurité de la mémoire en C et C++ nécessite une programmation défensive à chaque opération de pointeur et d'allocation de mémoire. Les erreurs de segmentation ne sont pas inévitables, elles peuvent être évitées grâce à des contrôles de nullité cohérents, à la validation des limites et à des modèles clairs de propriété de la mémoire. La détection de ces schémas avant la production permet d'éviter à la fois les plantages et les vulnérabilités exploitables.

FAQ

Vous avez des questions ?

Quels sont les défauts de segmentation les plus courants ?

Les cinq principales sont : les déréférences de pointeurs nuls (accès à ptr->field sans vérifier que ptr != NULL), les débordements de mémoire tampon (strcpy, sprintf, accès aux tableaux sans vérification des limites), use-after-free (accès à la mémoire après free()), double-free (appel à free() deux fois sur le même pointeur), et le débordement de la mémoire tampon de la pile (écriture au-delà des limites du tableau local). Ces failles représentent plus de 80 % des vulnérabilités de corruption de la mémoire dans les bases de code C/C++.

Comment éviter les défaillances les plus courantes ?

Vérifiez toujours les pointeurs avant de les déréférencer (if (!ptr) return ;). Utiliser des fonctions à longueur limitée : strncpy() au lieu de strcpy(), snprintf() au lieu de sprintf(). Suivre explicitement la taille des tampons et les valider avant l'écriture. En C++, préférez std::string et std::vector qui gèrent les limites automatiquement. Initialiser tous les pointeurs à NULL et les remettre à NULL après les avoir libérés.

Quelle est la différence entre un comportement non défini et des défauts de segmentation ?

Les erreurs de segmentation sont l'un des résultats possibles d'un comportement non défini, mais pas le seul. Un comportement non défini peut corrompre silencieusement la mémoire, produire des résultats erronés ou sembler fonctionner correctement lors des tests, mais échouer en production. Les erreurs de segmentation sont "chanceuses" parce qu'elles provoquent une panne immédiate et visible. La corruption silencieuse de la mémoire est pire car elle n'est pas détectée tout en corrompant l'état de l'application ou en créant des vulnérabilités en matière de sécurité.

Quel est le coût en termes de performances des vérifications de pointeurs nuls ?

Minime. La vérification d'un pointeur nul est une instruction de comparaison unique que les processeurs modernes exécutent en quelques nanosecondes. La prédiction de branchement est généralement exacte puisque la plupart des pointeurs sont valides. Le coût en termes de performances est négligeable par rapport au coût d'un crash de production ou d'une faille de sécurité. Établissez un profil avant d'optimiser, et vous constaterez que les vérifications de nullité apparaissent rarement dans les chemins chauds.

Obtenir la sécurité gratuitement

Sécurisez votre code, votre cloud et votre environnement d'exécution dans un système central.
Trouvez et corrigez rapidement et automatiquement les vulnérabilités.

Aucune carte de crédit n'est requise | Scanner les résultats en 32sec.