Spectre & Meltdown: le vulnerabilità più critiche degli ultimi 10 anni

Due tra i bug più critici mai scoperti, presenti in tutte le moderne CPU, sono Spectre e Meltdown. Tutta la comunità software, insieme ai maggiori vendor di hardware ed ai grandi colossi di cloud computing si stanno ancora muovendo per cercare soluzioni capaci di mitigare il problema, ma ancora non è stata trovata la soluzione perfetta. In questo articolo cercheremo di analizzare la natura di tali vulnerabilità e capire come un attaccante possa sfruttarle per poter leggere l'intero contenuto della memoria di un sistema bersaglio.

La natura del bug

L'aspetto interessante di Spectre e Meltdown è che tali vulnerabilità non sono causate da bug software, come tipicamente accade quando viene scoperta una nuova vulnerabilità, ma dipendono dall'hardware, cioè sono causate dal modo in cui funzionano internamente le moderne CPU. Per questo motivo è piuttosto difficile trovare una soluzione software per aggirare efficacemente una problematica di natura hardware.

Speculative Execution

Per comprendere il problema dobbiamo introdurre il concetto di "Speculative Execution", una tecnica di ottimizzazione che tutte le moderne CPU utilizzano per eseguire determinati compiti prima che essi siano effettivamente richiesti. Pensiamo ad un esempio della vita reale. Supponiamo di voler offrire un caffè ad un amico e, prima di porgli la domanda per sincerarci se effettivamente voglia un caffè, iniziamo a preparare la tazzina e accendiamo la macchinetta.

Le due azioni di "preparare la tazzina" e "accendere la macchinetta" sono state fatte da noi in modo speculativo, cioè non sappiamo ancora se sono effettivamente richieste, ma ci siamo portati avanti con il lavoro scommettendo sulla previsione che l'amico accetti il caffè.

Branch prediction

La CPU effettua ottimizzazioni analoghe a ciò che facciamo noi nell'esempio del caffè all'amico.

Vediamo, ad esempio, cosa succede con l'ottimizzazione chiamata "branch prediction".

Supponiamo che la CPU debba eseguire il codice seguente:

if (x < y) {
    z = v;
}

Ogni moderna CPU salva uno stato del passato per tutte le volte che ha eseguito una determinata condizione (branch), se tutte le precedenti volte la condizione è sempre stata vera (o sempre falsa), allora si può speculare sul fatto che la condizione sia vera (o falsa) anche le volte successive. Un pò come l'amico che abbia sempre accettato o sempre rifiutato il caffè, ogni volta che glielo abbiamo offerto.

Quindi, nel codice sopra, se la CPU ha forti convinzioni che x sia effettivamente minore di y, prima di leggere tali valori dalla memoria, potrebbe eseguire "z = v" speculativamente e salvare il risultato dell'operazione in uno stato temporaneo. Non appena i valori di x e y vengono ricevuti dalla memoria centrale nei registri della CPU, se la previsione era corretta, potrà confermare lo stato temporaneo e renderlo definitivo. In caso negativo, dovrà semplicemente annullare lo stato temporaneo (invalidando anche tutti i suoi effetti) e continuare la normale esecuzione.

Ed è proprio nell'invalidazione di tali effetti che si cela la natura del bug. Tutte le moderne CPU sono dotate di memorie cache (su più livelli): piccole memorie che garantiscono un accesso più rapido rispetto alla memoria centrale, dove vengono memorizzati i dati cui si accede più spesso. L'esecuzione speculativa dello statement "z = v" potrebbe causare il caricamento in cache di alcuni dati ed essi potrebbero restare in cache anche se lo stato temporaneo viene annullato.

Un attaccante potrebbe, quindi, analizzare il contenuto delle cache e reperire informazioni bypassando i tipici meccanismi di protezione software e hardware.

Bounds check bypass - Spectre v1

Esistono più varianti della vulnerabilità denominata "Spectre". La prima versione si identifica con il nome "bounds check bypass".

Vediamo come sia possibile sfruttare questo tipo di vulnerabilità per poter determinare il contenuto di aree di memoria arbitrarie all'interno di un processo senza accedervi direttamente.

Modifichiamo l'esempio precedente nel modo seguente:

