cd ..

Les failles de sécurité dans les micro-processeurs : Spectre, Meltdown et L1TF

AU
Aubin Puyoou · 07 août 2026

Introduction

En janvier 2018, la communauté informatique apprend simultanément l'existence de deux familles de vulnérabilités matérielles sans précédent : Spectre et Meltdown. Contrairement aux failles logicielles habituelles, ces vulnérabilités sont inscrites dans l'architecture même des processeurs modernes, Intel, AMD, ARM confondus. Elles touchent des milliards de machines en production et n'ont pas de correctif matériel : les patches sont nécessairement logiciels, avec un coût en performances.

Pour comprendre ces failles, il faut d'abord comprendre les mécanismes d'optimisation sur lesquels elles reposent.


Principes de fonctionnement des micro-processeurs

La hiérarchie mémoire

Lors de l'exécution d'un programme, les données transitent entre plusieurs niveaux de stockage, du plus lent au plus rapide : disque dur (HDD/SSD), mémoire vive (DRAM), puis caches processeurs (L1, L2, L3). Ces différents supports stockent à la fois des instructions (LOAD, ADD, STORE…), des variables et des adresses mémoire.

Les programmes référencent leurs données via une table d'adressage, principalement stockée en mémoire vive du fait de l'espace disponible. Chaque entrée associe une adresse virtuelle à une valeur ou une instruction :

Adresse virtuelle Valeur
0x1DF58D 15
0x1DF58E Alice
0x1DF58F LOAD
0x1DF590 0x1DF58F

Le cache CPU est divisé en plusieurs niveaux de taille croissante (L1 le plus petit et le plus rapide, L3 le plus grand mais plus lent). Son rôle est d'accélérer l'accès aux données les plus fréquemment sollicitées, en évitant des allers-retours coûteux vers la DRAM.

Mémoire virtuelle et séparation Kernel / User space

Les données en cache sont organisées en pages, listées dans des tables. Ces pages sont réparties de manière à * limiter l'accès aux données* selon que le code s'exécute en espace utilisateur ou en espace noyau.

Concrètement, deux tables d'adressage coexistent ; une pour le User Space, une pour le Kernel Space, mais les deux adresses virtuelles pointent vers la même mémoire physique. C'est le processeur qui se charge de la traduction adresse virtuelle → adresse physique.

Séparation User Space et Kernel Space en mémoire physique

Cette séparation est fondamentale : un programme utilisateur ne doit jamais pouvoir lire la mémoire du noyau, qui contient des données sensibles comme les clés de chiffrement, les mots de passe en transit, ou les structures internes du système d'exploitation.

Architecture interne du processeur

Pour effectuer ses calculs, le processeur distribue les données entre registres et caches. Les registres sont les emplacements de travail immédiats, ils contiennent les opérandes des instructions en cours. Une fois les calculs effectués, les résultats sont placés dans les caches pour que les accès futurs soient rapides.

Architecture interne d'un micro-processeur Intel Core i7

L'architecture d'un processeur moderne expose bien cette complexité : plusieurs unités d'exécution (ALU, unités vectorielles SSE/FMA), un Reorder Buffer (128 entrées), une Reservation Station (36 entrées), et plusieurs niveaux de TLB et de caches. Chaque étage a un rôle précis dans le pipeline d'exécution.

Cache hit et cache miss

Lorsqu'un programme accède à une donnée pour la première fois, celle-ci n'est pas encore en cache : c'est un cache miss. Le processeur doit aller la chercher en DRAM, ce qui coûte plusieurs centaines de cycles. La donnée est ensuite copiée dans les caches L1 → L2 → L3 pour les accès futurs.

Lors d'un accès ultérieur à la même adresse, la donnée est déjà en cache : c'est un cache hit, résolu en quelques cycles seulement.

int i = 2;
// ...
printf("%d", i);  // cache miss : i chargé depuis la DRAM → copié en cache
// ...
printf("%d", i);  // cache hit  : i lu directement depuis le cache L1

Accès aux données en cache : cache hit (vert, rapide) vs cache miss (rouge, lent)

Cette différence de latence, mesurable depuis l'extérieur avec une précision nanoseconde, est précisément ce que les attaques par canal auxiliaire (side-channel) exploitent.

Exécution spéculative et prédiction de branchement

Dans les cours d'architecture, on présente souvent les instructions comme exécutées séquentiellement. En pratique, une telle approche serait catastrophique pour les performances.

