Skip to content

Latest commit

 

History

History
657 lines (445 loc) · 25.5 KB

File metadata and controls

657 lines (445 loc) · 25.5 KB

🔝 Retour au Sommaire

29.4.1 — AddressSanitizer (-fsanitize=address)

Section 29.4 : Sanitizers · Chapitre 29


Introduction

AddressSanitizer — ASan — est le sanitizer le plus important de votre arsenal. Il détecte les erreurs d'accès mémoire : les débordements de buffer, les accès à de la mémoire libérée, les doubles libérations, et les fuites mémoire. Ce sont les bugs qui causent des crashes inexplicables, des corruptions de données silencieuses, et des vulnérabilités de sécurité critiques.

Sans ASan, un buffer overflow de quelques octets peut passer inaperçu pendant des années. Le programme fonctionne correctement — par chance — parce que les octets écrasés ne sont pas utilisés immédiatement. Puis un jour, une modification anodine du code change l'agencement de la mémoire, et le même overflow déclenche un crash impossible à reproduire. Avec ASan, l'overflow est détecté à la première exécution, à la ligne exacte où il se produit, avec un rapport qui montre quelle allocation est concernée et de combien l'accès dépasse.

ASan a été développé par Google et intégré à Clang en 2011, puis à GCC en 2013. Il est utilisé en continu sur les bases de code de Chrome, Android, le noyau Linux, et des centaines de projets open source. Il a permis de détecter des dizaines de milliers de bugs avant qu'ils n'atteignent la production.


Activation

g++ -fsanitize=address -g -O1 -fno-omit-frame-pointer -o prog main.cpp

Les flags, un par un :

  • -fsanitize=address — active l'instrumentation ASan. Le compilateur insère des vérifications autour de chaque accès mémoire et remplace l'allocateur standard par l'allocateur ASan.
  • -g — génère les informations de débogage pour que les rapports contiennent les noms de fichiers et numéros de lignes.
  • -O1 — niveau d'optimisation recommandé. -O0 fonctionne mais produit un binaire plus lent. -O2 fonctionne aussi mais peut rendre certains rapports moins lisibles.
  • -fno-omit-frame-pointer — préserve le frame pointer pour des backtraces complètes et rapides.

Le flag doit être passé à la fois au compilateur et au linker. Si vous compilez et linkez en une seule commande (g++ -fsanitize=address ... -o prog main.cpp), c'est automatique. Si vous séparez compilation et linkage, n'oubliez pas le flag au linkage :

g++ -fsanitize=address -g -O1 -fno-omit-frame-pointer -c main.cpp -o main.o  
g++ -fsanitize=address main.o -o prog    # -fsanitize aussi ici  

Combinaison avec UBSan

ASan et UBSan sont compatibles et se complètent. La combinaison est recommandée comme configuration par défaut :

g++ -fsanitize=address,undefined -g -O1 -fno-omit-frame-pointer -o prog main.cpp

Un seul binaire, deux sanitizers actifs. Le surcoût additionnel d'UBSan au-dessus d'ASan est négligeable.


Comment ASan fonctionne

Comprendre le mécanisme interne d'ASan n'est pas nécessaire pour l'utiliser, mais ça aide à comprendre ses rapports et ses limites.

Shadow memory

