Qu'y a-t-il derrière la faille de conception d'Intel forçant de nombreux correctifs?

Windows et Linux reçoivent tous deux des mises à jour de sécurité importantes qui peuvent, dans le pire des cas, entraîner une chute de moitié des performances, afin de se protéger contre un problème qui n'a pas encore été complètement révélé.

Les correctifs du noyau Linux se sont répandus au cours des dernières semaines. Microsoft teste les mises à jour Windows dans le programme Insider depuis novembre, et il est prévu que les modifications apportées aux versions Windows principales de Patch mardi apparaîtront la semaine prochaine. Azure de Microsoft a programmé la maintenance la semaine prochaine, et AWS d’Amazon est prévue pour la maintenance de vendredi, probablement liée.

Depuis que les correctifs Linux sont apparus, une image plus claire de ce qui semble être faux est apparue. Bien que Linux et Windows diffèrent à de nombreux égards, les éléments de base de la manière dont ces deux systèmes d'exploitation sont utilisés - et en fait, tous les autres systèmes d'exploitation x86 tels que FreeBSD et la mémoire système macOS-handle sont identiques, car ces parties du système d'exploitation couplé aux capacités du processeur.

Garder une trace des adresses

Chaque octet de mémoire dans un système est implicitement numéroté, ces numéros étant l'adresse de chaque octet. Les tout premiers systèmes d'exploitation utilisaient des adresses de mémoire physique, mais les adresses de mémoire physique sont peu pratiques pour de nombreuses raisons. Par exemple, il existe souvent des lacunes dans les adresses et (en particulier sur les systèmes 32 bits), les adresses physiques peuvent être difficiles à manipuler, nécessitant des nombres de 36 bits, voire des valeurs plus grandes.

En conséquence, les systèmes d'exploitation modernes dépendent tous d'un concept large appelé mémoire virtuelle. Les systèmes de mémoire virtuelle permettent aux programmes et aux noyaux eux-mêmes de fonctionner dans un environnement simple, propre et uniforme. Au lieu des adresses physiques avec leurs lacunes et autres anomalies, chaque programme, ainsi que le noyau lui-même, utilise des adresses virtuelles pour accéder à la mémoire. Ces adresses virtuelles sont contiguës (pas besoin de s'inquiéter des espaces vides) et sont classées de manière pratique pour faciliter leur manipulation. Les programmes 32 bits ne voient que les adresses 32 bits, même si l'adresse physique nécessite une numérotation de 36 bits ou plus.

Bien que cet adressage virtuel soit transparent pour presque tous les logiciels, le processeur doit en fin de compte savoir à quelle mémoire physique une adresse virtuelle fait référence. Il existe un mappage d'adresses virtuelles en adresses physiques, stocké dans une grande structure de données appelée table de pages. Les systèmes d'exploitation construisent la table de pages en utilisant une présentation déterminée par le processeur. Le processeur et le système d'exploitation utilisent conjointement la table de pages chaque fois qu'ils ont besoin de convertir des adresses virtuelles en adresses physiques.

L’ensemble de ce processus de mappage est si important et fondamental pour les systèmes d’exploitation et les processeurs modernes que le processeur dispose d’un cache dédié, le tampon d’affichage des traductions, ou TLB, qui stocke un certain nombre de mappages virtuels / physiques afin d’éviter toute utilisation intégrale. table de page à chaque fois.

L'utilisation de la mémoire virtuelle nous donne un certain nombre de fonctionnalités utiles au-delà de la simplicité d'adressage. Le plus important est que chaque programme individuel se voit attribuer son propre ensemble d’adresses virtuelles, avec son propre ensemble de mappages virtuels / physiques. C'est la technique fondamentale utilisée pour fournir une "mémoire protégée"; un programme ne peut pas corrompre ou altérer la mémoire d'un autre programme, car la mémoire de l'autre programme ne fait tout simplement pas partie du mappage du premier programme.

Mais ces utilisations d'un mappage individuel par processus, et donc de tables de pages supplémentaires, exercent une pression sur le cache TLB. Le TLB n’est pas très volumineux - généralement quelques centaines de mappages au total - et plus un système utilise de tables de pages, moins il est probable que le TLB inclura une traduction particulière physique-virtuelle.

Moitié moitié

Pour tirer le meilleur parti du TLB, chaque système d’exploitation traditionnel divise la plage d’adresses virtuelles en deux. La moitié des adresses est utilisée pour chaque programme; l'autre moitié est utilisée pour le noyau. Lorsque vous passez d'un processus à l'autre, seules la moitié des entrées de la table des pages changent, celles appartenant au programme. La moitié du noyau est commune à tous les programmes (puisqu'il n'y a qu'un seul noyau) et peut donc utiliser le même mappage de table de pages pour chaque processus. Cela aide énormément le TLB. Bien qu'il reste à supprimer les mappages appartenant à la moitié des adresses mémoire du processus, il peut conserver les mappages pour la moitié du noyau.

