Spectre - un grup de vulnerabilități hardware , o eroare în majoritatea procesoarelor moderne care au execuție de instrucțiuni speculativeși predicție avansată a ramurilor , permițând citirea datelor printr -un canal terță parte sub forma unei ierarhii cache comune . Afectează majoritatea microprocesoarelor moderne, în special arhitecturile x86/x86_64 (Intel și AMD) și unele nuclee de procesoare ARM [1] .
Vulnerabilitatea poate permite aplicațiilor locale (un atacator local, când rulează un program special) să acceseze conținutul memoriei virtuale a aplicației curente sau a altor programe [2] [3] [4] . Amenințării au primit doi identificatori CVE : CVE-2017-5753 și CVE-2017-5715 .
Spectre a fost descoperit independent de cercetătorii corporației nord-americane Google ( Proiectul Zero ) și de un grup care colaborează cu Paul Kocher, cu participarea angajaților Universității de Tehnologie din Graz . Vulnerabilitatea a fost găsită la jumătatea anului 2017 și a fost în discuție și corectare închisă timp de câteva luni. Publicarea detaliilor și remedierilor a fost programată pentru 9 ianuarie 2018, dar detaliile vulnerabilității au fost făcute publice pe 4 ianuarie 2018, în același timp cu atacul Meltdown , datorită publicațiilor jurnaliştilor The Register [5] care au aflat despre corecțiile KAISER/KPTI pentru a combate Meltdown din lista de corespondență a nucleului Linux [6] .
Bug-ul Spectre permite aplicațiilor de utilizator rău intenționate care rulează pe un anumit computer să obțină acces de citire la locații arbitrare din memoria computerului utilizată de procesul victimei, cum ar fi alte aplicații (adică întreruperea izolării memoriei între programe). Atacul Spectre afectează majoritatea sistemelor informatice care folosesc microprocesoare de înaltă performanță, inclusiv computere personale, servere, laptopuri și o serie de dispozitive mobile [7] . În special, atacul Spectre a fost demonstrat pe procesoarele fabricate de corporațiile Intel și AMD și pe cipuri care folosesc nuclee de procesoare ARM .
Există o variantă a atacului Spectre care folosește programe JavaScript pentru a obține acces la memoria browserelor (citirea datelor de pe alte site-uri sau a datelor salvate în browser) [8] .
Să presupunem că fragmentul de cod al victimei procesează
if ( x < array1_size ) y = matrice2 [ matrice1 [ x ] * 256 ];face parte dintr-o funcție care primește un întreg nesemnat x de la o sursă neîncrezătoare, iar procesul care execută acest cod are acces la o matrice de numere întregi nesemnate de 8 biți array1 de dimensiune array1_size și o a doua matrice de numere întregi nesemnate de 8 biți array2 de dimensiune 64 kb.
Acest fragment începe prin a verifica dacă x este o valoare validă. Și această verificare este esențială din punct de vedere al securității. În special, împiedică citirea informațiilor dincolo de limitele matricei1 . În absența acesteia, valorile x nevalide pot fie să creeze o excepție atunci când încearcă să citească date în afara memoriei disponibile a procesului, fie să citească informații confidențiale accesibile procesului, specificând x = <secret_byte_address> - <array1_address_array1> .
Din păcate, predicția greșită a unei ramuri condiționate în execuția speculativă a instrucțiunilor poate duce la execuția unei ramuri a codului programului care, în condiții normale, nu ar fi executată niciodată [9] .
De exemplu, fragmentul de cod de mai sus poate fi executat în următoarele condiții:
Astfel de condiții pot apărea spontan, cu toate acestea, ele pot fi, de asemenea, formate intenționat, de exemplu, prin citirea unei cantități mari de date străine pentru a umple memoria cache a procesorului cu aceste date și, în consecință, pentru a elimina array1_size și array2 din cache și apoi apelați funcția kernel care utilizează octetul secret k , pentru a-l stoca în cache. Cu toate acestea, dacă structura cache-ului este cunoscută sau procesorul oferă în mod voluntar o instrucțiune de resetare a memoriei cache (de exemplu, instrucțiunea cflush pentru procesoarele din familia x86 ), atunci sarcina de a crea condițiile necesare pentru executarea unui fragment de cod este mult simplificată.
Fragmentul de cod începe prin a compara valoarea lui x cu valoarea array1_size . Citirea valorii array1_size în condițiile descrise mai sus va avea ca rezultat o pierdere a memoriei cache, care la rândul său va duce la așteptarea ca valoarea array1_size să fie preluată din RAM. Datorită prezenței unui mecanism de execuție a instrucțiunii speculative în procesor, în timpul timpului de așteptare procesorul nu va fi inactiv, ci va încerca să execute una dintre ramurile codului programului urmând instrucțiunea de ramificare.
Deoarece accesările anterioare la fragment au fost efectuate cu valori valide ale lui x , predictorul de ramuri va presupune că de data aceasta predicatul (x < array1_size) va fi adevărat, iar procesorul va încerca să execute secvența corespunzătoare de instrucțiuni. Și anume, va citi octetul de la <array1_address> + x , adică octetul secret k , care, datorită condițiilor special formate, se află deja în cache. Apoi, procesorul folosește valoarea rezultată pentru a evalua expresia k * 256 și pentru a citi elementul array2[k * 256] , ceea ce va duce la o a doua pierdere a memoriei cache și așteaptă ca valoarea array2[k * 256] să fie preluat din RAM. În acest moment, valoarea array1_size va fi obținută din RAM , procesorul va recunoaște eroarea predictorului de ramură și va restabili starea arhitecturală la momentul înainte de începerea execuției ramurii incorecte a codului programului.
Cu toate acestea, pe procesoarele reale, o citire speculativă a matricei2[k * 256] va afecta starea cache-ului procesorului, iar această stare va depinde de k . Pentru a finaliza atacul, este necesar doar să detectați această modificare folosind un atac pe canal lateral (atacatorul trebuie să aibă acces la memoria cache a procesorului partajat și la sursa de timp exactă) și, pe baza acesteia, să calculeze octetul secret k . Acest lucru este ușor de făcut, deoarece citirea elementelor matricei2[n * 256] va fi rapidă pentru n = k și lentă pentru alte valori.
O ramură indirectă poate folosi mai mult de două adrese pentru a se ramifica. De exemplu, instrucțiunile procesorului x86 -family pot sări folosind o valoare de adresă într-un registru ( jmp eax ), în memorie ( jmp [eax] , sau jmp dword ptr [0xdeadc0de] ) sau pe stivă ( ret ). Instrucțiuni de salt indirecte se găsesc și în ARM ( mov pc, r14 ), MIPS ( jr $ra ), SPARC ( jmpl %o7 ), RISC-V ( jarl x0,x1,0 ) și multe altele.
Dacă determinarea unei adrese de ramură indirectă este întârziată din cauza unei erori de cache, iar predictorul de ramură indirectă este „antrenat” cu adrese special alese, poate avea loc execuția speculativă a instrucțiunilor la adresa dată de atacator. Comenzi care altfel nu ar fi fost executate niciodată. Dacă o astfel de performanță lasă efecte secundare măsurabile , atunci utilizarea sa devine un instrument puternic în mâinile atacatorului.
În prezent, nu există tehnologii software gata făcute care să protejeze împotriva atacului Spectre, deși se lucrează ceva [10] . Potrivit unui site web dedicat promovării atacului, „Nu este atât de ușor de remediat și (bunul) ne va bântui mult timp”.
O remediere a software-ului poate include recompilarea software-ului folosind noi compilatoare pentru a înlocui secvențele vulnerabile de cod de mașină (așa-numitul mecanism „retpolin”, implementat în GCC și Clang / LLVM ) [11] .
Mai multe remedieri au fost propuse de producătorii de procesoare, unele necesitând actualizări ale microcodurilor procesorului, altele necesitând adăugarea de noi instrucțiuni la procesoarele viitoare. Remedierile ar trebui să fie combinate cu recompilarea software [11] .
În versiunile timpurii ale notificării Spectre CVE, CERT a sugerat înlocuirea procesoarelor ca răspuns la vulnerabilitate: „Vulnerabilitatea este cauzată de alegerile în designul microprocesorului. Îndepărtarea completă a vulnerabilității necesită înlocuirea microprocesoarelor afectate.” Cu toate acestea, în textele ulterioare această versiune a corectării nu a mai fost menționată [11] .