Les race conditions kernel sont parmi les vulnérabilités les plus complexes et les plus puissantes en exploitation système. Contrairement aux buffer overflows qui corrompent la mémoire par débordement, les race conditions exploitent les fenêtres temporelles entre deux opérations qui devraient être atomiques mais ne le sont pas. Les deux catégories principales — Double-Fetch (le kernel lit deux fois une valeur en mémoire partagée, l'attaquant la modifie entre les deux lectures) et TOCTOU (Time-of-Check-Time-of-Use, le kernel vérifie une condition puis agit dessus, l'attaquant modifie l'état entre la vérification et l'utilisation) — permettent l'escalade de privilèges, le contournement des vérifications de sécurité et la corruption de données kernel. Ce guide technique couvre les mécanismes d'exploitation, les techniques de synchronisation (race winning), les CVE historiques (Dirty COW, Dirty Pipe), les outils de détection et les primitifs avancés d'exploitation de race conditions dans le noyau Linux et Windows.
En bref
- Double-Fetch : le kernel lit deux fois une valeur userspace — l'attaquant la modifie entre les lectures
- TOCTOU : Time-of-Check-Time-of-Use — l'état change entre la vérification et l'utilisation
- Exploitation : timing manipulation, multi-threading, CPU pinning et userfaultfd
- CVE historiques : Dirty COW (CVE-2016-5195), Dirty Pipe (CVE-2022-0847), io_uring races
- Détection : KCSAN, Thread Sanitizer, analyse statique et fuzzing concurrentiel (Syzkaller)
Double-Fetch : Mécanisme et Exploitation
Un double-fetch se produit quand le kernel accède deux fois au même emplacement en mémoire userspace : une première fois pour vérifier (validation de taille, type, permissions) et une seconde fois pour utiliser la valeur. L'attaquant utilise un second thread pour modifier la valeur entre les deux accès :
// KERNEL CODE VULNÉRABLE (simplifié)
// Le kernel lit la taille depuis l'espace utilisateur DEUX FOIS
struct user_request __user *req = (void *)arg;
// 1ère lecture : vérification de la taille
if (copy_from_user(&size, &req->size, sizeof(size)))
return -EFAULT;
if (size > MAX_SIZE)
return -EINVAL; // Vérification OK
// ⚠️ FENÊTRE DE RACE — l'attaquant modifie req->size ici
// 2ème lecture : utilisation de la taille (implicite dans copy_from_user)
buf = kmalloc(size, GFP_KERNEL); // Alloue avec la taille vérifiée
if (copy_from_user(buf, req->data, req->size)) // ❌ Re-lit req->size !
// req->size peut maintenant être > MAX_SIZE → buffer overflow kernel
return -EFAULT;
TOCTOU : Time-of-Check-Time-of-Use
Les vulnérabilités TOCTOU surviennent quand le kernel vérifie une condition (check) puis agit dessus (use), mais l'état peut changer entre les deux opérations. L'exemple classique est la vérification d'accès à un fichier :
// TOCTOU classique sur le filesystem
// Thread 1 (programme setuid) :
if (access("/tmp/config", R_OK) == 0) {
// CHECK : l'utilisateur a le droit de lire /tmp/config
// ⚠️ FENÊTRE DE RACE
// Thread 2 : symlink("/etc/shadow", "/tmp/config")
fd = open("/tmp/config", O_RDONLY);
// USE : ouvre le fichier — mais c'est maintenant /etc/shadow !
read(fd, buf, sizeof(buf)); // Lit /etc/shadow avec les privilèges root
}
Dirty COW (CVE-2016-5195) : La Race Condition Légendaire
Dirty COW est la race condition kernel la plus célèbre : elle exploite une race dans le mécanisme de Copy-on-Write (COW) du gestionnaire de mémoire Linux. Quand un processus écrit dans une page COW, le kernel doit copier la page avant l'écriture. Dirty COW exploite une race entre le thread de fault handling et le thread d'écriture pour modifier directement la page originale (partagée) sans la copier — permettant l'écriture dans des fichiers en lecture seule, y compris /etc/passwd et les binaires setuid.
Dirty Pipe (CVE-2022-0847) : Race sur les Pipes
Dirty Pipe exploite un bug dans la gestion des pipes Linux : le flag PIPE_BUF_FLAG_CAN_MERGE n'est pas correctement initialisé quand un pipe buffer est recyclé depuis le page cache. L'attaquant peut écrire dans n'importe quel fichier lisible (même en lecture seule) via un pipe, sans aucune race condition temporelle — le bug est déterministe. Dirty Pipe est considéré comme encore plus puissant que Dirty COW car il est fiable à 100%.
Techniques de Race Winning
Gagner une race condition kernel nécessite un contrôle précis du timing entre les threads :
- CPU Pinning : utiliser
sched_setaffinity()pour forcer les threads attaquant et victime sur des cœurs CPU spécifiques, réduisant la variabilité de scheduling - userfaultfd : intercepter les page faults userspace pour bloquer le kernel au milieu d'un
copy_from_user()— agrandit la fenêtre de race à l'infini - FUSE (Filesystem in Userspace) : monter un filesystem FUSE et bloquer les opérations de lecture pour contrôler le timing des accès fichier du kernel
- io_uring : les opérations asynchrones io_uring créent naturellement des fenêtres de race dans le kernel
- Flooding / contention : saturer les caches, les locks ou les schedulers pour augmenter la latence entre les opérations kernel
userfaultfd : L'Arme Ultime pour les Races Kernel
// userfaultfd — contrôler le timing d'un copy_from_user kernel
#include <linux/userfaultfd.h>
#include <sys/ioctl.h>
// 1. Créer un userfaultfd
int uffd = syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK);
// 2. Mapper une page et l'enregistrer avec userfaultfd
void *page = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
struct uffdio_register reg = {
.range = {.start = (unsigned long)page, .len = PAGE_SIZE},
.mode = UFFDIO_REGISTER_MODE_MISSING
};
ioctl(uffd, UFFDIO_REGISTER, ®);
// 3. Passer 'page' au syscall vulnérable
// Quand le kernel fait copy_from_user(page) → PAGE FAULT
// Le kernel se bloque et attend notre handler userfaultfd
// 4. Dans le handler (thread séparé) :
// - Modifier l'état kernel (autre thread/syscall)
// - Puis résoudre le fault en fournissant les données
struct uffdio_copy copy = {
.dst = (unsigned long)page,
.src = (unsigned long)malicious_data,
.len = PAGE_SIZE,
};
ioctl(uffd, UFFDIO_COPY, ©);
// → Le kernel reprend avec nos données malveillantes
Détection et Prévention
- KCSAN (Kernel Concurrency SANitizer) : détecteur de data races dynamique intégré au kernel Linux depuis 5.8
- Syzkaller : fuzzer kernel de Google qui génère automatiquement des séquences de syscalls concurrentes pour déclencher des races
- copy_from_user_once() : pattern kernel recommandé — copier les données userspace une seule fois dans un buffer kernel, puis utiliser uniquement le buffer kernel
- Restriction userfaultfd : Linux 5.11+ restreint userfaultfd aux processus avec CAP_SYS_PTRACE (sysctl vm.unprivileged_userfaultfd=0)
À retenir
- Les race conditions kernel exploitent les fenêtres temporelles entre opérations non-atomiques
- Double-fetch : le kernel lit 2x la mémoire userspace — l'attaquant modifie entre les lectures
- TOCTOU : l'état change entre la vérification (check) et l'utilisation (use) — bypass de permissions
- userfaultfd bloque le kernel au milieu de copy_from_user() — agrandit la fenêtre de race à l'infini
- Dirty COW et Dirty Pipe sont les race conditions kernel les plus célèbres — LPE fiable sur Linux
- KCSAN et Syzkaller détectent automatiquement les data races dans le kernel Linux
FAQ — Questions Fréquentes
Les race conditions kernel sont-elles exploitables de manière fiable ?
Historiquement non — les races étaient considérées comme non-fiables car dépendantes du timing CPU. Mais les techniques modernes (userfaultfd, FUSE, CPU pinning) permettent de contrôler le timing avec une précision suffisante pour une exploitation fiable. Dirty COW avait un taux de succès >90% avec la bonne configuration de threads. Dirty Pipe est déterministe (pas réellement une race condition temporelle).
Comment trouver des race conditions dans le kernel Linux ?
Les approches principales : Syzkaller (fuzzer kernel concurrentiel — le plus efficace), KCSAN (détection dynamique de data races pendant l'exécution), audit de code (rechercher les double-fetch dans copy_from_user et les TOCTOU dans les vérifications de permissions), et analyse statique (outils comme Coccinelle avec des patterns de détection de races).
userfaultfd est-il toujours disponible pour les attaquants ?
Sur les kernels récents (5.11+), userfaultfd nécessite CAP_SYS_PTRACE pour les processus non privilégiés si vm.unprivileged_userfaultfd=0 est configuré (défaut sur la plupart des distributions récentes). Cependant, l'alternative FUSE (Filesystem in Userspace) fournit des capacités similaires pour le contrôle de timing et est accessible sans privilèges spéciaux via fusermount.
Besoin d'un accompagnement expert ?
Nos consultants spécialisés en sécurité système et exploitation kernel vous accompagnent dans l'évaluation de votre posture de sécurité.
Contactez-nousTélécharger cet article en PDF
Format A4 optimisé pour l'impression et la lecture hors ligne
À propos de l'auteur
Ayi NEDJIMI
Expert Cybersécurité Offensive & Intelligence Artificielle
Ayi NEDJIMI est consultant senior en cybersécurité offensive et intelligence artificielle, avec plus de 20 ans d'expérience sur des missions à haute criticité. Il dirige Ayi NEDJIMI Consultants, cabinet spécialisé dans le pentest d'infrastructures complexes, l'audit de sécurité et le développement de solutions IA sur mesure.
Ses interventions couvrent l'audit Active Directory et la compromission de domaines, le pentest cloud (AWS, Azure, GCP), la rétro-ingénierie de malwares, le forensics numérique et l'intégration d'IA générative (RAG, agents LLM, fine-tuning). Il accompagne des organisations de toutes tailles — des PME aux grands groupes du CAC 40 — dans leur stratégie de sécurisation.
Contributeur actif à la communauté cybersécurité, il publie régulièrement des analyses techniques, des guides méthodologiques et des outils open source. Ses travaux font référence dans les domaines du pentest AD, de la conformité (NIS2, DORA, RGPD) et de la sécurité des systèmes industriels (OT/ICS).
Ressources & Outils de l'auteur
Articles connexes
Commentaires
Aucun commentaire pour le moment. Soyez le premier à commenter !
Laisser un commentaire