Cette conception n'est pas complètement figée. Un travail a été effectué sur Linux pour permettre de donner à un processus 32 bits l’ensemble des adresses, sans partage entre le tableau de pages du noyau et celui de chaque programme. Alors que cela donnait plus d’espace d’adresse aux programmes, cela entraînait un coût en performances, car le TLB devait recharger les entrées de la table de pages du noyau à chaque fois que le code du noyau devait s’exécuter. En conséquence, cette approche n’a jamais été largement utilisée sur les systèmes x86.

Un inconvénient de la décision de scinder l'espace d'adressage virtuel entre le noyau et chaque programme est que la protection de la mémoire est affaiblie. Si le noyau disposait de son propre ensemble de tables de pages et d'adresses virtuelles, il bénéficierait de la même protection que celle offerte par différents programmes. la mémoire du noyau serait simplement invisible. Mais avec l'adressage fractionné, les programmes utilisateur et le noyau utilisent la même plage d'adresses et, en principe, un programme utilisateur pourrait lire et écrire la mémoire du noyau.

Pour éviter cette situation manifestement indésirable, le processeur et le système d'adressage virtuel utilisent un concept de "sonneries" ou de "modes". Les processeurs x86 ont beaucoup d'anneaux, mais pour ce problème, seuls deux sont pertinents: "utilisateur" (anneau 3) et "superviseur" (anneau 0). Lors de l'exécution de programmes utilisateur normaux, le processeur est mis en mode utilisateur, anneau 3. Lors de l'exécution du code du noyau, le processeur est en mode superviseur de l'anneau 0, également appelé mode noyau.

Ces anneaux servent à protéger la mémoire du noyau contre les programmes utilisateur. Les tables de pages ne font pas que mapper des adresses virtuelles sur des adresses physiques; elles contiennent également des métadonnées sur ces adresses, y compris des informations sur les anneaux pouvant accéder à une adresse. Les entrées de la table des pages du noyau sont toutes marquées comme n'étant accessibles que pour l'anneau 0; les entrées du programme sont marquées comme étant accessibles depuis n'importe quel anneau. Si vous tentez d'accéder à la mémoire de l'anneau 0 pendant qu'il se trouve dans l'anneau 3, le processeur bloque l'accès et génère une exception. Il en résulte que les programmes utilisateur fonctionnant dans l'anneau 3 ne devraient rien apprendre sur le noyau et sa mémoire en anneau 0.

Au moins, c'est la théorie. La série de correctifs et de mises à jour montre que cela est tombé en panne quelque part. C'est là que réside le grand mystère.

Se déplacer entre les anneaux

Voici ce que nous savons. Chaque processeur moderne effectue une certaine quantité d’exécution spéculative. Par exemple, étant donné que certaines instructions ajoutent deux nombres, puis stockent le résultat en mémoire, un processeur peut effectuer cette addition de manière spéculative avant de déterminer si la destination en mémoire est réellement accessible et inscriptible. Dans le cas commun, où l'emplacement est Enregistrable, le processeur parvint à gagner du temps, tout comme l'arithmétique en parallèle pour déterminer quelle était la destination dans la mémoire. S'il découvre que l'emplacement n'est pas accessible - par exemple, un programme essayant d'écrire sur une adresse sans mappage ni emplacement physique - il générera une exception et l'exécution spéculative sera perdue.

Les processeurs Intel, plus précisément les processeurs AMD, permettent une exécution spéculative du code de la sonnerie 3 qui écrit dans la mémoire de la sonnerie 0. Les processeurs faire bloquer correctement l'écriture, mais l'exécution spéculative perturbe minutieusement l'état du processeur, car certaines données seront chargées dans le cache et le TLB afin de déterminer si l'écriture doit être autorisée. Cela signifie que certaines opérations seront plus rapides ou plus lentes, selon que leurs données sont encore dans le cache ou non. En outre, les processeurs Intel possèdent des fonctionnalités spéciales, telles que les SGX (Software Guard Extensions) introduites avec les processeurs Skylake, qui modifient légèrement la manière dont les tentatives d'accès à la mémoire sont gérées. Encore une fois, le processeur protège toujours la mémoire de l'anneau 0 des programmes de l'anneau 3, mais là encore, ses antémémoires et autres états internes sont modifiés, ce qui crée des différences mesurables.

Ce que nous ne savons pas encore, c’est combien de données de la mémoire du noyau peuvent être divulguées aux programmes utilisateur ou la facilité avec laquelle ces fuites peuvent se produire. Et quels processeurs Intel sont affectés? Encore une fois, ce n’est pas tout à fait clair, mais tout porte à croire que chaque puce Intel à exécution spéculative (ce qui correspond à tous les processeurs traditionnels introduits depuis le Pentium Pro de 1995) peut laisser échapper des informations de cette façon.

