Le correctif Spectre au niveau du compilateur de Microsoft montre à quel point ce problème sera difficile à résoudre

Les attaques Meltdown et Specter qui utilisent l’exécution spéculative du processeur pour divulguer des informations sensibles ont entraîné une vaste gamme de modifications logicielles visant à limiter la portée des dommages. La plupart d'entre eux sont des correctifs au niveau du système d'exploitation, dont certains dépendent des mises à jour du microcode du processeur.

Mais Spectre n'est pas une simple attaque à résoudre; les modifications du système d'exploitation aident beaucoup, mais des modifications au niveau de l'application sont également nécessaires. Apple a évoqué certaines des mises à jour apportées au moteur de rendu WebKit, utilisé dans son navigateur Safari, mais il ne s'agit que d'une seule application.

Microsoft propose une modification au niveau du compilateur pour Specter. Le label "Spectre" couvre en réalité deux attaques différentes. Celui que le compilateur de Microsoft adresse, appelé "variante 1", concerne le contrôle de la taille d'un tableau: avant d'accéder au Nième élément d'un tableau, le code devrait vérifier que le tableau contient au moins N éléments. Les programmeurs utilisant des langages tels que C et C ++ doivent souvent écrire explicitement ces vérifications. D'autres langues, telles que JavaScript et Java, les exécutent automatiquement. Quoi qu'il en soit, le test doit être effectué. les tentatives d'accès à des membres du groupe qui n'existent pas constituent une classe de bogues.

Fuites d'informations spéculatives

Le problème de Spectre est que le processeur n'attend pas toujours si le Nième élément existe avant d'essayer d'y accéder. Il peut spéculativement essayer d'accéder au Nième élément en attendant la fin du contrôle. Cet accès est toujours "sûr" dans la mesure où il n'introduit aucun bogue de programmation. Mais comme les chercheurs en sécurité l'ont découvert, il peut y avoir une fuite d'informations. Le processeur essaiera de charger le Nième élément (peu importe ce que c'est ou même s'il existe), ce qui peut modifier les données stockées dans le cache du processeur. Le changement peut être détecté et peut être utilisé pour divulguer des informations secrètes.

Ce type d’exécution spéculative est important pour les processeurs modernes. Les puces Intel actuelles peuvent exécuter 200 instructions de manière spéculative. Ils vont essentiellement deviner comment les comparaisons seront évaluées et quelle sera la marche à suivre dans le code, annulant tout ce qu'elles ont deviné si ces suppositions s'avéraient être erronées. Tout cela est censé être transparent pour les programmes en cours d'exécution; le problème de sécurité est dû au fait que ce n'est pas le cas, grâce à ces modifications apportées au cache.

Le correctif généralement accepté pour ce problème consiste à faire attendre le processeur: pour lui dire de ne pas accéder à la matrice jusqu'à ce que le test pour voir si l'élément existe soit terminé. La difficulté de cette opération (et la raison pour laquelle Microsoft étudie une modification au niveau du compilateur) réside dans l'identification précise des accès risqués et leur correction, ce qui nécessite une inspection minutieuse ligne par ligne du code source du programme. L'objectif de la modification de Microsoft est d'éviter cela et d'insérer des instructions pour que le processeur cesse de spéculer automatiquement aux bons endroits.

En tant que problème déconcertant, il n’existe vraiment aucun moyen vraiment efficace d’empêcher le processeur de spéculer et d’attendre. Étant donné que l'exécution spéculative est conçue pour être transparente, détail implémenté caché du fonctionnement du processeur, les processeurs ne donnent pas beaucoup de contrôle sur son fonctionnement. Il n'y a pas d'instruction explicite - pour l'instant - qui dit au processeur "ne spéculez pas au-delà de cette instruction" sans avoir d'autres effets.

Ce que nous avons, cependant, sont des instructions qui juste arriver d'agir comme un bloc de spéculation. Le plus ancien et le plus utilisé est une instruction appeléecpuid.cpuidn'a en fait rien à voir avec la spéculation. Le processeur contient diverses tables d’informations décrivant, par exemple, les extensions qu’il prend en charge (SSE, AVX, 64 bits, etc.) ou la topologie de son cache, etcpuidest utilisé pour lire ces tableaux.

cpuidest une instruction très lente (il faut des centaines de cycles à exécuter), de sorte qu'elle a toujours eu une propriété supplémentaire inhabituelle en plus de la lecture des tables de données du processeur: elle est documentée en tant qu '"instruction de sérialisation" qui bloque l'exécution spéculative. Toute instruction avant uncpuiddoit être complètement exécuté avant lacpuidcommence à courir, et aucune instruction après uncpuidpeut commencer à courir jusqu'aucpuidest fini.

En réponse à Spectre, ARM introduit une instruction appeléeCSDB, dont le seul but est d’être une barrière d’exécution spéculative. Mais sur x86, du moins pour le moment, aucune instruction de ce type n’est prévue; nous n'avons à la place que des instructions commecpuid, où le blocage de la spéculation est un effet secondaire.

Voici ce que c'est censé faire

La nouvelle fonctionnalité de compilateur de Microsoft insérera une instruction pour bloquer la spéculation dans un code que le compilateur détecte comme étant vulnérable à Spectre. Plus précisément, chaque fois qu'un code comme celui-ci est détecté:

if (untrusted_index <array1_length) {// accès spéculatif à un tableau char non signé char value = array1 [untrusted_index]; // utilise ces données accédées de manière spéculative // ​​de manière à perturber le cache char non signé valeur2 = tableau2 [valeur * 64]; }

C'est transformé en quelque chose de plus proche de ça:

if (untrusted_index <array1_length) {// assurez-vous que le processeur connaît le résultat // de la comparaison speculation_barrier (); // cet accès n'est plus spéculatif. unsigned char value = array1 [untrusted_index]; caractère non signé valeur2 = tableau2 [valeur * 64]; }

L'instruction choisie par Microsoft pour bloquer la spéculation s'appellelence, ce qui signifie "barrière de charge".lenceest une autre instruction qui n’a vraiment rien à voir avec une exécution spéculative. En principe, il est utilisé pour garantir que le processeur a terminé toutes les tentatives en attente de chargement de données en mémoire avant de lancer de nouvelles tentatives de chargement. La valeur exacte de cette instruction dans x86 n’est pas tout à fait claire (x86 a déjà des règles strictes concernant l’ordre dans lequel il tente d’effectuer des chargements à partir de la mémoire), mais avec la découverte de Spectre,lencea pris un nouveau rôle: c’est aussi un bloc de spéculation.

lenceest plus pratique quecpuidcar il ne modifie aucun registre, maislenceL'utilisation en tant que bloc de spéculation est légèrement délicate. Pour ses processeurs, Intel a toujours documentélencecomme ayant un comportement semi-sérialisant. En principe, les instructions exécutant des magasins pourraient être réorganisées et exécutées de manière spéculative, mais pas celles dépendant de charges. Comme aveccpuid, c’était en grande partie un effet secondaire etne pas le but explicite de l'instruction. Pour AMD, cependant,lencen'a pas toujours été en série. C'est sur certaines architectures AMD; ce n'est pas sur les autres. Cette variation était autorisée en raison de la manière dont le comportement d'exécution spéculatif a toujours été traité comme des détails d'implémentation, et non comme une partie documentée de l'architecture. Le comportement en termes d’architecture de processeur est le même quelencesérialise ou non.

Dans le cadre de leurs réponses à Spectre, Intel et AMD ont tous deux changélence. Pour Intel, le changement semble être de la documentation:lenceest maintenant une instruction de sérialisation complète, mais cela ne semble pas avoir nécessité de modifications matérielles. Il semble donc que l'instruction ait toujours eu ce comportement dans les implémentations d'Intel. AMD a adopté la convention d'Intel. aller de l'avant,lencesera toujours une instruction de sérialisation qui bloque une exécution spéculative. Pour les processeurs existants, AMD indique qu'un MSR (un "registre spécifique au modèle", un registre de fournisseur spécial et un registre de processeur spécifique au modèle pouvant être utilisé pour appliquer une configuration de bas niveau) peut être utilisé pour modifier la non-sérialisation.lence en sérialisationlence. Seuls les systèmes d'exploitation (et les hyperviseurs de virtualisation) peuvent modifier les MSR. Des mises à jour du système d'exploitation sont donc nécessaires pour garantir l'activation de cette fonctionnalité.

Mettre à jour: Une version précédente de cet article indiquait que certains processeurs AMD auraient besoin d'une mise à jour du microcode pour permettre cette sérialisationlencecomportement. Cela s'avère ne pas être le cas; Alors que certains processeurs AMD n'incluent pas le MSR, AMD indique que ces processeurs ont déjà le comportement de sérialisation de toute façon.

A l'avenir,lencesera probablement l'équivalent le plus proche de x86 à ARMCSDB.

Le changement de compilateur Microsoft injecte lelenceinstructions au bon endroit pour empêcher les attaques de Spectre sur ce type de code. Le compilateur de Microsoft fonctionne et le changement de code est effectif. Mais c’est là que les choses se compliquent.

Un problème complexe à résoudre

L'exécution spéculative est importante. Nous voulons que nos processeurs exécutent de manière spéculative presque tout le temps, car leur performance en dépend. En tant que tel, nous ne voulons paslenceinséré A chaque fois un tableau est accédé. Par exemple, beaucoup de programmes font quelque chose comme ça:

for (int i = 0; i <tableau.size (); ++ i) {valeur de caractère non signé = tableau [i]; }

Ce type de code, qui accède à chaque élément du tableau dans l’ordre, sera toujours sûr; le programme n'a tout simplement aucun moyen de générer une valeur dejec'est plus grand que la taille du tableau. Il n'a pas besoinlenceinstructions. En conséquence, le compilateur de Microsoft n'insère pas aveuglémentlenceinstructions à chaque fois. La plupart du temps, en fait, cela ne les ajoute pas. Au lieu de cela, il utilise certains types d'heuristiques non spécifiés pour déterminer où ils doivent être insérés.

Cette approche préserve les performances, mais malheureusement, les méthodes heuristiques de Microsoft sont étroitement contraintes. Ils détectent certains modèles de code vulnérables au spectre, mais pas tous. Même de petites modifications apportées à un élément de code vulnérable peuvent vaincre l'heuristique de Microsoft. Le code sera vulnérable à Spectre, mais le compilateur n'ajoutera rien.lenceinstructions pour le protéger.

Paul Kocher, l'un des chercheurs qui ont rédigé le document Spectre, a examiné de plus près ce que fait le compilateur. Il a découvert que l'atténuation du spectre de Microsoft par Microsoft est beaucoup plus étroite que ce à quoi on pourrait s'attendre à la lecture de la description de la société. Le code doit suivre de très près la structure vulnérable pour obtenir lelenceinséré. Si cela diffère un peu (par exemple, si le test de l'index de tableau est dans une fonction, mais que l'accès au tableau est dans une autre fonction), le compilateur suppose que le code n'est pas vulnérable. Ainsi, alors que le changement de Microsoft protège effectivement le code de la exact Spectre attaque décrit dans le document d'origine, sa protection est étroite.

C'est un problème, car les développeurs peuvent penser que leur code est sûr: ils ont construit leur code avec la protection Specter de Microsoft activée, alors qu'il est tout aussi vulnérable qu'il l'a toujours été. Comme l'écrit Kocher, "les barrières de spéculation ne sont une défense efficace que si elles sont appliquées à tous les modèles de code vulnérables d'un processus, de sorte que les mesures d'atténuation au niveau du compilateur doivent instrumentaliser tous les modèles de code potentiellement vulnérables." Le changement de compilateur de Microsoft ne le fait pas.

"Aucune garantie"

En toute honnêteté, Microsoft prévient qu '"il n'y a aucune garantie que toutes les instances possibles de la variante 1 seront instrumentées", mais comme le montre l'examen de Kocher, ce n'est pas simplement qu'un code vulnérable à Spectre échappera aux corrections du compilateur. Un code très vulnérable et peut-être même le plus vulnérable de Spectre s’échappera. Et même s'il ne s'agissait que de quelques cas, les méchants seraient en mesure de localiser les routines non protégées et de concentrer leurs attaques en conséquence.

Fondamentalement, le seul code qui nécessitelenceLes instructions sont celles où un attaquant peut contrôler l’indice de tableau utilisé. Sans ce contrôle, un attaquant ne peut pas influencer lequel les informations sont divulguées par exécution spéculative. Mais détecter exactement quels accès au tableau sont dérivés de l'entrée utilisateur et lesquels ne le sont pas est beaucoup trop complexe pour le compilateur. Dans un langage tel que C ou C ++, le seul moyen de prendre cette décision de manière fiable consiste à exécuter le programme.

Kocher suggère que Microsoft devrait proposer un mode plus pessimiste qui protège tout accès conditionnel. Mais cela aura un coût important: dans un exemple de code, il écrivait pour calculer les hachages SHA-256, la version aveclenceles instructions après chaque succursale ne représentaient que 40% des performances de la version non modifiée. Cela pose un compromis sécurité-performance qui est décidément inconfortable; Même si le compilateur offrait une telle option, peu de personnes seraient susceptibles d’accepter ce type de pénalité de performance en général. Mais pour les plus petits morceaux de code dont on sait qu’ils sont à risque, une telle option peut être utile.

La protection beaucoup plus restreinte de Microsoft a le mérite d’avoir un impact beaucoup plus faible; la société a déclaré avoir construit Windows avec la protection Specter et n'avoir trouvé aucune régression des performances.

Le travail effectué sur le compilateur et les limitations rencontrées soulignent le problème complexe que Spectre pose à l'industrie informatique. Les processeurs fonctionnent comme ils sont censés le faire. Nous ne pouvons pas nous passer d’une exécution spéculative de ce type - nous avons besoin des performances qu’elle offre - mais, de même, nous n’avons aucun moyen efficace de traiter systématiquement les problèmes de sécurité qu’elle crée. Les modifications du compilateur du type que Microsoft a apportées sont bien intentionnées, mais comme l’a montré l’enquête de Kocher, elles sont loin d’offrir une solution complète.