Un buffer overflow (ou dépassement de tampon) est une vulnérabilité mémoire qui se produit lorsqu'un programme écrit plus de données dans une zone mémoire que sa taille allouée, débordant sur les zones adjacentes. Découverte dans les années 70, popularisée par le ver Morris en 1988 et le papier Smashing The Stack For Fun And Profit d'Aleph One en 1996, c'est l'une des vulnérabilités les plus emblématiques de la sécurité informatique. En 2026, malgré 40 ans de protections (ASLR, DEP, canaries, CFG, langages mémoire-safe), elle reste classée par MITRE parmi les CWE les plus dangereuses (CWE-787 Out-of-bounds Write en tête du Top 25 depuis 2021).
1. Définition technique précise
Un buffer overflow apparaît quand :
- Un programme alloue une zone mémoire (buffer) de taille fixe N octets.
- Une opération d'écriture y dépose plus de N octets.
- Les octets en surplus écrasent des données adjacentes (autres variables, métadonnées, pointeurs, code).
L'opération d'écriture peut être triviale (strcpy, gets, memcpy mal dimensionné), arithmétique (boucle qui dépasse), ou indirecte (fonction tierce ne respectant pas le contrat).
Conséquences possibles, par ordre de gravité :
- Crash (segfault) - déni de service.
- Corruption logique - écrasement d'une variable adjacente, modifie un comportement (ex.
is_admin = 0→is_admin = 1). - Détournement du flux d'exécution - écrasement d'une adresse de retour ou d'un pointeur de fonction → exécution arbitraire de code (RCE).
Les CWE associés :
- CWE-787 : Out-of-bounds Write.
- CWE-119 : Improper Restriction of Operations within the Bounds of a Memory Buffer (parent).
- CWE-120 : Buffer Copy without Checking Size of Input (classic buffer overflow).
- CWE-121 : Stack-based Buffer Overflow.
- CWE-122 : Heap-based Buffer Overflow.
- CWE-125 : Out-of-bounds Read (variante en lecture, ex. Heartbleed).
2. Architecture mémoire d'un processus
Pour comprendre un buffer overflow, il faut visualiser la disposition mémoire d'un processus Linux x86-64 typique :
+--------------------+ adresses hautes
| Stack | ← grandit vers le bas
| ↓↓↓ |
+--------------------+
| | |
| ↓ |
| (libre) |
| ↑ |
| | |
+--------------------+
| Heap | ← grandit vers le haut
| ↑↑↑ |
+--------------------+
| BSS | variables globales non init
+--------------------+
| .data | variables globales init
+--------------------+
| .text | code (read + execute)
+--------------------+ adresses basses
- Stack : variables locales, paramètres de fonction, adresses de retour. Allocation/libération automatiques (LIFO).
- Heap : allocations dynamiques (
malloc,new). Gérées explicitement. - BSS / .data / .text : segments statiques.
Un overflow sur la stack et sur le heap exploite des structures différentes - d'où la distinction stack-based vs heap-based.
3. Stack-based buffer overflow
Le cas d'école, et historiquement le plus exploité.
3.1 Mécanisme
Quand une fonction est appelée, le CPU pousse l'adresse de retour sur la pile, puis la fonction réserve de l'espace pour ses variables locales. Une fois finie, la fonction restaure l'état et ret saute à l'adresse de retour stockée.
Si une variable locale est un buffer mal protégé et qu'on écrit au-delà, on peut écraser l'adresse de retour. À la sortie de la fonction, le CPU saute vers l'adresse maintenant contrôlée par l'attaquant.
3.2 Code vulnérable classique
#include <stdio.h>
#include <string.h>
void vulnerable(char *input) {
char buffer[64];
strcpy(buffer, input); // pas de borne, faille
printf("Reçu: %s\n", buffer);
}
int main(int argc, char **argv) {
if (argc > 1) vulnerable(argv[1]);
return 0;
}Si argv[1] fait 200 octets, strcpy écrira 200 octets dans un buffer de 64 - les 136 octets en trop écrasent ce qui suit sur la pile : autres locales, sauvegarde de RBP, puis l'adresse de retour.
3.3 État de la pile après débordement
Pile avant strcpy : Pile après strcpy d'un payload long :
+-------------------+ +----------------------------------+
| ret address | | AAAAAAAA (écrasé) |
+-------------------+ +----------------------------------+
| saved RBP | | AAAAAAAA (écrasé) |
+-------------------+ +----------------------------------+
| buffer[63] | | A |
| ... | | ... |
| buffer[0] | | A |
+-------------------+ +----------------------------------+
L'attaquant choisit son payload pour que les octets qui tombent sur l'adresse de retour pointent vers du code qu'il maîtrise (shellcode injecté dans le buffer ou - aujourd'hui - une chaîne ROP).
3.4 Fonctions C dangereuses
Les coupables historiques. À éviter ou à utiliser avec extrême prudence :
| Fonction dangereuse | Alternative recommandée |
|---|---|
gets() | fgets(buf, sizeof(buf), stdin) (ou supprimée en C11) |
strcpy() | strncpy() ou mieux strlcpy() (BSD), snprintf() |
strcat() | strncat(), strlcat() |
sprintf() | snprintf(buf, sizeof(buf), ...) |
scanf("%s", ...) | scanf("%63s", ...) avec largeur explicite |
memcpy() avec longueur calculée | Vérifier longueur ≤ taille destination |
4. Heap-based buffer overflow
Mécaniquement différent, exploite les métadonnées de l'allocateur (glibc malloc, dlmalloc, jemalloc, ptmalloc2).
4.1 Principe
malloc retourne un bloc avec en amont des métadonnées (taille, pointeurs vers chunk précédent/suivant en cas de free). Un overflow sur un chunk déborde sur les métadonnées du chunk adjacent. Lors d'un free ultérieur, l'allocateur suit les pointeurs corrompus → écriture arbitraire en mémoire.
4.2 Techniques d'exploitation historiques
- Unlink attack (Solar Designer, 2000s) : exploiter
unlink()lors de la coalescence de chunks libres. Mitigée depuis glibc 2.4 par check d'intégrité. - House of Force, House of Spirit, Fastbin Attack, Tcache Poisoning - techniques modernes ciblant les structures de l'allocateur. Activement étudiées en CTF.
Le heap exploitation est réputé plus difficile que le stack overflow : non-déterminisme, contraintes d'agencement, fenêtres de timing. Mais reste exploitable - cf. nombreux CVE récents sur navigateurs et noyaux.
5. Variantes proches
5.1 Off-by-one
Écriture d'un seul octet en trop. Souvent due à confusion < vs <= dans une boucle, ou oubli du \0 final d'une chaîne. Suffit parfois à écraser le LSB d'un pointeur sauvegardé et déclencher une RCE.
5.2 Integer overflow vers buffer overflow
size_t total = nb_items * item_size; // overflow possible
char *buf = malloc(total); // alloue trop peu
memcpy(buf, src, nb_items * item_size); // débordeSi nb_items * item_size déborde l'entier, malloc reçoit une petite valeur, mais le memcpy copie la valeur réelle. CVE-2002-0083 (OpenSSH), nombreux exemples ImageMagick.
5.3 Format string
printf(user_input); // si user_input contient %s, %x, %nPermet lecture (%x, %s) et écriture mémoire arbitraire (%n). Considéré comme une famille proche du buffer overflow car il viole l'isolation mémoire.
5.4 Out-of-bounds read
Symétrique : on lit au-delà du buffer. Conséquence typique : exfiltration de données. Cas célèbre : Heartbleed (CVE-2014-0160, OpenSSL) - un OOB read jusqu'à 64 ko de mémoire serveur exposée par requête.
6. Exemples historiques marquants
| Année | Incident | Type | Impact |
|---|---|---|---|
| 1988 | Morris Worm | Stack BO sur fingerd | ~6000 machines (10 % de l'Internet de l'époque) |
| 2001 | Code Red | Stack BO sur IIS .ida | 359 000 machines en 14h, ~2 G$ de dommages |
| 2003 | SQL Slammer | Stack BO sur SQL Server UDP | 75 000 machines en 10 min |
| 2003 | Blaster | Stack BO RPC DCOM Windows | Millions de machines |
| 2008 | Conficker | Stack BO RPC NetAPI (MS08-067) | 9-15 millions de machines |
| 2014 | Heartbleed | OOB read OpenSSL | Quasi-tout l'Internet TLS |
| 2017 | EternalBlue | Pool overflow SMBv1 (MS17-010) | Bases pour WannaCry, NotPetya |
| 2021 | Log4Shell | (ce n'est pas un BO, lookup JNDI - mais souvent confondu) | Quasi-tout l'écosystème Java |
| 2022-2025 | Nombreux CVE Chrome / WebKit / Linux kernel | Heap BO et UAF | Exploits sur navigateurs et téléphones (Pegasus chains) |
Les BO « basiques » sur services réseau exposés à Internet ont fortement diminué grâce aux mitigations - mais la classe reste vivante dans les navigateurs, noyaux, hyperviseurs, et logiciels embarqués.
7. Exploitation : du crash à la RCE
Trois étapes typiques pour transformer un BO en exécution de code :
7.1 Trouver l'offset
Calibrer le payload pour identifier l'offset exact entre le début du buffer et l'adresse de retour. Outils : cyclic / pattern_create (pwntools, Metasploit), debug avec gdb.
7.2 Choisir la cible du saut
- Avant DEP/NX : injecter du shellcode dans le buffer, faire pointer ret vers le buffer.
- Avec DEP/NX : impossible d'exécuter sur la stack. On utilise ROP (Return-Oriented Programming) - chaînes de petits gadgets (séquences se terminant par
ret) déjà présents dans le binaire ou les bibliothèques chargées. - Avec ASLR : adresses randomisées, il faut d'abord une fuite d'information pour défaire l'aléa.
7.3 Atteindre l'objectif
Spawn shell, lire fichier sensible, élever les privilèges, désactiver une protection. En réseau : binder un shell sur un port, ou faire revenir une connexion (reverse shell).
L'exploitation moderne (2020-2026) est rarement triviale : il faut chaîner BO + info leak + ROP + bypass de canary, parfois avec contraintes de caractères (pas de \0, pas d'espaces). C'est la spécialité pwn des CTF.
8. Protections modernes
Quatre décennies de mitigations empilées. Aucune n'est suffisante seule, leur combinaison rend l'exploitation difficile.
8.1 Stack Canaries (StackGuard, ProPolice, /GS)
Une valeur aléatoire (le canary) est insérée entre les variables locales et l'adresse de retour. Avant ret, le code vérifie que le canary n'a pas changé. Un BO classique l'écrase et déclenche un abort.
// Compilation avec canary :
gcc -fstack-protector-strong sample.cBypass possible : fuite de la valeur du canary, ou écriture qui ne traverse pas le canary (off-by-one ciblé).
8.2 DEP / NX bit (Data Execution Prevention)
Marque les pages de données (stack, heap) comme non exécutables. Un saut vers du shellcode injecté dans la stack provoque une exception. Disponible depuis 2004 (Windows XP SP2, processeurs avec NX bit).
Bypass : ROP / JOP / SROP. C'est précisément ce qui a poussé l'invention du ROP par Hovav Shacham (2007).
8.3 ASLR (Address Space Layout Randomization)
Les adresses de la stack, du heap, des bibliothèques (et du binaire si compilé PIE) sont randomisées à chaque exécution. L'attaquant ne sait plus où sauter sans information préalable.
Limites : entropie réduite sur 32 bits (bruteforce possible en quelques heures), ne protège pas si le binaire n'est pas PIE, contournable par fuite d'info.
# Vérifier ASLR sur Linux :
cat /proc/sys/kernel/randomize_va_space # 2 = full ASLR8.4 RELRO et FORTIFY_SOURCE
- RELRO (Relocation Read-Only) : marque la GOT en lecture seule après résolution dynamique.
- FORTIFY_SOURCE : remplace certaines fonctions par des versions vérifiant la taille à la compilation (
-D_FORTIFY_SOURCE=2 -O2).
8.5 Control Flow Integrity (CFI) et CET
- CFG (Control Flow Guard, Windows) : valide les cibles de sauts indirects.
- Intel CET (Control-flow Enforcement Technology) : Shadow Stack matérielle (sauvegarde séparée de l'adresse de retour) + IBT (Indirect Branch Tracking).
- ARM PAC (Pointer Authentication Codes) : signe les pointeurs avec une clé secrète. Apple Silicon, Android moderne.
- MTE (Memory Tagging Extension) : marque chaque allocation avec un tag, vérifié à l'accès. Disponible sur ARMv8.5+ (Pixel 8+).
8.6 Tableau récapitulatif
| Protection | Année | Défaut sur | Bypass classique |
|---|---|---|---|
| Stack canaries | 1998 | gcc, MSVC | Fuite d'info, brute force |
| DEP / NX | 2004 | Windows XP SP2+, Linux 2.6+ | ROP / JOP |
| ASLR | 2003-2007 | Linux, Windows Vista+ | Info leak, partial overwrite |
| RELRO Full | ~2010 | gcc/ld | Si lazy binding actif |
| CFG | 2014 | Windows 8.1+ | Bypass complexe, recherches actives |
| Intel CET | 2020 | Windows 11, Linux récent | Très récent, peu de bypass publics |
| ARM PAC | 2018 | Apple A12+, Android | PACMAN attack (académique 2022) |
| ARM MTE | 2024 | Pixel 8+, prochains SoC | En cours d'évaluation |
9. Détection et test
9.1 Sanitizers
- AddressSanitizer (ASan) : détection runtime de OOB read/write, UAF, double free. Coût ~2x mémoire et CPU. À activer en dev/test.
gcc -fsanitize=address -g -O1 sample.c -o sample
./sample- MemorySanitizer (MSan) : détecte les lectures de mémoire non initialisée.
- UndefinedBehaviorSanitizer (UBSan) : détecte les comportements indéfinis (overflow d'entier signé, etc.).
- Valgrind / Memcheck : alternative historique, plus lent qu'ASan.
9.2 Fuzzing
- AFL++ : fork de AFL, le standard open source.
- libFuzzer : intégré à Clang, in-process.
- honggfuzz : Google, multi-architecture.
- Jazzer, cargo-fuzz, go-fuzz - fuzzers par langage.
Combiner fuzzer + ASan est la méthode la plus efficace pour découvrir des BO en 2026.
9.3 Analyse statique
- clang-tidy, cppcheck, Coverity, CodeQL : détectent certains patterns (utilisation de
gets, oubli de borne, etc.). Faux positifs et faux négatifs présents.
9.4 Audit manuel
Pour les fonctions critiques, rien ne remplace une revue manuelle ciblée sur les opérations mémoire. Lecture systématique des appels à memcpy, strncpy, read, recv, et de toute arithmétique de taille.
10. Prévention en 2026
10.1 Choisir un langage mémoire-safe
La meilleure prévention reste de ne pas utiliser C/C++ pour le code nouveau quand c'est évitable :
- Rust : ownership et borrow checker éliminent les BO sans cost runtime. Adopté par Linux kernel (modules), Windows kernel (composants), Android (Bluetooth, ULTRA-HDR).
- Go, Java, C#, Kotlin, Swift, Python : runtime géré, BO impossibles dans le code utilisateur (mais possibles dans le runtime ou les bindings natifs).
Le NSA, la CISA, l'ANSSI, la Maison Blanche (rapport ONCD 2024) recommandent explicitement la migration des composants critiques vers les langages mémoire-safe.
10.2 Pour le code C/C++ existant
- Compiler avec toutes les protections :
-fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2 -fPIE -pie -Wl,-z,relro,-z,now. - Utiliser les API safe :
strncpy,snprintf,memcpy_s(C11 Annex K),strlcpy/strlcat(BSD). - Activer warnings stricts :
-Wall -Wextra -Wformat-security -Wconversion. - Audit régulier avec ASan + fuzzing en CI.
- Refactor des fonctions critiques en Rust quand possible (FFI / interop).
10.3 Au runtime
Activer toutes les mitigations OS : ASLR full, DEP, CFG, CET si disponible. Ne jamais les désactiver « pour debug » en production.
11. FAQ
11.1 Quelle différence entre stack overflow et heap overflow ?
Le stack overflow déborde sur la pile (variables locales, adresse de retour). Le heap overflow déborde sur le tas (chunks malloc, métadonnées allocateur). Le premier est historiquement plus simple à exploiter (adresse de retour immédiatement après le buffer), le second exploite les structures de l'allocateur, plus complexe mais toujours exploitable.
11.2 Pourquoi les buffer overflows existent encore en 2026 ?
Trois raisons : (1) le code C/C++ legacy représente des milliards de lignes en production (noyaux, bibliothèques système, navigateurs, embarqué) ; (2) certains domaines (firmware, OS, jeux haute performance) restent dépendants de C/C++ ; (3) même les nouveaux développements C/C++ continuent d'introduire des BO faute de discipline ou de revue.
11.3 ASLR et DEP suffisent-ils à empêcher l'exploitation ?
Non. ASLR + DEP rendent l'exploitation plus coûteuse mais une fuite d'information (info leak) suffit à défaire ASLR, et ROP contourne DEP. Les exploits modernes (Pwn2Own, NSO Pegasus) chaînent typiquement 3-7 vulnérabilités pour atteindre RCE + SBX + LPE.
11.4 Le langage Rust empêche-t-il vraiment tous les buffer overflows ?
Dans le code safe Rust (par défaut), oui : le compilateur refuse les accès hors borne, et les indexations runtime panique au lieu de déborder. Dans les blocs unsafe (FFI, structures de données performantes), les BO redeviennent possibles - d'où l'importance d'auditer ces sections. Mais leur surface est très réduite par rapport au tout-C/C++.
11.5 Comment savoir si un binaire a les protections activées ?
Outil checksec (recommandé) :
checksec --file=./binary
# Affiche : RELRO, Canary, NX, PIE, FortifySur Windows, PESec ou WinChecksec. Pour vérifier ASLR au runtime côté OS Linux : cat /proc/sys/kernel/randomize_va_space (2 = full).
11.6 Puis-je exploiter un buffer overflow pour apprendre ?
Oui, c'est la voie classique - dans un cadre légal. Les CTF (picoCTF, FCSC, HackTheBox, pwn.college) proposent des challenges pwn dédiés. Le tutoriel canonique reste Smashing The Stack For Fun And Profit d'Aleph One (Phrack #49, 1996) - lecture obligatoire pour tout aspirant exploit developer. Pour l'exploitation moderne, pwn.college et LiveOverflow (chaîne YouTube) sont les meilleures ressources gratuites.
Le buffer overflow est la vulnérabilité fondatrice de la sécurité offensive moderne. Comprendre son mécanisme - et celui des protections qui ont émergé en réponse - reste un passage obligé pour qui veut faire du pentest binaire, du reverse engineering, ou de l'AppSec native sérieuse. Sa disparition annoncée à chaque génération technologique ne s'est jamais réalisée : tant que du C/C++ tournera dans les noyaux, navigateurs et firmwares, les BO continueront d'alimenter l'industrie de l'exploitation et de la défense.