Les chercheurs de l’Université technique de Graz en Autriche ont été les premiers touchés par ce problème. La fuite d’informations qu’ils ont découverte était suffisante pour affaiblir la randomisation du format d’espace d’adresses en mode noyau (ASLR du noyau ou KASLR). ASLR est en quelque sorte un dernier effort pour empêcher l'exploitation des débordements de mémoire tampon. Avec ASLR, les programmes et leurs données sont placés à des adresses de mémoire aléatoires, ce qui complique un peu la tâche des attaquants pour exploiter les failles de sécurité. KASLR applique cette même randomisation au noyau afin que les données du noyau (y compris les tables de pages) et le code soient localisés de manière aléatoire.

Les chercheurs de Graz ont développé KAISER, un ensemble de correctifs du noyau Linux pour se défendre contre ce problème.

Si le problème était simplement qu'il permettait la dérandomisation de l'ASLR, ce ne serait probablement pas un désastre énorme. ASLR est une protection intéressante, mais elle est connue pour être imparfaite. Cela est censé être un obstacle pour les attaquants, pas une barrière impénétrable. La réaction du secteur - un changement assez important à la fois sous Windows et Linux, développé avec un certain secret - suggère que ce n’est pas seulement l’ASLR qui est vaincu et qu’une capacité plus générale de fuite d’informations à partir du noyau a été développée. En effet, les chercheurs ont commencé à tweeter qu'ils pouvaient fuir et lire des données de noyau arbitraires. Une autre possibilité est que la faille puisse être utilisée pour s'échapper d'une machine virtuelle et compromettre un hyperviseur.

La solution choisie par les développeurs Windows et Linux est sensiblement la même et dérivée de ce travail KAISER: les entrées de la table de pages du noyau ne sont plus partagées avec chaque processus. Sous Linux, cela s'appelle l'isolement de la table de pages de noyau (KPTI).

Avec les patchs, l'adresse mémoire est toujours divisée en deux; c'est juste que la moitié du noyau est presque vide. Ce n'est pas tout à fait vide, car quelques éléments du noyau doivent être mappés en permanence, que le processeur soit en cours d'exécution dans l'anneau 3 ou sonner 0, mais il est presque vide. Cela signifie que même si un programme utilisateur malveillant tente de sonder la mémoire du noyau et de divulguer des informations, il échouera. Il n'y a tout simplement rien à fuir. Les véritables tables de pages du noyau ne sont utilisées que lorsque le noyau lui-même est en cours d'exécution.

Cela sape la raison même de l'espace d'adressage divisé en premier lieu. Le TLB doit maintenant effacer toutes les entrées liées aux tables de pages du noyau réel chaque fois qu'il bascule vers un programme utilisateur, mettant ainsi fin à la sauvegarde des performances que le fractionnement permet.

L'impact de cela varie en fonction de la charge de travail. Chaque fois qu'un programme appelle le noyau pour lire sur le disque, envoyer des données au réseau, ouvrir un fichier, etc., cet appel coûtera un peu plus cher, car il forcera le vidage du TLB. et la vraie table de pages du noyau à charger. Les programmes qui n'utilisent pas beaucoup le noyau risquent de rencontrer une perte de 2 à 3% - il y a encore une surcharge car le noyau doit toujours être exécuté occasionnellement, pour gérer des tâches telles que le multitâche.

Mais les charges de travail qui appellent le noyau une tonne vont entraîner une baisse de performances bien supérieure. Dans un benchmark, un programme qui ne fait pratiquement rien autre qu’appel dans le noyau a vu ses performances chuter d’environ 50%; En d'autres termes, chaque appel dans le noyau a pris deux fois plus de temps avec le correctif qu'il n'en a fallu. Les benchmarks utilisant la mise en réseau en boucle de Linux ont également connu un franc succès, comme 17% dans ce benchmark de Postgres. L'impact réel des charges de travail de base de données utilisant un réseau réel devrait être moindre, car avec les réseaux réels, la surcharge d'appels dans le noyau a tendance à être dominée par la surcharge liée à l'utilisation du réseau réel.

Bien que les systèmes Intel soient connus pour présenter le défaut, ils ne sont peut-être pas les seuls concernés. Certaines plates-formes, telles que SPARC et IBM S390, sont immunisées contre le problème, car la gestion de la mémoire de leur processeur ne nécessite pas d'espace d'adressage fractionné ni de tables de pages de noyau partagées. Les systèmes d'exploitation de ces plates-formes ont toujours isolé leurs tables de pages du noyau de celles en mode utilisateur. Mais d'autres, comme ARM, peuvent ne pas être aussi chanceux; des correctifs comparables pour ARM Linux sont en cours de développement.