uint8_t array1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
unsigned int array1_size = 16;
uint8_t array2[256 * 512];
uint8_t z = 0;

if (x < array1_size) {
    z = array2[array1[x] * 512];
}

Rispetto all'esempio precedente abbiamo introdotto due array:
- "array1[]" ci servirà per accedere all'area di memoria bersaglio;
- "array2[]" chiamato "probe array" di cui l'attaccante ha pieno controllo in lettura.

NOTA: array2[] è composto da 256 elementi di 512 bytes (256 => un elemento per ogni possibile valore di 1 byte).

Per prima cosa dobbiamo "addestrare" la branch prediction per convincere la CPU che la condizione (x < array1_size) sia quasi sempre vera. Per fare ciò è sufficiente introdurre un ciclo "for" assegnando a x dei valori minori di array1_size. In questo modo la CPU si convincerà ad eseguire speculativamente lo statement "z = array2[array1[x] * 512" per qualsiasi valore di x che passeremo successivamente.

A questo punto l'idea è di passare un valore di x tale per cui array1[x] punti ad una locazione di memoria arbitraria, contenente il byte che vogliamo spiare.

Il byte array1[x] potrà avere un valore compreso tra 0 e 255, questo valore verrà usato come indice di array2[], specificatamente per accedere al byte array2[array1[x] * 512], che verrà caricato in cache.

La CPU poi si renderà conto che la condizione "x < array1_size" non è più vera e invaliderà l'esecuzione speculativa di "z = array2[array1[x] * 512]", ma il byte letto da array2[] rimarrà in cache. Avendo pieno controllo in lettura di array2[], l'attaccante potrà quindi accedere a tutti gli elementi di array2[], misurandone i tempi di accesso.

L'elemento che verrà letto più velocemente rispetto agli altri sarà quello che era presente in cache, e l'indice di tale elemento sarà proprio il byte contenuto della locazione arbitraria di memoria che volevamo spiare. Alla fine dell'articolo viene riportato l'intero codice del "proof-of-concept" di Spectre v1. È interessante notare che questa vulnerabilità funziona su qualsiasi sistema (Linux, Windows, MacOS), basta semplicemente che la CPU adotti l'esecuzione speculativa e tecniche di branch prediction.

Meltdown

Tutte le varianti di Spectre si basano sempre su concetti simili: forzare la CPU a caricare in cache determinati dati, e utilizzare attacchi side-channel per determinare il contenuto delle cache.

L'altra nota vulnerabilità denominata "Meltdown" si basa sempre sull'esecuzione speculativa, ma al posto della branch prediction utilizza la protezione della memoria tra kernel e user-space.

Senza entrare troppo nel dettaglio (dato che i concetti sono simili) possiamo comprendere Meltdown utilizzando il seguente esempio:

 uint8_t array2[256 * 4096];
 uint8_t kernel_memory = *(uint8_t *)(kernel_address);
 uint64_t final_kernel_memory = kernel_memory * 4096;
 uint8_t dummy = array2[final_kernel_memory];

Anche questa vulnerabilità utilizza un probe array (array2[]), come nel caso Spectre.

Invece di una condizione, in questo caso abbiamo un accesso a un indirizzo in kernel space (kernel_address). L'accesso a tale locazione di memoria da user-space causa ovviamente un "segmentation fault", che ne impedisce la lettura e la terminazione del processo, se il segnale SIGSEGV non viene gestito opportunamente. Tuttavia, i processori Intel eseguono speculativamente il codice in questione prima di verificare i permessi di protezione di memoria (analogamente a quanto avveniva nella branch prediction con Spectre); una volta che i permessi vengono verificati, il codice eseguito speculativamente viene invalidato, ma il dato letto rimane comunque in cache. A questo punto è possibile misurare i tempi di accesso del probe array (array2[]) e determinare il valore del byte letto dalla memoria kernel.

[...]

ATTENZIONE: quello che hai appena letto è solo un estratto, l'Articolo Tecnico completo è composto da ben 2501 parole ed è riservato agli ABBONATI. Con l'Abbonamento avrai anche accesso a tutti gli altri Articoli Tecnici e potrai fare il download in formato PDF eBook e Mobi per un anno. ABBONATI ORA, è semplice e sicuro.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend