patch the vuln. survive the audit.
Tu hérites d'un code C volontairement vulnérable. Ta mission : détecter, analyser et patcher les failles mémoire sans changer le comportement externe du programme.
Lire, compiler, exécuter. NE PAS modifier le code. Observer seulement.
Lancer Valgrind pour détecter les erreurs mémoire à l'exécution.
Corriger les vulnérabilités sans casser le comportement externe.
Expliquer chaque bug, l'outil utilisé, et la correction appliquée.
Tu es un pentester dans Cyberpunk 2077. On te donne accès au code source d'un système compromis. Tu dois trouver les failles avant que les hackers adverses les exploitent. Chaque bug non patché = une porte d'entrée pour l'ennemi.
Un buffer overflow se produit quand tu écris plus de données que la taille allouée d'un buffer. Tu écrases ce qui est en mémoire juste après.
/* ❌ DANGEREUX — gets() ne limite PAS la taille */
char buffer[64];
gets(buffer); /* Si l'user tape 100 chars → OVERFLOW */
/* ❌ DANGEREUX — scanf sans limite */
char username[32];
scanf("%s", username); /* lit jusqu'au whitespace, sans limite */
/* ✅ SÉCURISÉ — fgets avec taille explicite */
char buffer[64];
fgets(buffer, sizeof(buffer), stdin); /* Lit MAX 63 chars */
/* ✅ SÉCURISÉ — scanf avec limite */
char username[32];
scanf("%31s", username); /* 31 = taille - 1 pour le \0 */
C'est un inventaire de 10 slots dans Minecraft. Si tu forces 20 items dedans, les 10 en trop écrasent l'inventaire du joueur à côté. En mémoire ce joueur c'est peut-être l'adresse de retour de ta fonction — et là c'est le game over.
Un dangling pointer pointe vers de la mémoire déjà libérée avec free(). Y accéder = comportement indéfini.
/* ❌ USE-AFTER-FREE */
char *ptr = malloc(64);
free(ptr);
ptr[0] = 'A'; /* DANGER : mémoire déjà libérée */
/* ❌ DOUBLE FREE */
free(ptr);
free(ptr); /* DANGER : corruption du heap */
/* ✅ BONNE PRATIQUE */
free(ptr);
ptr = NULL; /* Ne pointe plus vers rien de dangereux */
if (ptr != NULL)
ptr[0] = 'A'; /* Check avant d'accéder */
Tu as une référence vers un ennemi dans un jeu. L'ennemi est tué (free). Si tu continues d'utiliser ta référence pour lui infliger des dégâts, tu touches la zone mémoire d'un autre objet qui a pris sa place.
Un memory leak se produit quand tu alloues de la mémoire avec malloc mais que tu oublies de la libérer avec free.
/* ❌ MEMORY LEAK — malloc sans free */
void create_user(void)
{
char *name = malloc(64);
strcpy(name, "Player1");
/* name n'est jamais free() → LEAK */
}
/* ✅ CORRIGÉ */
void create_user(void)
{
char *name = malloc(64);
if (name == NULL) /* TOUJOURS checker malloc ! */
return;
snprintf(name, 64, "Player1");
/* ... utilisation ... */
free(name);
name = NULL;
}
| FONCTION | LIMITE TAILLE ? | STATUT | ALTERNATIVE |
|---|---|---|---|
gets() | ❌ NON | 💀 BANNIE C11 | fgets() |
scanf("%s") | ❌ NON | ⚠️ DANGEREUX | scanf("%31s") |
strcpy() | ❌ NON | ⚠️ RISQUÉ | strncpy() |
strcat() | ❌ NON | ⚠️ RISQUÉ | strncat() |
sprintf() | ❌ NON | ⚠️ RISQUÉ | snprintf() |
fgets() | ✅ OUI | ✅ SÛR | — |
strncpy() | ✅ OUI | ⚠️ Forcer \0 | Ajouter buf[n-1] = '\0' |
snprintf() | ✅ OUI | ✅ SÛR | — |
gets() est tellement dangereuse qu'elle a été retirée du standard C11. Si tu la vois dans un code, c'est un red flag immédiat.Valgrind est un outil d'analyse dynamique qui exécute ton programme et surveille chaque accès mémoire en temps réel. Il détecte les erreurs que le compilateur ne voit pas.
C'est le mode replay avec hitbox visibles dans un jeu de combat. Ton programme tourne normalement, mais Valgrind enregistre chaque coup qui touche une zone interdite. Invisible à l'œil nu, flagrant avec l'outil.
-fsanitize=address ET Valgrind en même temps — ils sont incompatibles.==1234== est le PID du process. Il change à chaque exécution, c'est normal.#include <stdlib.h>
void *malloc(size_t size); /* alloue N octets sur le heap */
void free(void *ptr); /* libère la mémoire allouée */
void *calloc(size_t n, size_t s); /* alloue ET initialise à 0 */
void *realloc(void *ptr, size_t s); /* redimensionne une alloc */
Si malloc échoue il retourne NULL. Déréférencer NULL = crash immédiat.
Oublier = leak. Doubler = corruption du heap (double free).
free(ptr); ptr = NULL; — empêche le use-after-free accidentel.
Variables locales, milieu de tableau, chaînes littérales → jamais de free.
L'ownership = savoir qui est responsable de free() un pointeur. C'est la source principale de bugs quand ce n'est pas clair.
/* La fonction alloue ET retourne → l'appelant devient owner */
char *build_message(const char *name)
{
char *result = malloc(128);
if (result == NULL)
return (NULL);
snprintf(result, 128, "Welcome, %s!", name);
return (result); /* L'APPELANT devient responsable du free */
}
int main(void)
{
char *msg = build_message("Link");
if (msg == NULL)
return (1);
printf("%s\n", msg);
free(msg); /* main() est l'owner → il free */
msg = NULL;
return (0);
}
Dans un MMO, si tu craftes une épée et tu la donnes à un autre joueur, c'est lui qui doit la gérer. La fonction qui retourne un malloc transfère la responsabilité du free à l'appelant. Si personne ne s'en souvient → memory leak.
Ne pas lire de mémoire non initialisée
Toute variable doit être initialisée avant lecture.
int x;
printf("%d", x); /* ❌ */
int x = 0;
printf("%d", x); /* ✅ */
Ne pas déréférencer un pointeur NULL
Toujours vérifier après malloc.
char *p = malloc(64);
p[0] = 'A'; /* ❌ */
if (p != NULL)
p[0] = 'A'; /* ✅ */
Pas de comportement non spécifié
Le UB peut "fonctionner" chez toi et crasher sur le serveur Holberton. Ne jamais en dépendre.
Pas d'accès post-free
Après free(ptr), le pointeur est invalide. Toujours ptr = NULL derrière.
Nom de la vulnérabilité + fichier + numéro de ligne exacte
Quelle hypothèse était violée ? Quelle fonction était dangereuse ?
Inspection manuelle ? Valgrind ? Copie du message d'erreur exact.
Code avant / après. Quelle règle CERT ? Comportement externe inchangé ?
gets() | → | fgets(b, n, stdin) |
scanf("%s") | → | scanf("%31s") |
strcpy() | → | strncpy(d, s, n-1) |
strcat() | → | strncat(d, s, n) |
sprintf() | → | snprintf(b, n, ...) |