Les race conditions kernel figurent parmi les vulnérabilités les plus redoutables de l'exploitation système, et aussi parmi les plus difficiles à détecter. Contrairement aux buffer overflows, qui corrompent la mémoire par débordement d'un tampon, elles n'exploitent aucune écriture illégitime : elles tirent parti des fenêtres temporelles qui séparent deux opérations censées être atomiques sans jamais l'être réellement. Étudier une race condition kernel double-fetch TOCTOU revient à observer ce court intervalle pendant lequel l'espace utilisateur peut modifier une donnée déjà validée par le noyau. Le Double-Fetch survient lorsque le kernel lit deux fois la même structure depuis l'userland — une fois pour la vérifier, une fois pour l'utiliser. Le TOCTOU (Time-Of-Check to Time-Of-Use) généralise ce schéma à toute ressource partagée. Exploitées avec un contrôle précis de l'ordonnancement, ces failles mènent à une élévation de privilèges (LPE) complète, du simple utilisateur au root.

En bref

  • 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

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)
Race Condition (Condition de Course) — Vulnérabilité qui survient quand le résultat d'une opération dépend de l'ordre d'exécution de threads ou processus concurrents. En contexte kernel, une race condition se produit quand le noyau effectue plusieurs opérations non-atomiques sur des données partagées (mémoire userspace, fichiers, objets kernel), permettant à l'attaquant de modifier les données entre les opérations.

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 :

Retour terrain

Dans les projets techniques complexes, j'ai appris à toujours commencer par auditer la documentation existante plutôt que l'infrastructure elle-même. Dans 80 % des cas, le delta entre la documentation et la réalité est la source première de risques. Une infrastructure bien documentée qui ne correspond pas à la réalité est plus dangereuse qu'une infrastructure sans documentation — parce qu'elle induit une fausse confiance.

// 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)
⚠️ Attention — userfaultfd est l'outil le plus puissant pour exploiter les race conditions kernel car il permet de bloquer le kernel indéfiniment au milieu d'un copy_from_user(). Depuis Linux 5.11, userfaultfd nécessite CAP_SYS_PTRACE pour les processus non privilégiés — vérifiez que votre kernel a ce sysctl activé (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

Références externes