class: center, middle, hidden-table # Spectre et Meltdown ### Des failles qui vont vous hanter pendant longtemps
.full[ |Benjamin Bouvier|Sébastien Mériot| |----------------|----------------| |Senior Compiler Engineer @ Mozilla|Security Analyst @ OVH| |@bnjbvr|@smeriot| ]
#### INSHACK Lyon - 5 Avril 2018 ??? Notes de celui qui parle. Si vous voyez cela, on a raté un truc. --- class: center, middle, flex ![OVH](./img/ovh.png)
![Mozilla](./img/mozilla.png) ??? Se présenter, etc. --- .center[ # Comme un vent de panique ]
> Apologies for the discombobulation around today's disclosure. Obviously the original goal was to communicate this a little more coherently, but the unscheduled advances in the disclosure disrupted the efforts to pull this together more cleanly. > .right[ > ‐ LKML, 04/01/2018 9h10 CET ] --
- 2016 - Découverte du contournement de l'ASLR et de SMAP - Début 2017 - Découverte de Spectre & Meltdown - Mi-2017: Communication avec les acteurs concernés - Décembre 2017 - Patchs étranges dans le Kernel Linux (KAISER/KPTI) - 04/01/2018 - Early Disclosure - Chaos. --- class: middle, center # L'évolution des performances ### Ou comment monter un meuble en kit efficacement ??? Warning : métaphore filée, avec ses limites, etc. (Benjamin) --- background-image: url('./img/ikea-pipeline.png') background-size: 50% auto class: white-bg ??? (Benjamin) Un CPU lit une suite d'instructions de la meme maniere qu'un humain lit une notice IKEA : d'abord il va récupérer l'instruction en la lisant (fetch), puis il va l'interpréter et récupérer ce dont il a besoin avant de faire ce qu'il désire (decode), puis il va exécuter ce qu'il faut faire et stocker le résultat quelque part (execute). Première manière de faire : vis par vis : aller lire, prendre la vis, puis visser. Autre manière plus rapide : une fois que la premiere personne a fini de lire le manuel, une seconde personne peut lire le manuel pendant que la première commence à récupérer la vis. --- class: middle, center # Fetch, Decode, Execute, etc. .full[ | Clock | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |---------|----|----|----|----|----|---|---| | Fetch | A | | | B | | | C | | Decode | | A | | | B | | | | Execute | | | A | | | B | || ] ??? (Benjamin) Ces étapes sont mêmes décomposées en sous-cycles: entre decode et execute, va chercher les opérandes qui sont en mémoire ; execute va également écrire le résultat au bon endroit, etc. --- class: middle, center # Pipelining .full[ | Clock | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |---------|----|----|----|----|----|---|---| | Fetch | A | B | C | D | | | | | Decode | | A | B | C | D | | | | Execute | | | A | B | C | D | || ] ??? (Benjamin) Marche aussi conceptuellement s'il y a plus d'étapes. --- class: middle, center # Out-Of-Order --- background-image: url('./img/ikea-out-of-order.png') background-size: 100% auto class: white-bg ??? (Sebastien) --- class: split-the-code ## Out-of-order ```c int main(void) { int a = 0; int* b = &a; int c = 5; *b += 4; a++; c = c * c; if (a == *b) return a + c; return 0; } ``` --- class: split-the-code ## Out-of-order ```c int main(void) { int a = 0; int* b = &a; int c = 5; *b += 4; a++; c = c * c; if (a == *b) return a + c; return 0; } ``` ```asm # Dans la vraie vie... main: mov eax, 30 ret # Les codes présentés sont des # illustrations, nullement des # exploits (heureusement). ``` --- class: split-the-code ## Out-of-order ```c int main(void) { int a = 0; int* b = &a; int c = 5; *b += 4; a++; c = c * c; if (a == *b) return a + c; return 0; } ``` ```asm main: mov eax, 0 # a = 0 push eax lea edx, [ebp - 4] # b = &a mov ecx, 5 # c = 5 mov ebx, [edx] add ebx, 4 # b += 4 mov [ebp - 4], ebx mov edx, [ebp - 4] inc edx # a++ mov [ebp - 4], edx push edx mov eax, ecx imul ecx # c = c * c mov ecx, eax pop eax mov edx, [ebp - 4] cmp eax, edx # if (a == *b) je equals mov eax, 0 # return 0 add esp, 8 ret equals: add eax, ecx # return a + c add esp, 8 ret ``` --- class: split-the-code ## Out-of-order ```c *b += 4; // int* b = &a; a++; c = c * c; ``` ```asm add ebx, 4 # b += 4 * mov [ebp - 4], ebx ################################ mov edx, [ebp - 4] inc edx # a++ mov [ebp - 4], edx push edx ################################ mov eax, ecx imul ecx # c = c * c mov ecx, eax pop eax ``` --- class: split-the-code ## Out-of-order ```c *b += 4; // int* b = &a; a++; c = c * c; ``` ```asm add ebx, 4 # b += 4 * mov [ebp - 4], ebx ################################ mov edx, [ebp - 4] inc edx # a++ mov [ebp - 4], edx push edx ################################ * mov eax, ecx * imul ecx # c = c * c * mov ecx, eax pop eax ``` --- class: middle, center ## Spéculation --- background-image: url('./img/ikea-speculation.png') background-size: 100% auto class: white-bg ??? (Sebastien) jusqu'a 200 instructions exécutées spéculativement. --- class: split-the-code ## Spéculation ```c if (a == *b) return a + c; return 0; ``` ```asm pop eax * cmp eax, [ebp - 4] # if (a == *b) je equals mov eax, 0 # return 0 add esp, 8 ret equals: add eax, ecx # return a + c add esp, 8 ret ``` --- class: split-the-code ## Spéculation ```c void(*func_ptr) (int) = NULL; func_ptr = foo; func_ptr(5); ``` ```asm mov eax, 0 # func_ptr = NULL mov eax, [foo] # func_ptr = foo push 5 * call eax # func_ptr(5) sub edp, 4 ``` --- class: white-bg, center background-image: url('./img/cache.jpg') background-size: 50% auto # Cache ??? Localité temporelle : Les données utilisées récemment ont de grosses chances d'être réutilisées bientôt par la suite => les mettre à un endroit proche du processeur pour diminuer le temps d'accès (latence). => Exemple : tournevis. Localité spatiale : les données proches d'une donnée utilisée récemment vont probablement être utilisées par la suite => regrouper les accès mémoire en lignes de cache. => Exemple : vis. Application : plusieurs caches placés les uns à la suite des autres. Cache code vs cache données. Plus proche du processeur => plus rapide d'accès, plus cher aussi. - Politique d'évictions différentes (LRU pour L1, LRU/MRU pour L3) - Mapping peu documenté (reverse engineeré) --- class: middle, center # Cache - Latences .full[ | Type | Latence | |---------|----| | Cycle CPU (2Ghz) | ~0.5 ns | | L1 cache reference | ~0.5 ns | | L2 cache reference | 4 ns | | Main memory reference | 100 ns | | 1MB data sequential read from memory | 6,000 ns | | SSD random read | 16,000 ns | | 1MB data sequential read from SSD | 98,000 ns | | 1MB data sequential read from disk | 1,000,000 ns | | Disk seek | 3,000,000 ns | ] ??? Extrait de "Latency numbers every programmer should know." --- class: white-bg, middle, center ![Spectre logo](./img/spectre.svg) --- class: white-bg, center background-image: url('./img/clockwatch.jpg') background-size: 70% auto ## Cache-based side channel attack ??? (Benjamin, pendant que Seb fait la démo) --- background-image: url('./img/blackjack.jpg') background-size: 50% auto class: white-bg, center # Branch Target Buffer ??? Quelle est la cible d'une branche (conditionnelle ou non) ? Est-ce qu'une branche va être prise ou non ? --- # Variante 1 ```javascript let target_array = new Uint8Array(TARGET_SIZE); let probe_array = new Uint8Array(BIG_SIZE * 256); ... // Precondition: - target_array_length not in cache, // - index, target_array[index], probe_array in cache. let r = 0; if (index < target_array.length) { let read_value = target_array[index]; let x = probe_array[(read_value & 255) * BIG_SIZE]; r += x; } ``` -- ``` let hits = new Uint8Array(256); let r = 0; for (let i = 0; i < 256; i++) { let before = performance.now(); r = probe_array[i]; if (performance.now() - before < THRESHOLD) { hits[i]++; } } ``` ??? Disclaimer : ce code ne contient pas tous les éléments d'une attaque Spectre 2 et donc ne fonctionnera pas :) - Attaquant : entrainer la branche pour qu'elle soit toujours prise, afin que le prédicteur de branche. - s'arranger pour faire des appels valides avec des valeurs qui nous intéressent, afin d'entrainer les caches. - Race condition entre la vérification de la branche et l'exécution spéculative de la seconde lecture. - Besoin de timers haute précision pour mesurer le temps d'accès. Pas limité à des bounds checks : n'importe quelle condition suivie d'un accès mémoire peut être spéculé et doit donc être mitigé. Plusieurs preuves de concept dans tous les navigateurs. --- class: bigger # Mitigations : timers -- - API `SharedArrayBuffer` dans les navigateurs -- - Résolution de `performance.now()` diminuée -- - `performance.now()` += erreur aléatoire --- class: bigger # Mitigations : lfence `The software mitigation that Intel recommends is to insert a barrier to stop speculation in appropriate places.` -- .float-right[![nope](./img/nope.jpg)] --- class: bigger # Mitigations : mask Arrondir les tailles de tableaux à la puissance de deux supérieure. -- ``` // Say target_array.length is really 23, // rounded by the compiler to 32 = 0b00100000. // Then target_array.MASK = 0b00011111. if (index < target_array.length) { * // Added by compiler: * let new_index = index & target_array.MASK; let read_value = target_array[new_index]; * let new_index2 = ((read_value & 255) * BIG_SIZE) & probe_array.MASK; let x = probe_array[new_index2]; r += x; } ``` -- Mitigation également adoptée dans le kernel ```c mask = ~(long)(index | (size - 1 - index)) >> (BITS_PER_LONG - 1); ``` --- class: bigger # Mitigations : `cmovcc` -- `cmovcc`: check the state of one or more of the status flags in the EFLAGS register (CF, OF, PF, SF, and ZF) and perform a move operation if the flags are in a specified state (or condition). -- .center[ ![Pas de branche, pas de spéculation !](./img/no-branch-no-spec.jpg) ] --- class: bigger # Mitigations : `cmovcc` `cmovcc`: check the state of one or more of the status flags in the EFLAGS register (CF, OF, PF, SF, and ZF) and perform a move operation if the flags are in a specified state (or condition). ``` if (index < target_array.length) { * // Added by compiler: * let new_index = index; * new_index = cmovcc(new_index >= target_array.length, 0); let read_value = target_array[new_index]; * let new_index2 = ((read_value & 255) * BIG_SIZE); * new_index2 = cmovcc(new_index2 >= probe_array.length, 0); let x = probe_array[new_index2]; r += x; } ``` --- background-image: url('./img/blackjack.jpg') background-size: 50% auto class: white-bg, center # Branch History Buffer ??? Mapping entre un call indirect et les cibles possibles, lors des derniers appels récents (par ex. Haswell : 29 derniers appels). --- # Variante 2 -- ```javascript let probe_array = new Uint8Array(BIG_SIZE * 256); function targetFunc(username, password) { // do stuff. } let r = 0; function attacker(a, b, c, d, e, f) { // We want to read the password. r += probe_array[(b & 255) * BIG_SIZE]; } function main() { var f = attacker; for (var i = 0; i < 1000000; i++) { f(i, i, i, i, i, i); if (i === 999999) { // Precondition: memory pointed by targetFunc is not in cache. f = targetFunc; } } // timing side-channel } ``` ??? Disclaimer : ce code ne contient pas tous les éléments d'une attaque Spectre 2 et donc ne fonctionnera pas :) Une hash table en gros, qui ne prend en compte qu'une partie de l'adresse de la destination ! Il est donc possible d'entraîner le prédicteur de branches pour lui faire croire qu'une adresse de destination sera toujours atteinte, puis quand un autre call indirect a lieu, le code de la fonction prédite sera spéculativement exécuté jusqu'à ce que le processeur se rende compte qu'il s'agit en fait d'une erreur. Race condition entre le branch history buffer/branch target buffer et le déréférencement du pointeur de fonction : parce que le BHB permet de faire une prédiction de la cible de l'appel de fonction indirect, le code attaquant est spéculativement exécuté alors que le pointeur de fonction est déréférencé. --- class: center, bigger # Mitigation ``` gcc -mindirect-branch=thunk-extern ``` -- ![Stop using indirect calls](./img/indirect-call.jpg) --- # Mitigation : return trampoline ```asm ;; eax contains a function pointer call_eax: jmp setup_return_address inner_indirect_branch: call actual iloop_fence: lfence jmp iloop_fence actual: mov [esp], eax ret setup_return_address: call inner_indirect_branch next_instruction: ``` --- # Mitigation : return trampoline ```asm ;; eax contains a function pointer call_eax: * jmp setup_return_address ;; 1. jump to end inner_indirect_branch: call actual iloop_fence: lfence jmp iloop_fence actual: mov [esp], eax ret setup_return_address: * call inner_indirect_branch ;; 2. push &next_instruction onto the stack next_instruction: ``` .full[ |Stack| |--| |&next_instruction| ] --- # Mitigation : return trampoline ```asm ;; eax contains a function pointer call_eax: jmp setup_return_address ;; 1. jump to end inner_indirect_branch: * call actual ;; 3. push &actual onto the stack iloop_fence: lfence jmp iloop_fence actual: mov [esp], eax ret setup_return_address: call inner_indirect_branch ;; 2. push &next_instruction onto the stack next_instruction: ``` .full[ |Stack| |--| |&next_instruction| |&actual| ] --- # Mitigation : return trampoline ```asm ;; eax contains a function pointer call_eax: jmp setup_return_address ;; 1. jump to end inner_indirect_branch: call actual ;; 3. push &actual onto the stack iloop_fence: lfence jmp iloop_fence actual: * mov [esp], eax ;; 4. overwrites &actual on the stack with eax ret setup_return_address: call inner_indirect_branch ;; 2. push &next_instruction onto the stack next_instruction: ``` .full[ |Stack| |--| |&next_instruction| |~~&actual~~ -> eax| ] --- # Mitigation : return trampoline ```asm ;; eax contains a function pointer call_eax: jmp setup_return_address ;; 1. jump to end inner_indirect_branch: call actual ;; 3. push &actual onto the stack iloop_fence: lfence jmp iloop_fence actual: mov [esp], eax ;; 4. overwrites &actual on the stack with eax * ret ;; 5. indirect jump to eax :) setup_return_address: call inner_indirect_branch ;; 2. push &next_instruction onto the stack next_instruction: ``` .full[ |Stack| |--| |&next_instruction| ] --- # Mitigation : return trampoline ```asm ;; eax contains a function pointer call_eax: jmp setup_return_address ;; 1. jump to end inner_indirect_branch: call actual ;; 3. push &actual onto the stack iloop_fence: lfence jmp iloop_fence actual: mov [esp], eax ;; 4. overwrites &actual on the stack with eax ret ;; 5. indirect jump to eax :) * ;; 6. when function pointed by eax does a * ;; ret, it will return to &next_instruction. setup_return_address: call inner_indirect_branch ;; 2. push &next_instruction onto the stack next_instruction: ``` .full[ |Stack| |--| ] --- # Pourquoi ça marche ```asm ;; eax contains a function pointer call_eax: jmp setup_return_address inner_indirect_branch: * call actual ;; direct call iloop_fence: lfence jmp iloop_fence actual: mov [esp], eax ret setup_return_address: * call inner_indirect_branch ;; direct call next_instruction: ``` --- # Pourquoi ça marche ```asm ;; eax contains a function pointer call_eax: jmp setup_return_address inner_indirect_branch: call actual iloop_fence: lfence jmp iloop_fence actual: mov [esp], eax * ret ;; speculate here, but the return predictor speculates * ;; it returns at iloop_fence! setup_return_address: call inner_indirect_branch next_instruction: ``` --- class: center, bigger ## Mitigation : d'autres pistes explorées *Indirect Branch Restricted Speculation* (IBRS) *Indirect Branch Prediction Barrier* (IBPB)
.left[ Exploitation des bits de contrôle permettant de désactiver la spéculation au niveau du CPU - Rendu possible via un nouveau µCode - Performances désastreuses - Abandon au profit de retpoline ] --- class: middle, center background-image: url('./img/perfs-memes.jpeg') background-size: 100% auto --- background-image: url('./img/awfy.png') background-size: 100% auto class: white-bg --- background-image: url('./img/meltdown.svg') class: bottom, center, bigger --- class: middle, center, bigger # Meltdown ### Combinaison de 3 facteurs
Exécution Out-Of-Order + Attaque sur les caches + Lectures mémoire non vérifiées --- .center[ # Meltdown ] ```asm # rcx = kernel address # rbx = probe array retry: * mov al, byte [rcx] # Inaccessible kernel address shl rax, 12 jz retry mov rbx, [rbx + rax] ``` -- 1. Fetch, decode, execute. -- 2. Lecture en RAM. -- 3. Début du Out-Of-Order. 4. Préparation des µOpcodes. -- 5. Lecture en RAM terminée. 6. µOpcodes executés. -- 7. Exception: lecture d'adresse interdite. 8. Valeur des registres annulées. --- .center[ # Meltdown ] ```asm # rcx = kernel address # rbx = probe array retry: mov al, byte [rcx] # Inaccessible kernel address shl rax, 12 jz retry * mov rbx, [rbx + rax] # Covert channel ``` -- Possibilité de connaître la valeur qui était à l'adresse mémoire ! - Vider le cache (`clflush`) - Découvrir quelle valeur est en cache - byte = (offset >> 12) -- Félicitations ! - Vous avez lu la mémoire du Kernel depuis le User-land. --- background-image: url('./img/wat.jpg') background-size: 80% auto ### Lire la mémoire Kernel depuis le User-land --- ## Rappels sur la gestion de la mémoire MMU: Memory Management Unit - Adresses mémoires logiques <-> Adresses mémoires physiques - Création de pages mémoires avec des attributs: rwx - Ring:
Supervisor mode
vs
User mode
(CS register) --
.split-the-code.tiny-code[ ```c char *str = "Hello world"; *str = 'A'; printf("%s\n", str); ``` ```c char str[] = "Hello world"; *str = 'A'; printf("%s\n", str); ``` ] -- .split-the-code.tiny-code[ ```bash $ ./prog Segmentation fault # Segment .rodata = Read-Only ``` ```bash $ ./prog Aello world # Segment .text = Read/Write ``` ] --- ## Rappels sur la gestion de la mémoire ```c int main() { int pid; * pid = getpid(); // syscall printf("PID: %d\n", pid); return 0; } ``` -- - Lecture de l'adresse de
getpid
. - Execution de
getpid
: - Passage en mode
Supervisor
. - Lecture de la mémoire contenant le PID. - Passage en mode
User
pour rendre la main. --
=> Uniquement possible si la mémoire du kernel est adressable par le processus. --- ## Mitigations #### 1. KPTI - Kernel Page Table Isolation - La mémoire du kernel adressable par le processus est réduite au minimum: - Interrupt Descriptor Table (IDT) - Appels systèmes via des interruptions (0x80): - Le handler se déclenche. - Le registre CS est positionné en
Supervisor Mode
de facto. --- background-size: 80% auto background-image: url('./img/online-pti.jpg') Load average - Hébergement mutualisé Online --- ## Mitigations #### 2. UDEREF - Userland DEREFerence - Inclus dans grsecurity depuis longtemps - Discuté par la PaX Team depuis 2007 - Durcissement du partage de la mémoire entre Kernel-land et User-land - Evite les interruptions - Utilisation des registres GDT/LDT pour la segmentation mémoire - Réduit drastiquement la mémoire adressable par le processus --- # Le chaos - Intel émet des mises à jour de micro-code [(source)](https://liliputing.com/2018/02/intel-rolls-spectre-updates-6th-7th-8th-gen-core-chips.html). -- - Mais pas sur les CPUs les plus anciens [(source)](https://liliputing.com/2018/04/intel-wont-release-spectre-patches-for-some-older-chips-after-all.html). -- - La 8ème génération de processeurs Intel aura une protection intégrée contre Spectre [(source)](https://www.techspot.com/news/73736-intel-upcoming-8th-generation-cascade-lake-processors-have.html). -- - {Spectre,Meltdown}Prime, variantes basées sur un side channel prime&probe + variante multi-coeurs [(source)](https://www.theregister.co.uk/2018/02/14/meltdown_spectre_exploit_variants/). -- - Inspirée de Spectre 2, BranchScope permet de détecter si une branche est prise ou non [(source)](https://arstechnica.com/gadgets/2018/03/its-not-just-spectre-researchers-reveal-more-branch-prediction-attacks/). -- - Un patch de Windows pour Meltdown a permis la lecture de toute la mémoire à une vitesse en gigabits par seconde [(source)](https://blog.frizk.net/2018/03/total-meltdown.html). --- class: center, middle, hidden-table # Spectre et Meltdown ### Questions? ![OVH](./img/ikea-good-work.png) .full[ |Benjamin Bouvier|Sébastien Mériot| |----------------|----------------| |Senior Compiler Engineer @ Mozilla|Security Analyst @ OVH| |@bnjbvr|@smeriot| ] #### INSHACK Lyon - 5 Avril 2018 https://pandipanda69.github.io/INSHACK2k18-Meltdown-Spectre --- # Références / Sources - Blackjack : https://commons.wikimedia.org/wiki/File%3ABlackjack_board.JPG - Clockwatch : Veri Ivanova https://unsplash.com/photos/p3Pj7jOYvnM - https://www.spectreattack.com - https://www.meltdownattack.com - https://webkit.org/blog/8048/what-spectre-and-meltdown-mean-for-webkit/ - KAISER: hiding the kernel from user space, https://lwn.net/Articles/738975/ - Meltdown/Spectre mitigation for 4.15 and beyond, https://lwn.net/Articles/744287/ - UDEREF, https://grsecurity.net/~spender/uderef.txt - Google Project Zero Blogpost, https://googleprojectzero.blogspot.fr/2018/01/reading-privileged-memory-with-side.html --- class: middle, center # Merci ! ??? Commentaires, questions?