Vulnérabilités d'exécution spéculative dans les microprocesseurs
Le 4 janvier 2018, les vulnérabilités Spectre et Meltdown étaient rendues publiques suite à la mise en ligne de deux sites internet dédiés [1][2]. Ces deux failles appartiennent à une nouvelle classe de vulnérabilité dites d'exécution spéculative et ont été couvertes dans l'alerte CERTFR-2018-001 du CERT-FR [3].Les processeurs modernes intègrent plusieurs mécanismes d'optimisation des performances aux nombres desquelles on retrouve l'exécution spéculative. Ce procédé permet au processeur d'émettre des hypothèses sur les futures instructions à exécuter afin d'optimiser l'utilisation des ressources de calculs. Ces instructions sont alors exécutées de façon anticipée. Dans le cas où la prédiction a été correctement réalisée, un gain de temps à donc eu lieu. Dans le cas contraire les résultats des calculs ne sont pas pris en compte. Des chercheurs en sécurité ont néanmoins pu démontrer qu'il était possible de récupérer le résultat de ces instructions exécutés sous hypothèses en exploitant des attaques par canaux auxiliaires utilisant les caches de données.
La vulnérabilité Spectre regroupe deux variantes utilisant la technique décrite ci-dessus. La seconde variante de l'attaque exploite en particulier une vulnérabilité dans la prédiction des sauts. Cela peut par exemple être une hypothèse sur la valeur d'un saut conditionnel, ou encore sur un saut indirect.
Paul Turner, ingénieur chez Google, a présenté une contre-mesure baptisée retpoline (contraction de "return" et "trampoline") pour faire face à cette dernière catégorie d'attaques [4]. Cette mesure de contournement implémentée dans les compilateurs permettrait d'empêcher d'utiliser une application légitime pour réaliser une attaque via l'exécution spéculative. Elle consiste à induire systématiquement le processeur en erreur dans ses fonctions de prédiction de branchement tout en réalisant les branchements indirects correctement. La prédiction de branchement n'est ainsi plus utilisable pour l'attaquant.
Mesures de contournement retpoline
Plusieurs stratégie de prédictions peuvent exister dans un processeur lors d'un fonctionnement normal. Un saut dont l'emplacement de destination est conditionné par la valeur contenue dans un registre est appelé un branchement indirect. Lors d'un tel saut, il est possible pour le processeur d'émettre une supposition sur la destination. En effet, au cours de l'exécution, la destination d'un branchement indirect est stockée dans un tampon afin d'orienter de futurs prédictions de branchement. Il s'agit du tampon de destination de branchement (branch target buffer ou BTB). C'est ce tampon qui est empoisonné dans l'attaque Spectre.
[pastacode lang="c" manual="jmp%20*%25rax%3B" message="Exemple de branchement indirect" highlight="" provider="manual"/]
En contre-mesure pour cette attaque, Paul Turner propose de remplacer l'utilisation de ce mécanisme de prédiction par un second qui sera lui contrôlé pour en empêcher tout détournement. En effet, une instruction de branchement indirect sera substituée par un appel de fonction call suivi d'un retour de fonction ret. Une construction de ce type peut aussi dépendre d'une exécution spéculative mais elle sera cette fois-ci contrôlée par une seconde mémoire tampon : le tampon d'empilage des retours (return stack buffer ou RSB).
Lors d'un appel de fonction call dans une architecture x86 l'adresse de retour est ajoutée sur la pile. Elle est aussi dans notre cas ajoutée au RSB et les deux adresses sont ensuite gérées indépendamment. Cette entrée du RSB sera utilisée pour effectuer l’exécution spéculative tandis que l'utilisation de retpoline entraînera la réécriture sur la pile de l'adresse de retour pour correspondre à la destination souhaité pour notre saut indirect. Le code exécuté de façon spéculative sera lui contrôlé pour ne pas permettre de détournement par un potentiel attaquant.
L'exemple ci-dessous montre le fonctionnement du mécanisme retpoline.
Le saut indirect :
[pastacode lang="c" manual="jmp%20*%25r11" message="" highlight="" provider="manual"/]
sera remplacé par une construction du type :
[pastacode lang="c" manual="call%20construction_destination%3B%20(1)%0Acapture_execution_speculative%3A%20(4)%0Apause%3B%0Ajmp%20capture_execution_speculative%3B%0Aconstruction_destination%3A%0Amov%20%25r11%2C%20(%25rsp)%3B%20(2)%0Aret%3B%20(3)" message="Exemple de construction de saut indirect avec retpoline (source: Paul Turner)" highlight="" provider="manual"/]
Dans un premier temps (1), l'appel de fonction empilera l'adresse de retour et ajoutera une entrée dans le RSB. Cette adresse correspond à l'instruction suivant le call, soit le code identifié par l'étiquette "capture_execution_speculative". Lors de l'exécution de l'appel de la fonction "construction_destination" l'adresse de retour sur la pile est remplacée par la valeur contenue dans le registre r11 (2). Le retour de la fonction (3) dirigera donc le flux d'exécution vers le code pointé par le registre r11 réalisant bien l'équivalent d'un saut indirect "jmp *%r11". L'exécution spéculative sera en revanche orientée par la valeur contenue dans le tampon RSB et exécutera le code présent au niveau de l'étiquette "capture_execution_speculative" (4). Ce code est une boucle infinie qui n'entraînera pas d'effet de bord.
Mise en œuvre de retpoline
Une implémentation de retpoline a déjà été intégrée au compilateur gcc et une intégration dans LLVM est en cours d'évaluation.L'utilisation de l'option de compilation -mindirect-branch=thunk-extern dans gcc permet d'activer cette fonctionnalité.
Documentation
- Site dédié à la vulnérabilité Meltdown https://meltdownattack.com/
- Site dédié à la vulnérabilité Spectre https://spectreattack.com/
- Bulletin d'alerte du CERT-FR CERTFR-2018-ALE-001 https://cert.ssi.gouv.fr/alerte/CERTFR-2018-ALE-001/
- Publication de blogue de Paul Turner sur retpoline https://support.google.com/faqs/answer/7625886