Meltdown și Spectre, explicate

deși în zilele noastre sunt cunoscut mai ales pentru rețele la nivel de aplicație și sisteme distribuite, am petrecut prima parte a carierei mele lucrând la sisteme de operare și hipervizori. Mențin o fascinație profundă cu detaliile de nivel scăzut ale modului în care funcționează procesoarele și sistemele software moderne. Când au fost anunțate vulnerabilitățile recente Meltdown și Spectre, am săpat în informațiile disponibile și am fost dornic să aflu mai multe.

vulnerabilitățile sunt uluitoare; Aș susține că sunt una dintre cele mai importante descoperiri din domeniul informaticii din ultimii 10-20 de ani. Atenuările sunt, de asemenea, dificil de înțeles și informații exacte despre ele este greu de găsit. Acest lucru nu este surprinzător, având în vedere natura lor critică. Atenuarea vulnerabilităților a necesitat luni de muncă secretă de către toți furnizorii majori de CPU, Sistem de operare și cloud. Faptul că problemele au fost ținute sub acoperire timp de 6 luni, când literalmente sute de oameni lucrau probabil la ele este uimitor.

deși s-au scris multe despre Meltdown și Spectre de la anunțul lor, nu am văzut o introducere bună la nivel mediu a vulnerabilităților și atenuărilor. În această postare voi încerca să corectez acest lucru oferind o introducere blândă în fundalul hardware și software necesar pentru a înțelege vulnerabilitățile, o discuție despre vulnerabilitățile în sine, precum și o discuție despre atenuările actuale.

notă importantă: pentru că nu am lucrat direct la atenuări și nu lucrez la Intel, Microsoft, Google, Amazon, Red Hat etc. unele dintre detaliile pe care le voi oferi s-ar putea să nu fie în întregime corecte. Am pus cap la cap acest post pe baza cunoștințelor mele despre modul în care funcționează aceste sisteme, documentația disponibilă publicului și patch-urile/discuțiile postate la LKML și xen-devel. Mi-ar plăcea să fiu corectat dacă oricare dintre aceste postări este inexactă, deși mă îndoiesc că se va întâmpla în curând, având în vedere cât de mult din acest subiect este încă acoperit de NDA.

în această secțiune voi oferi câteva informații necesare pentru a înțelege vulnerabilitățile. Secțiunea glosează o cantitate mare de detalii și se adresează cititorilor cu o înțelegere limitată a hardware-ului computerului și a software-ului sistemelor.

memoria virtuală

memoria virtuală este o tehnică utilizată de toate sistemele de Operare încă din anii 1970. oferă un strat de abstractizare între aspectul adresei de memorie pe care îl vede majoritatea software-ului și dispozitivele fizice care susțin acea memorie (RAM, discuri etc.). La un nivel ridicat, permite aplicațiilor să utilizeze mai multă memorie decât are de fapt mașina; aceasta oferă o abstractizare puternică care facilitează multe sarcini de programare.

Figura 1: Memorie virtuală

figura 1 prezintă un computer simplist cu 400 de octeți de memorie stabiliți în „pagini” de 100 de octeți (computerele reale folosesc puteri de doi, de obicei 4096). Computerul are două procese, fiecare cu 200 de octeți de memorie pe 2 pagini fiecare. Procesele ar putea rula același cod folosind adrese fixe în intervalul 0-199 octeți, cu toate acestea sunt susținute de memorie fizică discretă, astfel încât să nu se influențeze reciproc. Deși sistemele de operare moderne și computerele folosesc memoria virtuală într-un mod substanțial mai complicat decât ceea ce este prezentat în acest exemplu, premisa de bază prezentată mai sus este valabilă în toate cazurile. Sistemele de operare abstractizează adresele pe care aplicația le vede din resursele fizice care le susțin.

traducerea adreselor virtuale în adrese fizice este o operație atât de obișnuită în computerele moderne încât, dacă sistemul de operare ar trebui implicat în toate cazurile, computerul ar fi incredibil de lent. Hardware-ul CPU Modern oferă un dispozitiv numit tampon de traducere Lookaside (TLB) care memorează în cache mapările utilizate recent. Acest lucru permite procesoarelor să efectueze traducerea adreselor direct în hardware majoritatea timpului.

