Qu'est-ce qu'une retpoline et comment ça marche?

244

Afin d'atténuer la divulgation de mémoire du noyau ou inter-processus (l' attaque Spectre ), le noyau Linux 1 sera compilé avec une nouvelle option , -mindirect-branch=thunk-externintroduite gccpour effectuer des appels indirects via une soi-disant retpoline .

Cela semble être un terme nouvellement inventé car une recherche Google ne s'avère que très récente (généralement en 2018).

Qu'est-ce qu'une retpoline et comment empêche-t-elle les récentes attaques de divulgation d'informations sur le noyau?


1 Ce n'est pas spécifique à Linux, cependant - une construction similaire ou identique semble être utilisée dans le cadre des stratégies d'atténuation sur d'autres systèmes d'exploitation.

BeeOnRope
la source
6
Un article de support intéressant de Google.
sgbj
2
oh, donc c'est prononcé / ˌtræmpəˈlin / (américain) ou / ˈtræmpəˌliːn / (britannique)
Walter Tross
2
Vous pourriez mentionner qu'il s'agit du noyau Linux, bien gccque cela pointe de cette façon! Je n'ai pas reconnu lkml.org/lkml/2018/1/3/780 comme sur le site de la liste de diffusion du noyau Linux, pas même une fois que j'y ai regardé (et j'ai reçu un instantané car il était hors ligne).
PJTraill
@PJTraill - a ajouté une balise de noyau Linux
RichVel
@PJTraill - bon point, j'ai mis à jour le texte de la question. Notez que je l'ai vu pour la première fois dans le noyau Linux en raison de son processus de développement relativement ouvert, mais sans aucun doute, des techniques identiques ou similaires sont utilisées comme atténuation à travers le spectre des systèmes d'exploitation ouverts et fermés. Je ne vois donc pas cela comme spécifique à Linux, mais le lien l'est certainement.
BeeOnRope

Réponses:

158

L'article mentionné par sgbj dans les commentaires écrits par Paul Turner de Google explique beaucoup plus en détail ce qui suit, mais je vais essayer:

Pour autant que je puisse reconstituer cela à partir des informations limitées pour le moment, une retpoline est un trampoline de retour qui utilise une boucle infinie qui n'est jamais exécutée pour empêcher le CPU de spéculer sur la cible d'un saut indirect.

L'approche de base peut être vue dans la branche du noyau d'Andi Kleen traitant de ce problème:

Il introduit le nouvel __x86.indirect_thunkappel qui charge la cible d'appel dont l'adresse mémoire (que j'appellerai ADDR) est stockée au-dessus de la pile et exécute le saut à l'aide d'une RETinstruction. Le thunk lui-même est ensuite appelé à l'aide de la macro NOSPEC_JMP / CALL , qui a été utilisée pour remplacer de nombreux (sinon tous) appels et sauts indirects. La macro place simplement la cible d'appel sur la pile et définit correctement l'adresse de retour, si nécessaire (notez le flux de contrôle non linéaire):

.macro NOSPEC_CALL target
    jmp     1221f            /* jumps to the end of the macro */
1222:
    push    \target          /* pushes ADDR to the stack */
    jmp __x86.indirect_thunk /* executes the indirect jump */
1221:
    call    1222b            /* pushes the return address to the stack */
.endm

Le placement de callà la fin est nécessaire pour que lorsque l'appel indirect est terminé, le flux de contrôle continue derrière l'utilisation de la NOSPEC_CALLmacro, de sorte qu'il peut être utilisé à la place d'un réguliercall

Le thunk lui-même ressemble à ceci:

    call retpoline_call_target
2:
    lfence /* stop speculation */
    jmp 2b
retpoline_call_target:
    lea 8(%rsp), %rsp 
    ret

Le flux de contrôle peut devenir un peu déroutant ici, alors permettez-moi de clarifier:

  • call pousse le pointeur d'instruction actuel (étiquette 2) vers la pile.
  • leaajoute 8 au pointeur de pile , éliminant efficacement le dernier mot-clé poussé, qui est la dernière adresse de retour (à l'étiquette 2). Après cela, le haut de la pile pointe à nouveau sur la véritable adresse de retour ADDR.
  • retsaute *ADDRet réinitialise le pointeur de pile au début de la pile d'appels.

En fin de compte, tout ce comportement équivaut pratiquement à sauter directement vers *ADDR. Le seul avantage que nous obtenons est que le prédicteur de branche utilisé pour les instructions de retour (Return Stack Buffer, RSB), lors de l'exécution de l' callinstruction, suppose que l' retinstruction correspondante passera à l'étiquette 2.

La partie après le label 2 n'est jamais exécutée, c'est simplement une boucle infinie qui, en théorie, remplirait le pipeline d' JMPinstructions avec des instructions. En utilisant LFENCE,PAUSE ou plus généralement, une instruction entraînant le blocage du pipeline d'instructions, le processeur ne perd ni temps ni énergie pour cette exécution spéculative. En effet, au cas où l'appel à retpoline_call_target reviendrait normalement, ce LFENCEserait la prochaine instruction à exécuter. C'est également ce que le prédicteur de branche prédira en fonction de l'adresse de retour d'origine (l'étiquette 2)

Pour citer le manuel d'architecture d'Intel:

Les instructions qui suivent un LFENCE peuvent être extraites de la mémoire avant le LFENCE, mais elles ne s'exécuteront pas tant que le LFENCE ne sera pas terminé.

Notez cependant que la spécification ne mentionne jamais que LFENCE et PAUSE provoquent le blocage du pipeline, donc je lis un peu entre les lignes ici.

Revenons maintenant à votre question initiale: la divulgation d'informations sur la mémoire du noyau est possible en raison de la combinaison de deux idées:

  • Même si l'exécution spéculative doit être sans effet secondaire lorsque la spéculation est erronée, l' exécution spéculative affecte toujours la hiérarchie du cache . Cela signifie que lorsqu'un chargement de mémoire est exécuté de manière spéculative, il peut toujours avoir provoqué l'expulsion d'une ligne de cache. Ce changement dans la hiérarchie du cache peut être identifié en mesurant soigneusement le temps d'accès à la mémoire qui est mappée sur le même ensemble de cache.
    Vous pouvez même divulguer quelques bits de mémoire arbitraire lorsque l'adresse source de la mémoire lue a elle-même été lue dans la mémoire du noyau.

  • Le prédicteur de branche indirecte des processeurs Intel utilise uniquement les 12 bits les plus bas de l'instruction source, il est donc facile d'empoisonner tous les 2 ^ 12 historiques de prédiction possibles avec des adresses mémoire contrôlées par l'utilisateur. Ceux-ci peuvent alors, lorsque le saut indirect est prédit dans le noyau, être exécutés de manière spéculative avec les privilèges du noyau. En utilisant le canal latéral de synchronisation du cache, vous pouvez ainsi fuir la mémoire du noyau arbitraire.

MISE À JOUR: Sur la liste de diffusion du noyau , il y a une discussion en cours qui m'amène à penser que les retpolines n'atténuent pas complètement les problèmes de prédiction de branche, comme lorsque le Return Stack Buffer (RSB) est vide, les architectures Intel plus récentes (Skylake +) retombent à la cible vulnérable Branch Buffer (BTB):

La retpoline en tant que stratégie d'atténuation échange les branches indirectes contre des retours, pour éviter d'utiliser des prédictions qui proviennent du BTB, car elles peuvent être empoisonnées par un attaquant. Le problème avec Skylake + est qu'un sous-dépassement RSB revient à utiliser une prédiction BTB, ce qui permet à l'attaquant de prendre le contrôle de la spéculation.

Tobias Ribizel
la source
Je ne pense pas que l'instruction LFENCE soit importante, l'implémentation de Google utilise une instruction PAUSE à la place. support.google.com/faqs/answer/7625886 Notez que la documentation que vous avez citée dit "ne s'exécutera pas" ne sera pas "ne sera pas exécutée de manière spéculative".
Ross Ridge
1
À partir de cette page de FAQ de Google: "Les instructions de pause dans nos boucles spéculatives ci-dessus ne sont pas nécessaires pour l'exactitude. Mais cela signifie que l'exécution spéculative non productive occupe moins d'unités fonctionnelles sur le processeur." Donc, cela ne supporte pas votre conclusion que LFENCE est la clé ici.
Ross Ridge
@RossRidge Je suis partiellement d'accord, pour moi cela ressemble à deux implémentations possibles d'une boucle infinie qui suggèrent au CPU de ne pas exécuter spéculativement le code suivant la PAUSE / LFENCE. Cependant, si la LFENCE a été exécutée de manière spéculative et n'a pas été annulée parce que la spéculation était correcte, cela contredirait l'affirmation selon laquelle elle ne sera exécutée qu'une fois le chargement de la mémoire terminé. (Sinon, l'ensemble des instructions qui ont été exécutées de manière spéculative devraient être annulées et exécutées à nouveau pour répondre aux spécifications)
Tobias Ribizel
1
Ceci a l'avantage de push/ retqu'il ne déséquilibre le prédicteur adresse de retour pile. Il y a une erreur (aller à lfenceavant que l'adresse de retour réelle ne soit utilisée), mais l'utilisation d'une callmodification + a rspcompensé cela ret.
Peter Cordes
1
oups, avantage sur push / ret(dans mon dernier commentaire). re: votre montage: le sous-dépassement RSB devrait être impossible car la retpoline comprend a call. Si la préemption du noyau faisait un changement de contexte là-bas, nous reprendrions l'exécution avec le RSB amorcé du calldans le planificateur. Mais peut-être qu'un gestionnaire d'interruption pourrait se terminer avec suffisamment de rets pour vider le RSB.
Peter Cordes
46

Une rétpoline est conçue pour protéger contre l'injection de cible de branche ( CVE-2017-5715 exploit d' ). Il s'agit d'une attaque dans laquelle une instruction de branchement indirect dans le noyau est utilisée pour forcer l'exécution spéculative d'un morceau de code arbitraire. Le code choisi est un "gadget" qui est en quelque sorte utile à l'attaquant. Par exemple, le code peut être choisi de manière à ce que les données du noyau fuient par la façon dont il affecte le cache. La retpoline empêche cet exploit en remplaçant simplement toutes les instructions de branchement indirect par une instruction de retour.

Je pense que la clé de la retpoline est juste la partie "ret", qu'elle remplace la branche indirecte par une instruction de retour afin que le CPU utilise le prédicteur de pile de retour au lieu du prédicteur de branche exploitable. Si une simple poussée et une instruction de retour étaient utilisées à la place, le code qui serait exécuté de manière spéculative serait le code auquel la fonction retournera finalement de toute façon, pas un gadget utile à l'attaquant. Le principal avantage de la partie trampoline semble être de maintenir la pile de retour, donc lorsque la fonction retourne réellement à son appelant, cela est prédit correctement.

L'idée de base derrière l'injection de cible de branche est simple. Il tire parti du fait que le CPU n'enregistre pas l'adresse complète de la source et de la destination des branches dans ses tampons cibles de branche. Ainsi, l'attaquant peut remplir le tampon en utilisant des sauts dans son propre espace d'adressage qui entraîneront des hits de prédiction lorsqu'un saut indirect particulier est exécuté dans l'espace d'adressage du noyau.

Notez que retpoline n'empêche pas la divulgation directe des informations sur le noyau, il empêche seulement les instructions de branchement indirectes d'être utilisées pour exécuter spéculativement un gadget qui divulguerait des informations. Si l'attaquant peut trouver d'autres moyens d'exécuter le gadget de manière spéculative, la retpoline n'empêche pas l'attaque.

L'article Specter Attacks: Exploiting Speculative Execution de Paul Kocher, Daniel Genkin, Daniel Gruss, Werner Haas, Mike Hamburg, Moritz Lipp, Stefan Mangard, Thomas Prescher, Michael Schwarz et Yuval Yarom donne l'aperçu suivant de la manière dont les branches indirectes peuvent être exploitées. :

Exploiter les branches indirectes. S'appuyant sur la programmation orientée retour (ROP), dans cette méthode, l'attaquant choisit un gadgetde l'espace d'adressage de la victime et influence la victime à exécuter le gadget de manière spéculative. Contrairement à ROP, l'attaquant ne s'appuie pas sur une vulnérabilité du code victime. Au lieu de cela, l'attaquant forme le Branch Target Buffer (BTB) pour mal interpréter une branche d'une instruction de branche indirecte à l'adresse du gadget, ce qui entraîne une exécution spéculative du gadget. Bien que les instructions exécutées de manière spéculative soient abandonnées, leurs effets sur le cache ne sont pas annulés. Ces effets peuvent être utilisés par le gadget pour divulguer des informations sensibles. Nous montrons comment, avec une sélection minutieuse d'un gadget, cette méthode peut être utilisée pour lire la mémoire arbitraire de la victime.

Pour mal comprendre le BTB, l'attaquant trouve l'adresse virtuelle du gadget dans l'espace d'adressage de la victime, puis effectue des branchements indirects vers cette adresse. Cette formation est effectuée à partir de l'espace d'adressage de l'attaquant, et peu importe ce qui réside à l'adresse du gadget dans l'espace d'adressage de l'attaquant; il suffit que la branche utilisée pour former les branches à utiliser la même adresse virtuelle de destination. (En fait, tant que l'attaquant gère les exceptions, l'attaque peut fonctionner même s'il n'y a pas de code mappé à l'adresse virtuelle du gadget dans l'espace d'adressage de l'attaquant.) Il n'y a pas non plus besoin d'une correspondance complète de l'adresse source de la succursale utilisée pour la formation et l'adresse de la succursale ciblée. Ainsi, l'attaquant dispose d'une grande flexibilité dans la mise en place de l'entraînement.

Une entrée de blog intitulée Lire la mémoire privilégiée avec un canal latéral par l'équipe de Project Zero chez Google fournit un autre exemple de la façon dont l'injection de cible de branche peut être utilisée pour créer un exploit fonctionnel.

Ross Ridge
la source
9

Cette question a été posée il y a un certain temps et mérite une réponse plus récente.

Résumé :

Les séquences de «retpoline» sont une construction logicielle qui permet d'isoler les branches indirectes de l'exécution spéculative. Cela peut être appliqué pour protéger les binaires sensibles (tels que les implémentations de système d'exploitation ou d'hyperviseur) contre les attaques par injection de cible de branche contre leurs branches indirectes.

Le mot " ret poline " est un portemanteau des mots "return" et "trampoline", tout comme l'amélioration " rel poline " a été inventée à partir de "relative call" et "trampoline". Il s'agit d'une construction de trampoline construite à l'aide d'opérations de retour qui garantit également au sens figuré que toute exécution spéculative associée «rebondira» à l'infini.

Afin d'atténuer la divulgation de mémoire du noyau ou inter-processus (l'attaque Spectre), le noyau Linux [1] sera compilé avec une nouvelle option, -mindirect-branch=thunk-externintroduite dans gcc pour effectuer des appels indirects via une soi-disant retpoline.

[1] Ce n'est pas spécifique à Linux, cependant - une construction similaire ou identique semble être utilisée dans le cadre des stratégies d'atténuation sur d'autres systèmes d'exploitation.

L'utilisation de cette option de compilateur protège uniquement contre Spectre V2 dans les processeurs affectés qui ont la mise à jour du microcode requise pour CVE-2017-5715. Il " fonctionnera " sur n'importe quel code (pas seulement sur un noyau), mais seul le code contenant des "secrets" mérite d'être attaqué.

Cela semble être un terme nouvellement inventé car une recherche Google ne s'avère que très récente (généralement en 2018).

Le compilateur LLVM avait un -mretpolinecommutateur depuis avant le 4 janvier 2018 . C'est à cette date que la vulnérabilité a été publiée pour la première fois . GCC a rendu ses correctifs disponibles le 7 janvier 2018.

La date CVE suggère que la vulnérabilité a été `` découverte '' en 2017, mais elle affecte certains des processeurs fabriqués au cours des deux dernières décennies (elle a donc probablement été découverte il y a longtemps).

Qu'est-ce qu'une retpoline et comment empêche-t-elle les récentes attaques de divulgation d'informations sur le noyau?

Tout d'abord, quelques définitions:

  • Trampoline - Parfois appelés trampolines à vecteurs de saut indirects, les emplacements de mémoire contiennent des adresses pointant vers des routines de service d'interruption, des routines d'E / S, etc. L'exécution saute dans le trampoline puis saute immédiatement ou rebondit, d'où le terme trampoline. GCC a traditionnellement pris en charge les fonctions imbriquées en créant un trampoline exécutable au moment de l'exécution lorsque l'adresse d'une fonction imbriquée est prise. Il s'agit d'un petit morceau de code qui réside normalement sur la pile, dans le cadre de pile de la fonction conteneur. Le trampoline charge le registre de chaîne statique puis saute à l'adresse réelle de la fonction imbriquée.

  • Thunk - Un thunk est un sous-programme utilisé pour injecter un calcul supplémentaire dans un autre sous-programme. Les Thunks sont principalement utilisés pour retarder un calcul jusqu'à ce que son résultat soit nécessaire, ou pour insérer des opérations au début ou à la fin de l'autre sous-programme

  • Mémorisation - Une fonction mémorisée "se souvient" des résultats correspondant à un ensemble d'entrées spécifiques. Les appels suivants avec des entrées mémorisées renvoient le résultat mémorisé plutôt que de le recalculer, éliminant ainsi le coût principal d'un appel avec des paramètres donnés de tous, sauf le premier appel effectué vers la fonction avec ces paramètres.

Très grossièrement, une rétpoline est un trampoline avec un retour en tant que thunk , pour « gâcher » la mémorisation dans le prédicteur de branche indirecte.

Source : La retpoline comprend une instruction PAUSE pour Intel, mais une instruction LFENCE est nécessaire pour AMD car sur ce processeur, l'instruction PAUSE n'est pas une instruction de sérialisation, donc la boucle pause / jmp utilisera une puissance excessive car elle est spéculée sur l'attente du retour de mal prédire la bonne cible.

Arstechnica a une explication simple du problème:

"Chaque processeur a un comportement architectural (le comportement documenté qui décrit le fonctionnement des instructions et dont les programmeurs dépendent pour écrire leurs programmes) et un comportement microarchitectural (le comportement d'une implémentation réelle de l'architecture). Ceux-ci peuvent diverger de manière subtile. Par exemple, sur le plan architectural, un programme qui charge une valeur à partir d'une adresse particulière en mémoire attendra que l'adresse soit connue avant d'essayer d'effectuer le chargement. Cependant, au niveau microarchitectural, le processeur peut essayer de deviner spéculativement l'adresse pour qu'elle puisse démarrer. charger la valeur à partir de la mémoire (qui est lente) avant même qu'elle ne soit absolument certaine de l'adresse à utiliser.

Si le processeur devine mal, il ignorera la valeur devinée et effectuera à nouveau le chargement, cette fois avec la bonne adresse. Le comportement défini architecturalement est ainsi préservé. Mais cette supposition erronée perturbera d'autres parties du processeur, en particulier le contenu du cache. Ces perturbations microarchitecturales peuvent être détectées et mesurées en chronométrant le temps nécessaire pour accéder aux données qui devraient (ou ne devraient pas) être dans le cache, permettant à un programme malveillant de faire des inférences sur les valeurs stockées en mémoire. ".

Extrait du document d'Intel: " Retpoline: A Branch Target Injection Mitigation " ( .PDF ):

"Une séquence de retpoline empêche l'exécution spéculative du processeur d'utiliser le" prédicteur de branche indirecte "(une façon de prédire le flux du programme) pour spéculer sur une adresse contrôlée par un exploit (satisfaisant l'élément 4 des cinq éléments de l'injection de cible de branche (variante 2 du spectre) ) exploitent la composition indiquée ci-dessus). ".

Remarque, l'élément 4 est: "L'exploit doit réussir à influencer cette branche indirecte pour mal interpréter spéculativement et exécuter un gadget. Ce gadget, choisi par l'exploit, fuit les données secrètes via un canal latéral, généralement par synchronisation du cache.".

Rob
la source