Génie civil & structural
Comment écrire un code C sécurisé pour prévenir les dépassements de tampons
Table of Contents
Bien qu'ils soient bien documentés depuis des décennies, ils continuent de poser de graves problèmes tels que la corruption des données, les pannes de système et l'exécution de codes à distance. L'écriture de code C sécurisé nécessite une compréhension approfondie de la façon dont les débordements de tampon se produisent et une approche disciplinée pour les prévenir. Cet article fournit un guide complet pour l'écriture de code C robuste et résistant aux débordements, couvrant les concepts fondamentaux, les fonctions sûres, les techniques de validation, les protections des compilateurs et les mesures défensives modernes.
Comprendre les dépassements de tampons
Un dépassement de tampon se produit lorsqu'un programme écrit plus de données à un bloc contigu de mémoire (un tampon) que le tampon a été alloué pour tenir. Puisque les tampons résident dans la mémoire de pile ou de tas, dépassant leurs limites écrase les emplacements de mémoire adjacents. Cette corruption peut modifier l'état du programme, introduire un comportement imprévisible, ou être exploité par un attaquant pour injecter et exécuter le code arbitraire.
Les conséquences dépendent de ce qui est écrasé. L'écrasement d'une adresse de retour sur la pile peut rediriger l'exécution vers le code contrôlé par l'attaquant. Les pointeurs d'écrasement peuvent conduire à des écritures de mémoire arbitraires. Même les crashs simples peuvent être mis à profit pour les attaques de déni de service.
Dépassements par piles
Les variables locales, y compris les tampons déclarés à l'intérieur des fonctions, sont stockées sur la pile. La pile contient également l'adresse de retour, les pointeurs de cadre enregistrés et d'autres données de contrôle. Lorsqu'un tampon linéaire comme est dépassé, les données se déversent dans l'adresse de retour et au-delà.
Dépassements liés au heap
Les tampons attribués dynamiquement (via , , etc.) résident sur le tas. Les écoulements peuvent ici corrompre les métadonnées utilisées par l'allocator, conduisant à des accidents ou à une exploitation par pulvérisation de tas ou des attaques après utilisation.
Fonctions vulnérables communes et leurs alternatives sûres
La bibliothèque standard C fournit plusieurs fonctions qui ne effectuent pas de vérification des limites. L'utilisation de celles-ci est la cause la plus courante de débordement de tampon.
Copie de chaînes et concaténation
- — Insûre : copie jusqu'à un terminateur nul, sans limite de longueur.
]Sûre alternative :[ — copie au maximum n caractères; notez qu'il ne met pas fin à nul si la source est plus longue que n, de sorte que toujours manuellement null-termine. - Mieux encore : — disponible sur BSD et de nombreux systèmes Linux ; toujours null-termine et retourne la longueur de la chaîne source pour la détection de troncation.
- — Insécurité: concaténates sans limites.
Safe alternative: — appendices au plus n caractères et toujours null-terminates.
Produit et entrée formatés
- — Insûre: écrit une sortie formatée dans un tampon sans vérification de taille.
Safe alternative:[ — limite la sortie à la taille-1 caractères plus terminateur nul. - — Risque similaire; utiliser à la place.
- — Extrêmement dangereux; retiré de la norme C11. Utiliser à la place.
- — Aucun contrôle de limites. Utiliser ou avec un spécificateur de largeur de champ.
Copier et déplacer la mémoire
- — En sécurité seulement si n est vérifié pour ne pas dépasser la taille du tampon de destination.
] [recoupement des poignées] et toujours assurer n ≤ taille de la poignée. - Certaines plates-formes fournissent de l'annexe K (facultatif en C11), mais l'adoption est limitée.
Validation et gestion de la taille
Même avec des fonctions sûres, vous devez valider les longueurs d'entrée, assurer des tailles de tampon appropriées, et gérer la troncation potentielle gracieusement.
Vérifier les longueurs d'entrée
Avant de copier ou de traiter les données externes (entrée utilisateur, données réseau, contenu du fichier), déterminez sa longueur maximale acceptable et rejetez ou tronquez les données qui les dépassent.
#define MAX_INPUT 255
char buffer[MAX_INPUT + 1]; // +1 for null
if (strlen(user_input) > MAX_INPUT) {
// Handle error: reject or truncate
fputs("Input too long", stderr);
return -1;
}
strncpy(buffer, user_input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
Utiliser des tampons de taille fixe avec des limites connues
Dans la mesure du possible, définissez les tampons avec une taille constante et appliquez-le dans tout le code. Évitez les tableaux de longueur variable (VLA) qui peuvent causer des débordements de pile si de grandes tailles sont fournies.
Poignez la troncature explicitement
Des fonctions comme et peuvent tronquer les données. Soyez conscient de la valeur de retour pour détecter la troncation et décider si les données tronquées sont acceptables ou si une erreur doit être soulevée. Ignorer la troncation peut laisser des tampons dans un état inattendu.
Compiler les drapeaux de sécurité et les protections contre les temps d'exécution
Les compilateurs modernes offrent des drapeaux qui ajoutent la détection de débordement de tampon et l'atténuation sans changement de code. Activez-les dans votre système de construction.
- / — Insère les piles de canaires (valeurs aléatoires) avant les adresses de retour. Si un dépassement de tampon écrase le canaire avant de modifier l'adresse de retour, le programme avorte avant que l'exploitation ne soit terminée.
- — Remplace les appels à des fonctions dangereuses comme et par des versions vérifiées qui avortent si le tampon de destination est trop petit. Nécessite ou une optimisation plus élevée.
- — Prévient les vulnérabilités de la chaîne de format qui peuvent entraîner des débordements de tampon ou des fuites d'informations.
- — Code des instruments AddressSanitizer (ASan) pour détecter les débordements de tampon, l'utilisation après-vente et d'autres erreurs de mémoire au moment de l'exécution.
- — évite d'optimiser les contrôles de débordement (utiliser avec prudence).
Protections du système d'exploitation
Les canaris de piles ne sont qu'une couche. Exploitez les technologies d'atténuation dans les OS modernes comprennent:
- Prévention de l'exécution des données (DEP) / NX bit — Marque la pile et le tas comme non exécutables, empêchant l'exécution de shellcode.
- Adresse randomisation de la disposition de l'espace (ASLR) — Randomise les adresses de mémoire (stable, tas, bibliothèques partagées) pour rendre plus difficile la prévision des cibles.
- Relocation Read-Only (RELRO) — Protège GOT (Global Offset Table) contre l'écrasement.
L'activation de ces protections (généralement par défaut) soulève la barre d'exploitation mais ne remplace pas le codage sécurisé.
Vérifications de code et analyse statique
L'examen humain combiné à l'analyse statique automatisée peut attraper les problèmes de dépassement de tampon tôt. Intégrez-les dans votre workflow de développement.
- Examen manuel du code[ — Recherchez les utilisations de fonctions dangereuses, les vérifications de taille manquantes et les boucles qui écrivent au-delà des limites du tampon.
- Outils d'analyse statique[ — Des outils comme , , et détectent les débordements potentiels, l'utilisation de fonctions dangereuses et les erreurs hors-par-un. Ils peuvent être exécutés dans des pipelines CI.
- Fuzzing — Utilisez libFuzzer, AFL ou d'autres dispositifs de flou pour tester automatiquement la manipulation des entrées avec des données inattendues qui peuvent déclencher des débordements.
Exemples pratiques de code sécurisé
Copie de chaîne de sécurité avec contrôle des liens
#include <stdio.h>
#include <string.h>
int safe_string_copy(char *dest, size_t dest_size, const char *src) {
if (!dest || !src || dest_size == 0) {
return -1; // Invalid parameters
}
size_t src_len = strlen(src);
if (src_len >= dest_size) {
// Source too large; truncation or error
// Option: copy what fits and null-terminate
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0';
return 1; // Truncation occurred
}
strncpy(dest, src, dest_size);
// strncpy fills remaining with null, so dest_size fits; no need to null-terminate if src shorter
return 0; // Success, no truncation
}
Manipulation en entier sécuritaire pour les tailles de tampon
Le dépassement de tampon peut également résulter de dépassements entiers lors du calcul des tailles. Vérifiez toujours l'arithmétique avant l'attribution.
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
void *safe_malloc_array(size_t nmemb, size_t size) {
if (nmemb == 0 || size == 0) {
return NULL; // Or handle zero-size allocation
}
if (nmemb > SIZE_MAX / size) {
// Integer overflow would occur
errno = ENOMEM;
return NULL;
}
return malloc(nmemb * size);
}
Utilisation de snprintf pour les cordes formatées
char log_message[256];
int ret = snprintf(log_message, sizeof(log_message),
"User %s logged in from %s", username, ip_address);
if (ret < 0) {
// Output error
} else if ((size_t)ret >= sizeof(log_message)) {
// Truncation occurred; handle if needed
}
Pratiques exemplaires supplémentaires
- Initialiser les tampons — Toujours zéro-initialiser les tampons pour éviter de fuiter la mémoire non initialisée.
- Éviter la récursion avec une profondeur non limitée — Les débordements de cheminée peuvent survenir à partir de la récursion profonde; utiliser l'itération ou la profondeur limite.
- Utiliser Qualificateur — Aide le compilateur à optimiser et peut attraper des problèmes d'alias, mais pas empêcher directement les débordements.
- Prefere -correctitude — Prévient la modification accidentelle des chaînes d'entrée et fait respecter l'intention.
- Gestion des erreurs d'exécution[ — Ne pas ignorer les valeurs de retour de fonctions comme , , , etc.
Ressources pour l'apprentissage continu
- Norme de codage C de la CERT SEI — Règles complètes pour le codage C sécurisé.
- CWE-120: Copie tampon sans vérification de la taille de l'entrée — Classification MITRE=s des faiblesses de dépassement de tampon.
- OWASP Bouffer Overflow[ — Directives pratiques du projet de sécurité des applications Web ouvertes.
- GNU C Library Manual: String and Array Utilities — Documentation pour des fonctions de chaînes de caractères sûres.
- AdresseSanitizer — Un détecteur d'erreur mémoire rapide.
Conclusion
En comprenant les mécanismes des débordements, en remplaçant les fonctions dangereuses par des solutions plus sûres, en validant rigoureusement les entrées et les tailles, en permettant des protections du compilateur et en utilisant des analyses et des tests statiques, vous pouvez réduire considérablement le risque de ces vulnérabilités. Aucune technique unique ne suffit; la défense en profondeur, combinant discipline de codage, drapeaux du compilateur, atténuations de l'exploitation et tests approfondis, offre la protection la plus forte.Avec ces pratiques, vous pouvez écrire un code C qui est à la fois puissant et sécurisé, capable de fonctionner en toute sécurité dans les systèmes critiques.