Figura 2: Traducerea memoriei virtuale

Figura 2 prezintă fluxul de traducere a adresei:

  1. un program preia o adresă virtuală.
  2. CPU încearcă să-l traducă folosind TLB. Dacă se găsește adresa, se folosește traducerea.
  3. dacă adresa nu este găsită, CPU consultă un set de „tabele de pagini” pentru a determina maparea. Tabelele de pagini sunt un set de pagini de memorie fizică furnizate de sistemul de operare într-o locație pe care hardware-ul le poate găsi (de exemplu, Registrul CR3 pe hardware-ul x86). Tabelele de pagini mapează adresele virtuale la adresele fizice și conțin, de asemenea, metadate, cum ar fi permisiunile.
  4. dacă tabelul de pagini conține o mapare, acesta este returnat, memorat în cache în TLB și utilizat pentru căutare. Dacă tabelul de pagini nu conține o mapare, o” eroare de pagină ” este ridicată la sistemul de operare. O eroare de pagină este un tip special de întrerupere care permite sistemului de Operare să preia controlul și să determine ce să facă atunci când există o mapare lipsă sau nevalidă. De exemplu, sistemul de operare poate termina programul. S-ar putea aloca, de asemenea, unele de memorie fizică și harta-l în procesul de. Dacă un handler de erori de pagină continuă execuția, Noua mapare va fi utilizată de TLB.

Figura 3: mapări de memorie virtuală utilizator / kernel

figura 3 prezintă o imagine puțin mai realistă a modului în care arată memoria virtuală într-un computer modern (pre — Meltdown-mai multe despre acest lucru mai jos). În această configurare avem următoarele caracteristici:

  • memoria Kernel-ului este afișată în roșu. Acesta este conținut în intervalul de adrese fizice 0-99. Memoria Kernel este o memorie specială pe care numai sistemul de operare ar trebui să o poată accesa. Programele utilizatorilor nu ar trebui să le poată accesa.
  • memoria utilizatorului este afișată în gri.
  • memoria fizică nealocată este afișată în albastru.