Les processeurs modernes utilisent plusieurs mécanismes d'optimisation :

L'exécution spéculative : le processeur n'attend pas le résultat d'une opération pour commencer les instructions suivantes. Il anticipe le résultat probable et avance, quitte à annuler si la prédiction s'avère fausse. Cette annulation restaure l'état architectural visible ; registres, flags ; mais pas nécessairement le contenu du cache.

La prédiction de branchement : lorsqu'une instruction conditionnelle (if, boucle) est rencontrée, le processeur tente de deviner quelle branche sera empruntée et l'exécute en avance. En cas d'erreur, les résultats spéculatifs sont marqués invalides, mais les données chargées restent présentes dans le cache jusqu'à être écrasées.

L'exécution dans le désordre (out-of-order execution) : les instructions sont chargées dans des files d'attente et exécutées dès que leurs opérandes sont disponibles, indépendamment de l'ordre du programme. Une fois toutes les instructions précédentes terminées, les résultats sont réordonnés et retournés dans les registres. Cela permet de traiter bien plus d'instructions par cycle qu'une exécution strictement séquentielle.

C'est ce résidu dans le cache, issu d'une exécution spéculative ou dans le désordre, qui constitue le vecteur d'attaque de Spectre, Meltdown et leurs variantes.


Partie 2 — Exemples de failles connues

Spectre v1 — Bounds Check Bypass

Spectre v1 (CVE-2017-5753) exploite la prédiction de branchement pour contourner une vérification de bornes, et lire un octet arbitraire dans la mémoire d'une victime.

Considérons ce code présent dans le processus victime :

if (x < array1_size)
    y = array2[array1[x] * 4096];

L'attaquant choisit une valeur de x délibérément hors bornes, de sorte que array1[x] pointe vers un octet secret k situé ailleurs dans la mémoire de la victime. La condition x < array1_size est censée bloquer cet accès, mais le prédicteur de branchement va en décider autrement.

L'attaque se déroule en quatre étapes :

  1. Conditionnement : l'attaquant appelle le code répétitivement avec des valeurs valides de x, entraînant le prédicteur à toujours prédire que la condition est vraie.

  2. Spéculation malveillante : l'attaquant envoie une valeur hors bornes. Fort de ses prédictions passées, le processeur exécute spéculativement le corps du if avant d'avoir vérifié la condition. array1[x] lit la valeur secrète k, puis array2[k * 4096] est chargé en cache.

  3. Correction : quand la vérification détecte l'erreur, le processeur annule l'exécution spéculative. Les registres sont restaurés, mais array2[k * 4096] reste en cache.

  4. Extraction : L'attaquant mesure le temps d'accès à chacune des 256 entrées possibles d'array2. L'entrée qui répond vite (cache hit) révèle la valeur de k.

Cette technique de mesure est l'attaque FLUSH+RELOAD : l'attaquant vide (flush) toutes les entrées d'array2 du cache avant la spéculation, laisse la victime exécuter le code, puis recharge (reload) chaque entrée en chronométrant l'accès.

Chronologie FLUSH+RELOAD : l'attaquant (rouge/bleu) et la victime (vert) s'entrelacent sur le cache

Le diagramme illustre différents scénarios (A à E) selon que la victime accède à la donnée avant ou après que l'attaquant recharge les slots. Dans tous les cas, la différence de timing entre un cache hit et un cache miss permet de confirmer la valeur de k.

Spectre v2 — Branch Target Injection

La deuxième variante (CVE-2017-5715) cible le prédicteur de cible de branchement indirect, qui mémorise vers quelle adresse un call [registre] va sauter. Ce prédicteur est partagé entre contextes d'exécution : c'est ici que se présente la faille.

L'attaque s'appuie sur deux contextes contrôlés par l'attaquant (deux processus, deux VMs sur un même cœur) :

  1. Entraînement : depuis le Context A, l'attaquant appelle répétitivement un branchement indirect vers une adresse pointant vers un gadget Spectre, un fragment de code anodin qui charge quand même une partie du secret dans un registre.

  2. Injection : dans le Context B (la victime), lorsqu'un branchement indirect similaire est exécuté, le prédicteur, conditionné depuis A, prédit la mauvaise cible et s'élance spéculativement vers le gadget.

  3. Exfiltration : en effectuant des opérations arithmétiques contrôlées sur les octets chargés (additions, soustractions avec des valeurs connues), l'attaquant peut déduire le secret octet par octet.