ASan maintient une shadow memory — une copie compacte de l'espace d'adressage qui enregistre, pour chaque groupe de 8 octets de mémoire applicative, si ces octets sont accessibles ou non. Chaque octet de shadow memory encode l'état d'un groupe de 8 octets :

  • 0 : les 8 octets sont accessibles
  • 1-7 : seuls les N premiers octets sont accessibles (fin d'allocation non alignée)
  • Valeur négative : les 8 octets sont inaccessibles (zone interdite)

Avant chaque accès mémoire, le code instrumenté consulte la shadow memory. Si l'octet correspondant indique que la zone est inaccessible, ASan signale l'erreur.

Zones rouges (redzones)

ASan entoure chaque allocation de redzones — des zones de mémoire marquées comme inaccessibles dans la shadow memory :

┌──────────┬─────────────────────┬──────────┐
│ Redzone  │  Allocation réelle  │ Redzone  │
│ (poison) │   (accessible)      │ (poison) │
└──────────┴─────────────────────┴──────────┘

Si un accès touche une redzone, c'est un overflow (ou underflow). La taille par défaut des redzones est de 16 octets, mais elle peut être augmentée pour détecter des overflows plus grands :

ASAN_OPTIONS="redzone=128" ./prog    # Redzones de 128 octets

Quarantaine

Quand un bloc de mémoire est libéré (delete, free), ASan ne le rend pas immédiatement au système. Il le place en quarantaine — la mémoire est marquée comme inaccessible dans la shadow memory, mais elle n'est pas réutilisée. Si le programme accède à cette mémoire après la libération, ASan détecte le use-after-free.

La quarantaine a une taille limitée (256 Mo par défaut). Quand elle est pleine, les blocs les plus anciens sont recyclés. Cela signifie qu'un use-after-free qui accède à de la mémoire libérée très longtemps auparavant peut échapper à la détection.


Catégorie 1 : Heap buffer overflow

L'accès au-delà des limites d'une allocation sur le heap — le bug mémoire le plus courant.

Exemple

// heap_overflow.cpp
#include <cstdio>

int main() {
    int* array = new int[10];
    
    for (int i = 0; i <= 10; ++i) {   // Bug : <= au lieu de <
        array[i] = i * 100;
    }
    
    std::printf("Dernier : %d\n", array[9]);
    delete[] array;
    return 0;
}

La boucle itère de 0 à 10 inclus, mais le tableau n'a que 10 éléments (indices 0 à 9). L'écriture à array[10] dépasse de 4 octets. Sans ASan, ce programme peut fonctionner parfaitement — les 4 octets écrasés font partie de la redzone du gestionnaire de mémoire ou d'une allocation adjacente.

Compilation et exécution

g++ -fsanitize=address -g -O1 -fno-omit-frame-pointer -o heap_overflow heap_overflow.cpp
./heap_overflow

Rapport ASan

=================================================================
==54321==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x604000000038 
at pc 0x0000004f4b27 bp 0x7ffc8a9b1230 sp 0x7ffc8a9b1228  
WRITE of size 4 at 0x604000000038 thread T0  
    #0 0x4f4b26 in main heap_overflow.cpp:7
    #1 0x7f8a12345890 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58
    #2 0x7f8a12345949 in __libc_start_main csu/../csu/libc-start.c:360
    #3 0x4f4a04 in _start (heap_overflow+0x4f4a04)

0x604000000038 is located 0 bytes after 40-byte region [0x604000000010,0x604000000038)
allocated by thread T0 here:
    #0 0x4e8c30 in operator new[](unsigned long) (heap_overflow+0x4e8c30)
    #1 0x4f4a90 in main heap_overflow.cpp:5
    #2 0x7f8a12345890 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58

SUMMARY: AddressSanitizer: heap-buffer-overflow heap_overflow.cpp:7 in main  
Shadow bytes around the buggy address:  
[...]
==54321==ABORTING

Lecture du rapport

Ligne d'erreur : heap-buffer-overflow — le type d'erreur est clair.

WRITE of size 4 at 0x604000000038 thread T0 — une écriture de 4 octets (un int) dans le thread principal.

Pile d'appels de l'accès fautif : heap_overflow.cpp:7 — la ligne de la boucle array[i] = i * 100 quand i == 10.

0 bytes after 40-byte region [0x604000000010,0x604000000038) — l'accès se situe immédiatement après un bloc de 40 octets (10 entiers × 4 octets). Le dépassement est de 0 octets après la fin du bloc — c'est-à-dire que l'écriture commence exactement au premier octet hors limites.

Pile d'appels de l'allocation : heap_overflow.cpp:5 — le new int[10]. Vous voyez à la fois où le bug se manifeste et quelle allocation est insuffisante.


Catégorie 2 : Stack buffer overflow

L'accès au-delà d'un tableau alloué sur la pile. Particulièrement dangereux car il peut écraser l'adresse de retour ou d'autres variables locales.

Exemple

// stack_overflow.cpp
#include <cstring>
#include <cstdio>

void process(const char* input) {
    char buffer[16];
    std::strcpy(buffer, input);    // Bug : pas de vérification de taille
    std::printf("Traité : %s\n", buffer);
}

int main() {
    process("cette-chaine-est-beaucoup-trop-longue-pour-le-buffer");
    return 0;
}

Compilation et exécution

g++ -fsanitize=address -g -O1 -fno-omit-frame-pointer -o stack_overflow stack_overflow.cpp
./stack_overflow

💡 Note Ubuntu : sur les distributions récentes, la protection _FORTIFY_SOURCE (activée par défaut) peut intercepter le strcpy avant ASan. Si vous obtenez *** buffer overflow detected *** au lieu d'un rapport ASan, ajoutez -D_FORTIFY_SOURCE=0 à la compilation pour désactiver cette protection et laisser ASan faire son travail.

Rapport ASan (extrait)

==54322==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc8a9b1240
at pc 0x0000004f4c10 bp 0x7ffc8a9b1210 sp 0x7ffc8a9b0dd0  
WRITE of size 53 at 0x7ffc8a9b1240 thread T0  
    #0 0x4f4c0f in __interceptor_strcpy (stack_overflow+0x4f4c0f)
    #1 0x4f5120 in process(char const*) stack_overflow.cpp:6
    #2 0x4f5190 in main stack_overflow.cpp:11

Address 0x7ffc8a9b1240 is located in stack of thread T0 at offset 48 in frame
    #0 0x4f50a0 in process(char const*) stack_overflow.cpp:4

  This frame has 1 object(s):
    [32, 48) 'buffer' (line 5)    <== Memory access at offset 48 overflows this variable

Le rapport est plus détaillé pour les overflows de stack : ASan identifie la variable locale exacte (buffer, ligne 5) et indique que l'accès à l'offset 48 dépasse la fin de cette variable (qui occupe les offsets 32 à 48 dans le frame).

C'est le type de bug que Valgrind ne détecte pas — Valgrind ne surveille pas les accès à la pile. ASan est le seul outil courant capable de détecter les stack buffer overflows.


Catégorie 3 : Global buffer overflow

L'accès au-delà d'un tableau global ou statique.

Exemple

// global_overflow.cpp
#include <cstdio>

int lookup_table[8] = {10, 20, 30, 40, 50, 60, 70, 80};

int get_value(int index) {
    return lookup_table[index];    // Pas de vérification de bornes
}

int main() {
    std::printf("Valeur : %d\n", get_value(12));   // index hors limites
    return 0;
}

Rapport ASan (extrait)

==54323==ERROR: AddressSanitizer: global-buffer-overflow on address 0x000000601090
at pc 0x0000004f4a60 bp 0x7ffc8a9b1250 sp 0x7ffc8a9b1248  
READ of size 4 at 0x000000601090 thread T0  
    #0 0x4f4a5f in get_value(int) global_overflow.cpp:6
    #1 0x4f4ab0 in main global_overflow.cpp:10

0x000000601090 is located 16 bytes after global variable 'lookup_table'
defined in 'global_overflow.cpp:3:5' (0x601060) of size 32

ASan identifie la variable globale (lookup_table, définie à la ligne 3, taille 32 octets) et le dépassement (16 octets après la fin, soit 4 éléments au-delà).


Catégorie 4 : Use-after-free

L'accès à de la mémoire qui a été libérée. L'un des bugs les plus insidieux en C++ : la mémoire libérée peut être réallouée à un autre objet, et l'accès via l'ancien pointeur lit ou écrit des données qui appartiennent maintenant à quelqu'un d'autre.

Exemple

// use_after_free.cpp
#include <cstdio>
#include <string>

int main() {
    std::string* msg = new std::string("hello world");
    std::string& ref = *msg;
    
    delete msg;
    
    // Bug : ref pointe vers de la mémoire libérée
    std::printf("Message : %s\n", ref.c_str());
    
    return 0;
}

Rapport ASan (extrait)

==54324==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010
at pc 0x0000004f5a40 bp 0x7ffc8a9b1230 sp 0x7ffc8a9b1228  
READ of size 8 at 0x602000000010 thread T0  
    #0 0x4f5a3f in main use_after_free.cpp:11
    #1 0x7f8a12345890 in __libc_start_call_main (...)

0x602000000010 is located 0 bytes inside of 32-byte region [0x602000000010,0x602000000030)
freed by thread T0 here:
    #0 0x4e9a20 in operator delete(void*) (use_after_free+0x4e9a20)
    #1 0x4f5980 in main use_after_free.cpp:8

previously allocated by thread T0 here:
    #0 0x4e8c30 in operator new(unsigned long) (use_after_free+0x4e8c30)
    #1 0x4f5910 in main use_after_free.cpp:5

SUMMARY: AddressSanitizer: heap-use-after-free use_after_free.cpp:11 in main

Le rapport contient trois piles d'appels :

  1. L'accès fautif : use_after_free.cpp:11 — la ligne qui lit via la référence dangereuse.
  2. La libération : use_after_free.cpp:8 — le delete msg.
  3. L'allocation originale : use_after_free.cpp:5 — le new std::string(...).

Vous avez les trois pièces du puzzle : où la mémoire a été créée, où elle a été détruite, et où on a essayé de l'utiliser après sa destruction. Ce diagnostic complet est la raison pour laquelle ASan est si efficace — avec un seul rapport, vous comprenez le cycle de vie complet du bug.


Catégorie 5 : Double-free

Libérer deux fois la même allocation.

Exemple

// double_free.cpp
#include <cstdio>

void cleanup(int* data) {
    delete[] data;
}

int main() {
    int* values = new int[5]{1, 2, 3, 4, 5};
    
    cleanup(values);
    
    // ... du code intermédiaire ...
    
    delete[] values;    // Bug : déjà libéré par cleanup()
    
    return 0;
}

Rapport ASan (extrait)

==54325==ERROR: AddressSanitizer: attempting double-free on 0x603000000010 in thread T0:
    #0 0x4e9b40 in operator delete[](void*) (double_free+0x4e9b40)
    #1 0x4f4b80 in main double_free.cpp:14

0x603000000010 is located 0 bytes inside of 20-byte region [0x603000000010,0x603000000024)
freed by thread T0 here:
    #0 0x4e9b40 in operator delete[](void*) (double_free+0x4e9b40)
    #1 0x4f4a30 in cleanup(int*) double_free.cpp:4
    #2 0x4f4b50 in main double_free.cpp:10

previously allocated by thread T0 here:
    #0 0x4e8c30 in operator new[](unsigned long) (double_free+0x4e8c30)
    #1 0x4f4af0 in main double_free.cpp:8

Même structure à trois piles : le double-free (ligne 14), la première libération (ligne 4 via cleanup), et l'allocation (ligne 8). La solution est évidente : soit cleanup est responsable de la libération, soit main — pas les deux. En C++ moderne, ce bug est éliminé par les smart pointers (chapitre 9).


Catégorie 6 : Memory leaks

ASan inclut un détecteur de fuites mémoire — LeakSanitizer (LSan) — qui s'exécute à la fin du programme et signale toute allocation non libérée.

Exemple

// memory_leak.cpp
#include <cstdio>
#include <vector>

void process_data() {
    auto* buffer = new std::vector<int>(1000);
    buffer->push_back(42);
    // Oubli du delete — le pointeur sort de la portée
}

int main() {
    for (int i = 0; i < 100; ++i) {
        process_data();    // 100 allocations jamais libérées
    }
    std::printf("Terminé\n");
    return 0;
}

Rapport ASan (extrait)

Terminé

=================================================================
==54326==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 2400 byte(s) in 100 object(s) allocated from:
    #0 0x4e8c30 in operator new(unsigned long) (memory_leak+0x4e8c30)
    #1 0x4f4b10 in process_data() memory_leak.cpp:5
    #2 0x4f4c20 in main memory_leak.cpp:11

Indirect leak of 401600 byte(s) in 100 object(s) allocated from:
    #0 0x4e8c30 in operator new(unsigned long) (memory_leak+0x4e8c30)
    #1 0x7f8a1237ab50 in std::vector<int>::_M_default_append(unsigned long) (...)
    #2 0x4f4b10 in process_data() memory_leak.cpp:5

SUMMARY: AddressSanitizer: 404000 byte(s) leaked in 200 allocation(s).

LSan distingue deux catégories de fuites :

  • Direct leak : l'allocation dont le pointeur a été perdu (le new std::vector<int>(1000) — 24 octets par vector × 100 = 2400 octets).
  • Indirect leak : les allocations internes faites par l'objet fuité (le buffer interne du vector — ~4000 octets × 100 = 401600 octets).

Cette distinction est précieuse : les fuites directes sont les causes racine à corriger. Les fuites indirectes disparaissent automatiquement quand les fuites directes sont corrigées.

Contrôler la détection de fuites

# Désactiver la détection de fuites (garder seulement ASan)
ASAN_OPTIONS="detect_leaks=0" ./prog

# Activer uniquement la détection de fuites, sans ASan complet
g++ -fsanitize=leak -g -o prog main.cpp

Le flag -fsanitize=leak active LSan seul, sans l'instrumentation mémoire complète d'ASan. Le binaire est beaucoup plus rapide, mais ne détecte que les fuites — pas les overflows ou les use-after-free.


Catégorie 7 : Use-after-return

L'accès à une variable locale après le retour de sa fonction. Le cadre de pile a été détruit, mais un pointeur vers la variable survit.

Exemple

// use_after_return.cpp
#include <cstdio>

int* get_value() {
    int local = 42;
    return &local;    // Retourne l'adresse d'une variable locale
}

int main() {
    int* ptr = get_value();
    std::printf("Valeur : %d\n", *ptr);    // Bug : ptr pointe vers un frame détruit
    return 0;
}

Détection

La détection du use-after-return nécessite une option d'exécution spécifique — elle n'est pas activée par défaut car elle a un coût mémoire supplémentaire :

ASAN_OPTIONS="detect_stack_use_after_return=1" ./use_after_return

Avec GCC 15+ et Clang 19+, cette option est activée par défaut. Vérifiez le comportement de votre version.


Use-after-scope

Une variante plus subtile : accéder à une variable après la fin de sa portée (son bloc {}), mais avant le retour de la fonction.

Exemple

// use_after_scope.cpp
#include <cstdio>

int main() {
    int* ptr;
    {
        int scoped_var = 42;
        ptr = &scoped_var;
    }    // scoped_var est détruit ici
    
    std::printf("Valeur : %d\n", *ptr);    // Bug : scoped_var n'existe plus
    return 0;
}

Détection

g++ -fsanitize=address -g -O1 -fno-omit-frame-pointer \
    -fsanitize-address-use-after-scope -o use_after_scope use_after_scope.cpp
./use_after_scope

Le flag -fsanitize-address-use-after-scope est nécessaire pour cette détection. Il instrumente les sorties de portée pour empoisonner la mémoire des variables locales dès qu'elles sont hors scope.


Options d'exécution essentielles

Les options ASan sont passées via la variable d'environnement ASAN_OPTIONS. Voici les plus utiles :

Continuer après la première erreur

Par défaut, ASan arrête le programme à la première erreur. Pour détecter plusieurs erreurs en une seule exécution :

ASAN_OPTIONS="halt_on_error=0" ./prog

Utile quand vous exécutez une suite de tests et voulez voir tous les problèmes d'un coup. Attention cependant : après une erreur mémoire, l'état du programme est potentiellement corrompu. Les erreurs suivantes peuvent être des conséquences de la première, pas des bugs indépendants.

Rediriger les rapports vers des fichiers

ASAN_OPTIONS="log_path=/tmp/asan_report" ./prog
# Génère /tmp/asan_report.54321 (PID ajouté automatiquement)

Indispensable en CI où vous voulez archiver les rapports comme artefacts.

Augmenter la profondeur des backtraces

ASAN_OPTIONS="malloc_context_size=20" ./prog

Par défaut, ASan capture environ 30 frames dans les piles d'appels. Si votre programme a des piles d'appels profondes (frameworks, callbacks imbriqués), augmentez cette valeur.

Agrandir la quarantaine

ASAN_OPTIONS="quarantine_size_mb=512" ./prog

Augmente la taille de la quarantaine pour détecter des use-after-free sur des allocations libérées plus tôt. Utile pour les programmes à longue durée de vie avec beaucoup d'allocations.

Résumé des options courantes

# Configuration recommandée pour le CI
ASAN_OPTIONS="halt_on_error=0:detect_leaks=1:log_path=/tmp/asan:print_stats=1"

# Configuration recommandée pour le développement interactif
ASAN_OPTIONS="halt_on_error=1:detect_leaks=1:detect_stack_use_after_return=1"

Surcoût de performance

ASan a un coût. Il est important de le connaître pour savoir où et quand l'activer :

Métrique Surcoût typique
Temps d'exécution ~2x plus lent
Mémoire ~3x plus de mémoire (shadow memory + quarantaine + redzones)
Taille du binaire ~2x plus gros
Temps de compilation ~1.5x plus long

Un facteur 2x de ralentissement est tout à fait acceptable pour les tests automatisés. C'est incomparablement mieux que Valgrind (20-50x) et ça permet d'exécuter les sanitizers dans le pipeline CI sans exploser les temps de build.

Pour les tests de performance (benchmarks), désactivez les sanitizers — le surcoût fausse les mesures.


ASan et GDB : combiner les deux

ASan et GDB fonctionnent très bien ensemble. Quand ASan détecte une erreur, vous pouvez configurer le programme pour qu'il s'arrête dans GDB au lieu de terminer :

# Option 1 : ASan appelle abort(), GDB intercepte le signal
ASAN_OPTIONS="abort_on_error=1" gdb ./prog
(gdb) run
# ASan détecte l'erreur et appelle abort()
# GDB intercepte SIGABRT et vous rend la main
(gdb) backtrace
(gdb) info locals
# Option 2 : poser un breakpoint sur le handler d'erreur ASan
gdb ./prog
(gdb) break __asan::ReportGenericError
(gdb) run
# GDB s'arrête quand ASan détecte une erreur, AVANT l'affichage du rapport
(gdb) backtrace

La deuxième option est particulièrement puissante : le programme est en pause à l'instant exact de l'accès fautif, avec toutes les variables accessibles. Vous pouvez inspecter le pointeur fautif, le contenu de la mémoire autour, et l'état complet du programme — comme si vous aviez posé un breakpoint sur la ligne problématique.


Faux positifs et suppressions

ASan produit très peu de faux positifs — c'est l'un de ses points forts. Mais il peut signaler des erreurs dans des bibliothèques tierces que vous ne pouvez pas corriger. Dans ce cas, utilisez un fichier de suppressions :

# asan_suppressions.txt
# Format : type_erreur:pattern_fonction_ou_fichier
leak:third_party/legacy_lib.cpp  
leak:libcrypto.so  
LSAN_OPTIONS="suppressions=asan_suppressions.txt" ./prog

Les suppressions fonctionnent principalement pour les fuites mémoire (via LSan). Pour les erreurs d'accès mémoire (overflow, use-after-free), il n'y a pas de mécanisme de suppression — ces erreurs sont trop graves pour être ignorées.

Si ASan signale un faux positif dans votre propre code (ce qui est extrêmement rare), vous pouvez annoter la fonction :

// Désactiver ASan pour cette fonction spécifique
__attribute__((no_sanitize("address")))
void legacy_function() {
    // Code qui fait des choses que ASan ne comprend pas
    // (allocateur personnalisé, mmap direct, etc.)
}

Utilisez cette annotation avec parcimonie et documentez toujours la raison.


Limitations connues

Ce qu'ASan ne détecte pas

  • Lectures de mémoire non initialisée. C'est le domaine de MemorySanitizer (section 29.4.4), pas d'ASan.
  • Overflows à l'intérieur d'une structure. Si un accès dépasse un champ de structure mais reste dans les limites de la structure elle-même, ASan ne le détecte pas — il n'y a pas de redzone entre les champs.
  • Overflows exacts sur des allocations alignées. Si un overflow tombe exactement dans le padding d'alignement entre deux allocations, il peut échapper à la détection. En pratique, c'est rare grâce à la taille des redzones.
  • Data races. ASan ne détecte pas les accès concurrents non synchronisés. C'est le domaine de ThreadSanitizer (section 29.4.3).

Impact sur l'allocateur

ASan remplace l'allocateur standard (malloc/free, new/delete) par son propre allocateur. Cela signifie que les bugs qui dépendent du comportement spécifique de l'allocateur système (fragmentation, ordre de réutilisation) peuvent se manifester différemment sous ASan. Le bug existe toujours, mais son symptôme peut changer.

Couverture des bibliothèques tierces

ASan instrumente le code compilé avec -fsanitize=address. Les bibliothèques précompilées (installées via apt, par exemple) ne sont pas instrumentées. Un buffer overflow dans une bibliothèque non instrumentée peut échapper à la détection.

Pour une couverture maximale, compilez toutes les dépendances avec ASan. En pratique, c'est souvent trop coûteux — concentrez-vous sur votre code et les bibliothèques critiques.


Checklist ASan

À intégrer dans votre workflow de développement :

  • Compilation quotidienne avec ASan + UBSan — en développement local, compilez par défaut avec -fsanitize=address,undefined
  • Tests unitaires sous ASan — chaque exécution de ctest ou make test doit utiliser un build ASan
  • CI avec ASan — un job CI dédié qui compile et exécute les tests avec ASan
  • Zéro erreur ASan tolérée — toute erreur ASan est un bug à corriger, jamais un "faux positif à ignorer"
  • Log des rapports en CIASAN_OPTIONS="log_path=..." pour archiver les rapports
  • Combinaison avec GDB quand le rapport ASan ne suffit pas — abort_on_error=1 + GDB pour inspection interactive

À retenir : ASan détecte les buffer overflows (heap, stack, global), les use-after-free, les double-free, et les fuites mémoire, avec un rapport qui inclut la pile d'appels de l'accès fautif, de l'allocation, et de la libération. Le surcoût de ~2x est négligeable comparé au temps que vous perdriez à diagnostiquer ces bugs manuellement. Activez-le par défaut. Toujours.

⏭️ UndefinedBehaviorSanitizer (-fsanitize=undefined)