Il predittore di salto è un componente critico delle moderne architetture CPU progettate per migliorare le prestazioni speculando sulla direzione delle istruzioni di salto (ad esempio, istruzioni if-else) prima che vengano risolte. Questa speculazione consente alla CPU di precaricare ed eseguire istruzioni lungo il percorso previsto, riducendo così la latenza percepita e migliorando il throughput complessivo. Tuttavia, questa ottimizzazione delle prestazioni introduce potenziali vulnerabilità che possono essere sfruttate negli attacchi temporali della CPU, in particolare nel contesto della fuga di informazioni sensibili.
La previsione dei rami funziona mantenendo una cronologia dei risultati dei rami e utilizzando questa cronologia per prevedere i rami futuri. Quando viene incontrata un'istruzione di diramazione, il predittore utilizza questi dati storici per indovinare se la diramazione verrà eseguita o meno. Se la previsione è corretta, la CPU continua l'esecuzione senza interruzioni. Se non corretto, la CPU deve eseguire il rollback ed eseguire il percorso corretto, il che comporta una riduzione delle prestazioni. Questa penalità, seppure piccola, può essere misurata e sfruttata dagli aggressori.
Gli aggressori possono manipolare il predittore del ramo per creare una differenza temporale misurabile tra i rami previsti correttamente e quelli errati. Questa differenza può essere utilizzata per dedurre il percorso di esecuzione di un programma, che a sua volta può rivelare informazioni sensibili. Uno degli esempi più noti di tale attacco è la vulnerabilità Spectre, che sfrutta l’esecuzione speculativa e la previsione dei rami per accedere a posizioni di memoria non autorizzate.
In un tipico attacco Spectre, l'aggressore addestra innanzitutto il predittore del ramo a seguire uno schema specifico. Questa fase di addestramento prevede l'esecuzione di una sequenza di istruzioni di diramazione che condizionano il predittore a fare una particolare previsione. Una volta addestrato il predittore, l'aggressore esegue un segmento del codice della vittima che include un ramo dipendente dai dati segreti. Se il predittore fa una previsione errata in base all'addestramento dell'attaccante, la CPU eseguirà speculativamente istruzioni che accedono alla memoria in base ai dati segreti. Sebbene queste istruzioni speculative alla fine vengano scartate, lasciano tracce nella cache della CPU.
L'aggressore può quindi misurare i tempi di accesso a diverse posizioni di memoria per determinare a quali dati è stato effettuato l'accesso speculativo. Questa tecnica, nota come attacco temporale della cache, consente all'aggressore di dedurre i dati segreti in base alle differenze temporali osservate. I passaggi chiave in un simile attacco sono:
1. Formazione del Branch Predictor: L'attaccante esegue una sequenza controllata di istruzioni che influenzano lo stato del predittore del ramo. Ad esempio, l'esecuzione ripetuta di un'istruzione di salto con un risultato coerente (ad esempio, sempre preso) condiziona il predittore ad aspettarsi quel risultato nelle esecuzioni future.
2. Innesco dell'esecuzione speculativa: L'aggressore esegue il codice della vittima con un'istruzione di ramo dipendente da dati segreti. A causa della formazione precedente dell'aggressore, il predittore di ramo esegue speculativamente il percorso sbagliato, che comporta l'accesso alla memoria in base ai dati segreti.
3. Misurazione dei tempi di accesso alla cache: Dopo l'esecuzione speculativa, l'aggressore misura il tempo necessario per accedere a specifiche posizioni di memoria. Tempi di accesso più rapidi indicano che i dati sono presenti nella cache, il che implica che l'accesso è stato speculativo. Analizzando questi tempi, l'aggressore può dedurre i dati segreti.
Per illustrare ciò con un esempio concreto, si consideri uno scenario in cui i dati segreti determinano l'indice di accesso di un array all'interno di un ramo. L'attaccante addestra innanzitutto il predittore del ramo ad assumere una determinata direzione del ramo. Quando viene eseguito il codice vittima, il predittore di ramo esegue speculativamente l'accesso all'array in base alla direzione addestrata. Se la speculazione implica l'accesso a un particolare elemento dell'array, viene caricata la riga della cache corrispondente. L'aggressore può quindi eseguire una serie di accessi temporizzati alla memoria per determinare quali linee di cache vengono caricate, deducendo così l'indice segreto.
Per mitigare tali attacchi sono necessarie diverse strategie. Le soluzioni basate su hardware includono il miglioramento dell'isolamento tra percorsi di esecuzione speculativa e non speculativa e la garanzia che l'esecuzione speculativa non influisca sulle risorse condivise come la cache. Le soluzioni basate su software implicano tecniche come l'inserimento di istruzioni "recinto" per impedire l'esecuzione speculativa oltre determinati punti del codice o l'utilizzo di pratiche di programmazione a tempo costante per garantire che il tempo di esecuzione non dipenda da dati segreti.
La complessità e la sofisticazione degli attacchi temporali basati su predittori di filiale sottolineano la necessità di ricerca e sviluppo continui nella sicurezza sia hardware che software. Man mano che le architetture delle CPU continuano ad evolversi, anche le strategie di protezione da queste e altre forme di attacchi side-channel devono evolversi.
Altre domande e risposte recenti riguardanti Attacchi di temporizzazione della CPU:
- Quali sono alcune delle sfide e dei compromessi coinvolti nell'implementazione di misure di mitigazione hardware e software contro gli attacchi temporali mantenendo le prestazioni del sistema?
- In che modo la programmazione a tempo costante può contribuire a mitigare il rischio di attacchi temporali negli algoritmi crittografici?
- Che cos'è l'esecuzione speculativa e in che modo contribuisce alla vulnerabilità dei processori moderni agli attacchi temporali come Spectre?
- In che modo gli attacchi temporali sfruttano le variazioni del tempo di esecuzione per dedurre informazioni sensibili da un sistema?
- Cos'è un attacco temporale?