Spectre v2 : le Branch Predictor partagé est manipulé depuis Context A pour faire spéculer Context B vers un gadget malveillant

Meltdown — Rogue Data Cache Load

Meltdown (CVE-2017-5754) exploite l'exécution dans le désordre pour lire directement la mémoire du noyau depuis l'espace utilisateur, contournant la protection Kernel/User.

Le mécanisme est le suivant : quand un programme utilisateur accède à une adresse noyau, le processeur doit d'abord traduire l'adresse virtuelle en physique, puis vérifier à quel espace elle appartient. Cette vérification déclenche une exception. Mais, du fait de l'exécution dans le désordre, l'exception n'est levée qu'au moment de réorganiser les résultats, après que les unités d'exécution ont déjà traité la donnée.

Il est ainsi possible de faire concourir l'instruction accédant à la donnée noyau et celle levant l'exception. Si la donnée est propagée dans le cache avant le traitement de l'exception, elle est lisible via FLUSH+RELOAD.

Le code PoC minimal illustrant le principe :

int my_array[2] = {0, 1};
int out_of_bounds_index = sizeof(my_array) * 4096;
throw 1;   /* exception levée ici... */
printf("%s\n", my_array[out_of_bounds_index]);  /* ...mais ceci est exécuté dans le désordre */

Contre-intuitivement, même si throw 1 précède le printf dans le code source, le processeur exécute spéculativement le printf avant de traiter l'exception. L'accès hors bornes charge une adresse noyau en cache, puis l'exception est gérée, mais la donnée reste accessible via le timing.

En analysant les temps d'accès à un tableau oracle de 256 entrées (une par valeur d'octet possible), il suffit de comparer les latences pour reconstituer l'octet secret lu en mémoire noyau.

Foreshadow / L1TF — L1 Terminal Fault

Foreshadow (CVE-2018-3615, aussi appelée L1TF) est une variante de Meltdown qui cible spécifiquement le cache L1, et se révèle particulièrement redoutable dans les environnements virtualisés, enclaves SGX, hyperviseurs, machines virtuelles imbriquées.

Son nom vient de l'exploitation d'entrées de table de pages marquées non présentes (not present). Lorsque le processeur tente d'accéder à une telle page, il déclenche une terminal fault, mais l'exécution dans le désordre a déjà lu la donnée depuis le cache L1 si elle s'y trouve.

L'attaque se déroule en trois phases :

Phase I — Mise en cache du secret : l'attaquant s'arrange pour que la donnée secrète (ici dans une enclave SGX) soit présente en cache L1.

Phase II — Exécution transitoire : l'attaquant marque la page contenant le secret comme non présente. Lors d'un accès, le processeur lance une exception, mais l'exécution dans le désordre a déjà utilisé la valeur du cache L1 pour indexer un tableau oracle de 256 slots (slot #0 à slot #255).

Phase III — Extraction : l'exception est gérée, et l'attaquant mesure le temps d'accès à chaque slot de l'oracle. Le slot en cache hit révèle l'octet secret.

Les trois phases de l'attaque Foreshadow : mise en cache, exécution transitoire, extraction via l'oracle

Dans un environnement avec des machines virtuelles imbriquées, si les correctifs ne sont pas appliqués séparément sur chaque OS de la hiérarchie, la faille peut être exploitée entre deux VMs de même niveau pour lire la mémoire de l'une depuis l'autre.


Partie 3 — Correctifs et impact sur les performances

KPTI — Kernel Page Table Isolation

Le correctif principal pour Meltdown est KPTI (initialement nommé KAISER, Kernel Page Table Isolation). L'idée est de briser le partage des tables de pages entre espace noyau et espace utilisateur.

Sans KPTI, les adresses noyau sont mappées dans la table de pages du mode utilisateur (pour éviter les coûts de rechargement lors des syscalls), c'est précisément ce qui rend Meltdown possible. Avec KPTI :

  • En mode utilisateur, la table de pages ne contient que les adresses utilisateur et un stub minimal du noyau ( entrée des syscalls, gestion des interruptions).

  • Lors d'un basculement vers le noyau, le processeur bascule sur une table de pages complète incluant toute la mémoire noyau.

Cette séparation élimine la surface d'attaque de Meltdown : même si le processeur exécute spéculativement un accès noyau, l'adresse n'est plus mappée dans la table utilisateur.

Comparaison de la répartition mémoire avant KPTI (Regular OS) et après (KAISER + SMAP/SMEP)

Retpoline — Correctif Spectre v2