în acest exemplu, începem să vedem câteva dintre caracteristicile utile ale memoriei virtuale. În primul rând:

  • memoria utilizatorului în fiecare proces este în intervalul virtual 0-99, dar susținută de memorie fizică diferită.
  • memoria Kernel-ului în fiecare proces este în intervalul virtual 100-199, dar susținută de aceeași memorie fizică.așa cum am menționat Pe scurt în secțiunea anterioară, fiecare pagină are biți de permisiune asociați. Chiar dacă memoria kernel-ului este mapată în fiecare proces utilizator, atunci când procesul rulează în modul utilizator, acesta nu poate accesa memoria kernel-ului. Dacă un proces încearcă să facă acest lucru, va declanșa o eroare de pagină în care sistemul de Operare o va termina. Cu toate acestea, atunci când procesul rulează în modul kernel (de exemplu în timpul unui apel de sistem), procesorul va permite accesul.

    în acest moment, voi observa că acest tip de mapare duală (fiecare proces având nucleul mapat direct în el) a fost o practică standard în proiectarea sistemului de operare de peste treizeci de ani din motive de performanță (apelurile de sistem sunt foarte frecvente și ar dura mult timp pentru a remapa nucleul sau spațiul utilizatorului la fiecare tranziție).

    CPU cache topologie

    >

    figura 4: subiect CPU, Core, pachet, și topologie cache.

    următoarea informație de fundal necesară pentru a înțelege vulnerabilitățile este topologia procesorului și cache-ului procesoarelor moderne. Figura 4 prezintă o topologie generică care este comună majorității procesoarelor moderne. Este compus din următoarele componente:

    • unitatea de bază de execuție este „firul CPU” sau „firul hardware” sau „hyper-thread.”Fiecare fir CPU conține un set de registre și capacitatea de a executa un flux de cod mașină, la fel ca un fir de software.
    • firele CPU sunt conținute într-un ” nucleu CPU.”Majoritatea procesoarelor moderne conțin două fire pe nucleu.
    • procesoarele moderne conțin, în general, mai multe niveluri de memorie cache. Nivelurile cache mai aproape de firul CPU sunt mai mici, mai rapide și mai scumpe. Cu cât este mai departe de CPU și mai aproape de memoria principală, memoria cache este mai mare, mai lentă și mai puțin costisitoare.
    • designul tipic modern al procesorului utilizează o memorie cache L1/L2 pe nucleu. Aceasta înseamnă că fiecare fir CPU de pe miez folosește aceleași cache-uri.
    • mai multe nuclee CPU sunt conținute într-un „pachet CPU.”Procesoarele moderne pot conține peste 30 de nuclee (60 de fire) sau mai mult pe pachet.
    • toate nucleele CPU din pachet Partajează de obicei o memorie cache L3.
    • pachetele CPU se încadrează în ” prize.”Majoritatea computerelor de consum sunt un singur soclu, în timp ce multe servere datacenter au mai multe prize.

    execuție speculativă

    figura 5: motor de execuție CPU modern (sursă: Google images)

    piesa finală de informații de fond necesare pentru a înțelege vulnerabilitățile este o tehnică CPU modernă cunoscută sub numele de „execuție speculativă.”Figura 5 prezintă o diagramă generică a motorului de execuție în interiorul unui procesor modern.

    ideea principală este că procesoarele moderne sunt incredibil de complicate și nu execută pur și simplu instrucțiunile mașinii în ordine. Fiecare fir CPU are un motor de conducte complicat, care este capabil de a executa instrucțiuni de ordine. Motivul pentru aceasta are legătură cu cache-ul. După cum am discutat în secțiunea anterioară, fiecare procesor folosește mai multe niveluri de cache. Fiecare cache dor adaugă o cantitate substanțială de timp de întârziere la executarea programului. Pentru a atenua acest lucru, procesoarele sunt capabile să execute înainte și în afara ordinii în așteptarea încărcărilor de memorie. Aceasta este cunoscută sub numele de execuție speculativă. Următorul fragment de cod demonstrează acest lucru.

    if (x < array1_size) {
    y = array2 * 256];
    }

    în fragmentul anterior, imaginați-vă că array1_size nu este disponibil în cache, dar adresa array1 este. CPU-ul ar putea ghici (specula) că x este mai mic decât array1_size și mergeți mai departe și efectuați calculele din instrucțiunea if. Odată cearray1_size este citit din memorie, procesorul poate determina dacă a ghicit corect. Dacă a făcut-o, poate continua să economisească o grămadă de timp. Dacă nu, se poate arunca calculele speculative și începe peste. Acest lucru nu este mai rău decât dacă ar fi așteptat în primul rând.

    Un alt tip de execuție speculativă este cunoscut sub numele de predicție indirectă a ramurilor. Acest lucru este extrem de frecvent în programele moderne datorită expedierii virtuale.

    class Base {
    public:
    virtual void Foo() = 0;
    };class Derived : public Base {
    public:
    void Foo() override { … }
    };Base* obj = new Derived;
    obj->Foo();

    (sursa fragmentului anterior este această postare)

    modul în care fragmentul anterior este implementat în codul mașinii este de a încărca „v-table” sau „virtual dispatch table” din locația de memorie pe careobj o indică și apoi o apelează. Deoarece această operație este atât de comună, procesoarele moderne au diverse cache-uri interne și vor ghici adesea (specula) unde va merge ramura indirectă și va continua execuția în acel moment. Din nou, dacă CPU ghicește corect, poate continua să economisească o grămadă de timp. Dacă nu, se poate arunca calculele speculative și începe peste.

    vulnerabilitatea Meltdown

    după ce am acoperit acum toate informațiile de fond, ne putem scufunda în vulnerabilități.

    Rogue data cache load

    prima vulnerabilitate, cunoscută sub numele de Meltdown, este surprinzător de simplă de explicat și aproape banală de exploatat. Codul de exploatare arată aproximativ după cum urmează:

    1. uint8_t* probe_array = new uint8_t;
    2. // ... Make sure probe_array is not cached
    3. uint8_t kernel_memory = *(uint8_t*)(kernel_address);
    4. uint64_t final_kernel_memory = kernel_memory * 4096;
    5. uint8_t dummy = probe_array;
    6. // ... catch page fault
    7. // ... determine which of 256 slots in probe_array is cached

    să facem fiecare pas de mai sus, să descriem ce face și cum duce la citirea memoriei întregului computer dintr-un program utilizator.

    1. În prima linie, este alocată o „matrice de sonde”. Aceasta este memoria în procesul nostru, care este folosit ca un canal lateral pentru a prelua date din kernel. Cum se face acest lucru va deveni evident în curând.
    2. în urma alocării, atacatorul se asigură că niciuna dintre memoria din matricea probe nu este memorată în cache. Există diferite modalități de a realiza acest lucru, dintre care cea mai simplă include instrucțiuni specifice procesorului pentru a șterge o locație de memorie din memoria cache.
    3. atacatorul continuă apoi să citească un octet din spațiul de adrese al kernel-ului. Amintiți-vă din discuția noastră anterioară despre memoria virtuală și tabelele de pagini că toate nucleele moderne mapează de obicei întregul spațiu virtual de adrese al nucleului în procesul utilizatorului. Sistemele de operare se bazează pe faptul că fiecare intrare în tabelul de pagini are setări de permisiune și că programele în modul utilizator nu au voie să acceseze memoria kernel-ului. Orice astfel de acces va duce la o eroare de pagină. Aceasta este într-adevăr ceea ce se va întâmpla în cele din urmă la Pasul 3.
    4. cu toate acestea, procesoarele moderne efectuează, de asemenea, executarea speculativă și vor executa înainte de instrucțiunea de defectare. Astfel, pașii 3-5 se pot executa în conducta procesorului înainte ca defecțiunea să fie ridicată. În acest pas, octetul memoriei kernel-ului (care variază între 0-255) este înmulțit cu dimensiunea paginii sistemului, care este de obicei 4096.
    5. în acest pas, octetul înmulțit al memoriei nucleului este apoi utilizat pentru a citi din matricea sondei într-o valoare falsă. Înmulțirea octetului cu 4096 este de a evita o caracteristică CPU numită „prefetcher” să citească mai multe date decât dorim în cache.
    6. prin acest pas, CPU-ul și-a dat seama de greșeala sa și a revenit la Pasul 3. Cu toate acestea, rezultatele instrucțiunilor speculate sunt încă vizibile în cache. Atacatorul folosește funcționalitatea sistemului de operare pentru a prinde instrucțiunile de eroare și pentru a continua execuția (de exemplu, manipularea SIGFAULT).
    7. la Pasul 7, atacatorul iterează și vede cât timp este nevoie pentru a citi fiecare dintre cei 256 de octeți posibili din matricea probe care ar fi putut fi indexați de memoria kernel-ului. CPU-ul va fi încărcat una dintre locații în cache și această locație se va încărca substanțial mai repede decât toate celelalte locații (care trebuie citite din memoria principală). Această locație este valoarea octetului din memoria kernel-ului.folosind tehnica de mai sus și faptul că este o practică standard pentru sistemele de operare moderne să mapeze toată memoria fizică în spațiul virtual de adrese al kernel-ului, un atacator poate citi întreaga memorie fizică a computerului.

      acum, s-ar putea să vă întrebați: „ați spus că tabelele de pagini au biți de permisiune. Cum se poate ca codul modului Utilizator să fi putut accesa în mod speculativ memoria kernel-ului?”Motivul este că acesta este un bug în procesoarele Intel. În opinia mea, nu există niciun motiv întemeiat, performanță sau altfel, pentru ca acest lucru să fie posibil. Amintiți-vă că tot accesul la memoria virtuală trebuie să aibă loc prin TLB. Este ușor posibil în timpul executării speculative pentru a verifica dacă o mapare cache are permisiuni compatibile cu nivelul curent de privilegiu de funcționare. Hardware-ul Intel pur și simplu nu face acest lucru. Alți furnizori de procesoare efectuează o verificare a permisiunii și blochează execuția speculativă. Astfel, din câte știm, Meltdown este doar o vulnerabilitate Intel.

      Edit: se pare că cel puțin un procesor ARM este, de asemenea, susceptibil de topire, așa cum este indicat aici și aici.

      Meltdown mitigations

      Meltdown este ușor de înțeles, banal de exploatat și, din fericire, are și o atenuare relativ simplă (cel puțin conceptual — dezvoltatorii kernel-ului s-ar putea să nu fie de acord că este simplu de implementat).

      kernel page Table isolation (KPTI)

      reamintim că în secțiunea despre memoria virtuală am descris că toate sistemele de operare moderne folosesc o tehnică în care memoria kernel-ului este mapată în fiecare mod utilizator proces spațiu de adrese de memorie virtuală. Acest lucru este atât din motive de performanță, cât și din motive de simplitate. Înseamnă că atunci când un program efectuează un apel de sistem, nucleul este gata de utilizare fără alte lucrări. Fix pentru Meltdown este de a nu mai efectua această mapare dublă.

      Figura 6: kernel page table isolation

      figura 6 prezintă o tehnică numită kernel page table isolation (KPTI). Acest lucru se reduce practic la maparea memoriei kernel-ului într-un program atunci când rulează în spațiul utilizatorului. În cazul în care nu există nici o cartografiere prezent, executarea speculativă nu mai este posibilă și va vina imediat.

      pe lângă faptul că complică managerul de memorie virtuală (VMM) al sistemului de operare, fără asistență hardware, această tehnică va încetini considerabil sarcinile de lucru care fac un număr mare de tranziții în modul utilizator la modul kernel, datorită faptului că tabelele de pagini trebuie modificate la fiecare tranziție și TLB trebuie spălat (având în vedere că TLB poate ține la mapări învechite).

      procesoarele x86 mai noi au o caracteristică cunoscută sub numele de ASID (address space ID) sau PCID (process context ID) care poate fi utilizată pentru a face această sarcină substanțial mai ieftină (ARM și alte microarhitecturi au această caracteristică de ani de zile). PCID permite ca un ID să fie asociat cu o intrare TLB și apoi să spele doar intrările TLB cu acel ID. Utilizarea PCID face KPTI mai ieftin, dar încă nu este gratuit.în rezumat, Meltdown este o vulnerabilitate extrem de gravă și ușor de exploatat. Din fericire, are o atenuare relativ simplă, care a fost deja implementată de toți furnizorii majori de sisteme de operare, avertismentul fiind că anumite sarcini de lucru vor rula mai lent până când hardware-ul viitor va fi conceput în mod explicit pentru separarea spațiului de adrese descris.

      Spectre vulnerabilitate

      Spectre împărtășește unele proprietăți ale Meltdown și este compus din două variante. Spre deosebire de Meltdown, Spectre este substanțial mai greu de exploatat, dar afectează aproape toate procesoarele moderne produse în ultimii douăzeci de ani. În esență, Spectre este un atac împotriva proiectării moderne a procesorului și a sistemului de operare față de o vulnerabilitate specifică de securitate.

      Bounds check bypass (Spectre variant 1)

      prima variantă Spectre este cunoscută sub numele de „bounds check bypass.”Acest lucru este demonstrat în următorul fragment de cod (care este același fragment de cod pe care l-am folosit pentru a introduce execuția speculativă de mai sus).

      if (x < array1_size) {
      y = array2 * 256];
      }

      în exemplul anterior, presupunem următoarea secvență de evenimente:

      1. atacatorul controlează x.
      2. array1_size nu este memorat în cache.
      3. array1 este memorat în cache.
      4. CPU ghicește căx este mai mică decâtarray1_size. (Procesoarele folosesc diferiți algoritmi și euristici proprietare pentru a determina dacă să speculeze, motiv pentru care detaliile de atac pentru Spectre variază între furnizorii de procesoare și modele.)
      5. CPU execută corpul instrucțiunii if în timp ce așteaptăarray1_size să se încarce, afectând memoria cache într-un mod similar cu Meltdown.
      6. atacatorul poate determina apoi valoarea reală aarray1 printr-una din diferite metode. (A se vedea lucrarea de cercetare pentru mai multe detalii despre atacurile de inferență cache.)

      Spectre este considerabil mai dificil de exploatat decât Meltdown, deoarece această vulnerabilitate nu depinde de escaladarea privilegiilor. Atacatorul trebuie să convingă nucleul să ruleze cod și să speculeze incorect. De obicei, atacatorul trebuie să otrăvească motorul speculațiilor și să-l păcălească să ghicească incorect. Acestea fiind spuse, cercetătorii au arătat mai multe exploatări de dovadă a conceptului.

      vreau să reiterez ce descoperire cu adevărat incredibilă este această exploatare. Nu consider personal acest lucru un defect de proiectare a procesorului, cum ar fi Meltdown în sine. Consider că aceasta este o revelație fundamentală despre modul în care hardware-ul și software-ul modern lucrează împreună. Faptul că cache-urile CPU pot fi utilizate indirect pentru a afla despre modelele de acces este cunoscut de ceva timp. Faptul că cache-urile CPU pot fi folosite ca un canal lateral pentru a arunca memoria computerului este uluitor, atât conceptual, cât și în implicațiile sale.

      injecție țintă de ramură (Spectre varianta 2)

      reamintim că ramificarea indirectă este foarte frecventă în programele moderne. Varianta 2 a Spectre utilizează predicția indirectă a ramurilor pentru a otrăvi CPU-ul în executarea speculativă într-o locație de memorie pe care altfel nu ar fi executat-o niciodată. Dacă executarea acestor instrucțiuni poate lăsa starea în urmă în memoria cache care poate fi detectată folosind atacuri de inferență cache, atacatorul poate apoi să arunce toată memoria kernel-ului. La fel ca Spectre variant 1, Spectre variant 2 este mult mai greu de exploatat decât Meltdown, cu toate acestea, cercetătorii au demonstrat exploatări de probă de concept ale variant 2.

      atenuarea spectrului

      atenuarea spectrului este substanțial mai interesantă decât atenuarea topirii. De fapt, lucrarea Academic Spectre scrie că în prezent nu există atenuări cunoscute. Se pare că în culise și în paralel cu activitatea academică, Intel (și probabil alți furnizori de CPU) și principalii furnizori de sisteme de operare și cloud lucrează furios de luni de zile pentru a dezvolta atenuări. În această secțiune voi acoperi diferitele atenuări care au fost dezvoltate și desfășurate. Aceasta este secțiunea pe care sunt cel mai tulbure, deoarece este incredibil de dificil să obțin informații exacte, așa că pun lucrurile împreună din diverse surse.

      analiza statică și împrejmuirea (atenuarea variantei 1)

      singura atenuare cunoscută a variantei 1 (bypass de verificare a limitelor) este analiza statică a codului pentru a determina secvențele de cod care ar putea fi controlate de atacator pentru a interfera cu speculațiile. Secvențele de cod vulnerabile pot avea o instrucțiune de serializare, cum ar fi lfence inserată, care oprește execuția speculativă până când toate instrucțiunile până la gard au fost executate. Trebuie să aveți grijă la introducerea instrucțiunilor de gard, deoarece prea multe pot avea un impact sever asupra performanței.

      Retpoline (varianta 2 de atenuare)

      prima variantă Spectre 2 (branch target injection) de atenuare a fost dezvoltat de Google și este cunoscut sub numele de „retpoline.”Nu îmi este clar dacă a fost dezvoltat izolat de Google sau de Google în colaborare cu Intel. Aș specula că a fost dezvoltat experimental de Google și apoi verificat de inginerii hardware Intel, dar nu sunt sigur. Detalii despre abordarea „retpoline” pot fi găsite în lucrarea Google pe această temă. Le voi rezuma aici (sunt glossing peste unele detalii, inclusiv underflow, care sunt acoperite în hârtie).

      Retpoline se bazează pe faptul că apelarea și întoarcerea de la funcții și manipulările stivei asociate sunt atât de frecvente în programele de calculator, încât procesoarele sunt puternic optimizate pentru a le efectua. (Dacă nu sunteți familiarizați cu modul în care stiva funcționează în legătură cu apelarea și revenirea din funcții, acest post este un primer bun.) Pe scurt, atunci când se efectuează un” apel”, adresa de retur este împinsă pe stivă. „ret” afișează adresa de retur și continuă execuția. Hardware-ul de execuție speculativă își va aminti adresa de returnare împinsă și va continua în mod speculativ execuția în acel moment.

      construcția retpoline înlocuiește un salt indirect la locația de memorie stocată în registrul r11:

      jmp *%r11

      cu:

      call set_up_target; (1)
      capture_spec: (4)
      pause;
      jmp capture_spec;
      set_up_target:
      mov %r11, (%rsp); (2)
      ret; (3)

      Să vedem ce face codul de asamblare anterior pas cu pas și cum atenuează injecția țintă a ramurii.

      1. În acest pas, codul apelează o locație de memorie cunoscută la momentul compilării, deci este un offset codat greu și nu indirect. Aceasta plasează adresa de retur acapture_spec pe stivă.
      2. adresa de retur de la apel este suprascrisă cu ținta reală de salt.
      3. o întoarcere este efectuată pe ținta reală.
      4. atunci când CPU execută speculativ, se va întoarce într-o buclă infinită! Amintiți-vă că procesorul va specula înainte până când încărcările de memorie sunt complete. În acest caz, speculația a fost manipulată pentru a fi capturată într-o buclă infinită care nu are efecte secundare observabile unui atacator. Când CPU-ul execută în cele din urmă randamentul real, acesta va anula execuția speculativă care nu a avut niciun efect.

      în opinia mea, aceasta este o atenuare cu adevărat ingenioasă. Kudos inginerilor care l-au dezvoltat. Dezavantajul acestei atenuări este că necesită recompilarea tuturor software-urilor, astfel încât ramurile indirecte să fie convertite în ramuri retpoline. Pentru serviciile cloud, cum ar fi Google, care dețin întreaga stivă, recompilarea nu este mare lucru. Pentru alții, poate fi o afacere foarte mare sau imposibilă.

      IBRS, STIBP și IBPB (varianta 2 mitigation)

      se pare că, în același timp cu dezvoltarea retpoline, Intel (și AMD într-o oarecare măsură) au lucrat cu furie la modificările hardware pentru a atenua atacurile de injecție țintă a ramurilor. Cele trei noi caracteristici hardware livrate ca actualizări de microcod CPU sunt:

      • speculații restricționate de ramură indirectă (IBRS)
      • predictori de ramură indirectă cu un singur fir (STIBP)
      • barieră Predictoare de ramură indirectă (IBPB)

      informații limitate despre noile caracteristici de microcod sunt disponibile de la Intel aici. Am reușit să pun cap la cap ceea ce fac aceste noi caracteristici citind documentația de mai sus și uitându-mă la patch-urile kernel-ului Linux și Xen hypervisor. Din analiza mea, fiecare caracteristică este potențial utilizată după cum urmează:

      • IBRS spală memoria cache de predicție a ramurilor între nivelurile de privilegii (utilizator la kernel) și dezactivează predicția ramurilor pe firul procesorului frate. Amintiți-vă că fiecare nucleu CPU are de obicei două fire CPU. Se pare că pe procesoarele moderne hardware-ul de predicție a ramurilor este împărțit între fire. Aceasta înseamnă că nu numai că codul modului utilizator poate otrăvi predictorul ramurii înainte de a introduce codul kernel-ului, codul care rulează pe firul procesorului frate îl poate otrăvi și el. Activarea IBRS în modul kernel împiedică în esență orice execuție anterioară în modul utilizator și orice execuție pe firul procesorului frate să afecteze predicția ramurii.
      • STIBP pare a fi un subset de IBRS care dezactivează doar predicția ramurii pe firul procesorului frate. Din câte pot spune, principalul caz de utilizare pentru această caracteristică este de a împiedica un fir CPU frate să otrăvească predictorul ramurii atunci când rulează două procese diferite de mod utilizator (sau mașini virtuale) pe același nucleu CPU în același timp. Sincer, nu este complet clar pentru mine acum când ar trebui folosit STIBP.
      • IBPB pare să spele memoria cache de predicție a ramurilor pentru codul care rulează la același nivel de privilegii. Acest lucru poate fi utilizat atunci când comutați între două programe de mod utilizator sau două mașini virtuale pentru a vă asigura că codul anterior nu interferează cu codul care urmează să ruleze (deși fără STIBP cred că codul care rulează pe firul procesorului frate ar putea încă otrăvi predictorul ramurii).

      începând cu această scriere, principalele atenuări pe care le văd implementate pentru vulnerabilitatea injecției țintă a ramurii par a fi atât retpoline, cât și IBRS. Probabil că acesta este cel mai rapid mod de a proteja nucleul de programele de mod utilizator sau hipervizorul de oaspeții mașinii virtuale. În viitor, m-aș aștepta ca atât STIBP, cât și IBPB să fie implementate în funcție de nivelul paranoia al diferitelor programe de mod utilizator care interferează între ele.

      costul IBRS pare, de asemenea, să varieze extrem de mult între arhitecturile CPU, procesoarele Intel Skylake mai noi fiind relativ ieftine în comparație cu procesoarele mai vechi. La Lyft, am văzut o încetinire de aproximativ 20% a anumitor sarcini grele de apelare a sistemului pe instanțele AWS C4 atunci când atenuările au fost lansate. Aș specula că Amazon a lansat IBRS și potențial și retpoline, dar nu sunt sigur. Se pare că Google ar fi putut lansa retpoline doar în cloud.

      de-a lungul timpului, m-aș aștepta ca procesoarele să se mute în cele din urmă la un model IBRS „Always on”, unde hardware-ul doar implicit pentru a curăța separarea predictorului ramurii între firele CPU și starea corectă la schimbările de nivel de privilegii. Singurul motiv pentru care acest lucru nu s-ar face astăzi este costul aparent de performanță al modernizării acestei funcționalități pe microarhitecturile deja lansate prin actualizări de microcod.

      concluzie

      este foarte rar ca un rezultat al cercetării să schimbe fundamental modul în care computerele sunt construite și rulate. Meltdown și Spectre au făcut exact asta. Aceste descoperiri vor modifica substanțial designul hardware și software în următorii 7-10 ani (următorul ciclu hardware CPU), deoarece designerii iau în considerare noua realitate a posibilităților de scurgere a datelor prin canalele laterale cache.

      între timp, constatările Meltdown și Spectre și atenuările asociate vor avea implicații substanțiale pentru utilizatorii de computere pentru anii următori. Pe termen scurt, atenuările vor avea un impact asupra performanței care poate fi substanțial în funcție de volumul de muncă și de hardware-ul specific. Acest lucru poate necesita modificări operaționale pentru unele infrastructuri (de exemplu, la Lyft mutăm agresiv unele sarcini de lucru în instanțele AWS C5 datorită faptului că IBRS pare să ruleze substanțial mai rapid pe procesoarele Skylake, iar noul hipervizor Nitro oferă întreruperi direct oaspeților folosind SR-IOV și APICv, eliminând multe ieșiri de mașini virtuale pentru sarcini grele IO). Nici utilizatorii de computere Desktop nu sunt imuni, din cauza atacurilor browserului proof-of-concept folosind JavaScript pe care Furnizorii de sisteme de operare și browser lucrează pentru a le atenua. În plus, datorită complexității vulnerabilităților, este aproape sigur că cercetătorii în domeniul securității vor găsi noi exploatări care nu sunt acoperite de atenuările actuale care vor trebui remediate.

      deși îmi place să lucrez la Lyft și simt că munca pe care o facem în spațiul de infrastructură a sistemelor microservice este una dintre cele mai de impact lucrări realizate în industrie chiar acum, evenimente de genul acesta mă fac să-mi fie dor să lucrez la sisteme de operare și hipervizori. Sunt extrem de gelos pe munca eroică care a fost făcută în ultimele șase luni de un număr imens de oameni în cercetarea și atenuarea vulnerabilităților. Mi-ar fi plăcut să fi fost o parte din ea!

      lecturi suplimentare

      • Meltdown și Spectre lucrări academice:https://spectreattack.com/
      • Google Project Zero blog post:https://googleprojectzero.blogspot.com/2018/01/reading-privileged-memory-with-side.html
      • Intel Spectre mitigations hardware:https://software.intel.com/sites/default/files/managed/c5/63/336996-Speculative-Execution-Side-Channel-Mitigations.pdf
      • retpoline blog post:https://support.google.com/faqs/answer/7625886
      • bun rezumat al informațiilor cunoscute:https://github.com/marcan/speculation-bugs/blob/master/README.md



Lasă un răspuns

Adresa ta de email nu va fi publicată.