Pour Spectre v2, le correctif principal est Retpoline (Return Trampoline), introduit par Google. Il remplace les branchements indirects (call [registre]) par une séquence qui manipule l'instruction ret de façon à tromper le prédicteur de branchement.

Le principe : le prédicteur de ret (Return Stack Buffer) est distinct du prédicteur de branchement indirect. En remplaçant un jmp [reg] par une construction utilisant ret, on fait spéculer le CPU vers une boucle infinie inoffensive (trampoline) plutôt que vers un gadget malveillant. La cible réelle est quand même atteinte, mais via un chemin qui ne permet pas la spéculation dangereuse.

En complément, un aplanissement des temps d'accès aux caches est introduit dans certaines implémentations : la latence est rendue uniforme que la donnée provienne du cache L1 ou de la DRAM. Cela rend les attaques FLUSH+RELOAD beaucoup moins fiables, car la différence de timing exploitable devient imperceptible.

Correctifs L1TF

Les correctifs pour L1TF opèrent à plusieurs niveaux :

  • BIOS des machines physiques : nouvelles options pour vider le cache L1 lors des transitions entre contextes de confiance différents (changement de VM, sortie d'enclave SGX).

  • Hyperviseurs : paramètres pour empêcher deux VMs partageant un cœur physique de s'espionner via le cache L1 partagé.

  • Machines virtuelles imbriquées : les correctifs doivent être appliqués séparément sur chaque OS de la hiérarchie. Une VM intermédiaire non patchée reste une surface d'attaque, exploitable entre deux VMs du même niveau.

Impact sur les performances

Une préoccupation majeure lors de la divulgation de ces failles était le coût des correctifs. Pour KPTI, les mesures publiées se sont révélées rassurantes pour la plupart des workloads.

Benchmark Overhead moyen (KAISER)
PARSEC 3.0 0,37 %
pgbench 0,39 %
SPLASH-2X 0,09 %

L'overhead de KAISER est quasi imperceptible sur ces benchmarks, car ils sont majoritairement CPU-bound et effectuent peu de syscalls. L'impact est plus marqué sur les workloads à forte densité de syscalls (serveurs web, bases de données avec de nombreuses petites requêtes).

Pour L1TF, l'impact est plus variable. Les benchmarks I/O-intensifs et réseau (sockperf, pgbench en écriture, stress-ng) présentent des régressions notables sur certains kernels, tandis que les workloads CPU-bound restent peu affectés. Les environnements multi-VM sont les plus touchés, car le vidage du cache L1 à chaque changement de contexte VM est coûteux.


Conclusion

Spectre, Meltdown et L1TF illustrent une vérité fondamentale en sécurité système : les optimisations de performance peuvent créer des canaux d'information involontaires. Trois décennies d'innovation dans l'architecture des processeurs (exécution spéculative, prédiction de branchement, exécution dans le désordre) ont ouvert des brèches que personne n'avait anticipées à cette échelle.

Ce qui rend ces failles particulièrement sévères, c'est leur indépendance vis-à-vis du logiciel : elles opèrent au niveau matériel, et aucun code applicatif, aussi bien écrit soit-il, ne peut les empêcher seul. Les correctifs (KPTI, Retpoline, patches BIOS) ont démontré qu'il est possible de les mitiger à un coût raisonnable, mais la découverte continue de variantes (MDS, RIDL, Fallout, Spectre-BHI…) montre que ce vecteur d'attaque est loin d'être épuisé.

La sécurité de l'architecture matérielle est désormais un enjeu de premier plan, au même titre que la sécurité logicielle.


Références

  • Kocher, Paul et al. Spectre Attacks: Exploiting Speculative Execution, 2019.

  • Lipp, Moritz et al. Meltdown: Reading Kernel Memory from User Space, Communications of the ACM 63, n° 6, 2020. doi:10.1145/3357033

  • Yarom, Yuval et Falkner, Katrina. FLUSH+RELOAD: A High Resolution, Low Noise, L3 Cache Side-Channel Attack.

  • Schwarzl, Martin et al. Speculative Dereferencing: Reviving Foreshadow, Financial Cryptography and Data Security, Springer, 2021.

  • Gruss, D. et al. KASLR is Dead: Long Live KASLR, Engineering Secure Software and Systems, 2017. doi:10.1007/978-3-319-62105-0_11

  • Amit, Nadav et al. Dealing with (Some of) the Fallout from Meltdown, ACM SYSTOR, 2021. doi:10.1145/3456727.3463776