Che cos'è un retpoline e come funziona?


244

Al fine di mitigare la divulgazione della memoria tra kernel o tra processi (l' attacco Spettro ), il kernel 1 di Linux verrà compilato con una nuova opzione , -mindirect-branch=thunk-externintrodotta gccper eseguire chiamate indirette attraverso un cosiddetto retpoline .

Questo sembra essere un termine appena inventato poiché una ricerca su Google rivela solo un uso molto recente (generalmente tutto nel 2018).

Che cos'è un retpoline e come impedisce i recenti attacchi alla divulgazione di informazioni sul kernel?


1 Non è specifico di Linux, tuttavia: un costrutto simile o identico sembra essere usato come parte delle strategie di mitigazione su altri sistemi operativi.


6
Un interessante articolo di supporto di Google.
sgbj,

2
oh, quindi è pronunciato / ˌtræmpəˈlin / (americano) o / ˈtræmpəˌliːn / (britannico)
Walter Tross

2
Potresti menzionare che questo è il kernel di Linux , sebbene gccpunti in questo modo! Non ho riconosciuto lkml.org/lkml/2018/1/3/780 come nel sito della Mailing List del kernel Linux, nemmeno una volta che ho guardato lì (e mi è stata offerta un'istantanea perché era offline).
PJTraill

@PJTraill - aggiunto un tag del kernel Linux
RichVel

@PJTraill - buon punto, ho aggiornato il testo della domanda. Si noti che l'ho visto prima nel kernel di Linux a causa del suo processo di sviluppo relativamente aperto, ma senza dubbio le stesse o simili tecniche vengono utilizzate come mitigazioni attraverso lo spettro di sistemi operativi open e closed source. Quindi non lo vedo come specifico di Linux, ma il collegamento lo è sicuramente.
BeeOnRope,

Risposte:


158

L'articolo citato da sgbj nei commenti scritti da Paul Turner di Google spiega quanto segue in modo molto più dettagliato, ma ci proverò:

Per quanto riesco a mettere insieme dalle informazioni limitate al momento, un retpoline è un trampolino di ritorno che utilizza un loop infinito che non viene mai eseguito per impedire alla CPU di speculare sull'obiettivo di un salto indiretto.

L'approccio di base può essere visto nel ramo del kernel di Andi Kleen che affronta questo problema:

Presenta la nuova __x86.indirect_thunkchiamata che carica il target di chiamata il cui indirizzo di memoria (che chiamerò ADDR) è memorizzato in cima allo stack ed esegue il salto usando RETun'istruzione. Il thunk stesso viene quindi chiamato utilizzando la macro NOSPEC_JMP / CALL , utilizzata per sostituire molte chiamate e salti indiretti (se non tutti). La macro posiziona semplicemente la destinazione della chiamata nello stack e imposta correttamente l'indirizzo di ritorno, se necessario (notare il flusso di controllo non lineare):

.macro NOSPEC_CALL target
    jmp     1221f            /* jumps to the end of the macro */
1222:
    push    \target          /* pushes ADDR to the stack */
    jmp __x86.indirect_thunk /* executes the indirect jump */
1221:
    call    1222b            /* pushes the return address to the stack */
.endm

Il posizionamento di callalla fine è necessario in modo che quando la chiamata indiretta è terminata, il flusso di controllo continua dietro l'uso della NOSPEC_CALLmacro, quindi può essere utilizzato al posto di un normalecall

Lo stesso thunk si presenta come segue:

    call retpoline_call_target
2:
    lfence /* stop speculation */
    jmp 2b
retpoline_call_target:
    lea 8(%rsp), %rsp 
    ret

Il flusso di controllo può creare un po 'di confusione qui, quindi vorrei chiarire:

  • call spinge il puntatore dell'istruzione corrente (etichetta 2) nello stack.
  • leaaggiunge 8 al puntatore dello stack , eliminando efficacemente il quadword inviato più di recente, che è l'ultimo indirizzo di ritorno (all'etichetta 2). Dopodiché, la parte superiore dello stack punta nuovamente all'indirizzo ADDR reale.
  • retpassa *ADDRe reimposta il puntatore dello stack all'inizio dello stack di chiamate.

Alla fine, tutto questo comportamento è praticamente equivalente a saltare direttamente a *ADDR. L'unico vantaggio che otteniamo è che il predittore di diramazione utilizzato per le dichiarazioni di ritorno (Return Stack Buffer, RSB), quando esegue l' callistruzione, presuppone che l' retistruzione corrispondente salterà all'etichetta 2.

La parte dopo l'etichetta 2 in realtà non viene mai eseguita, è semplicemente un ciclo infinito che teoricamente riempirebbe la pipeline di JMPistruzioni con istruzioni. Utilizzando LFENCE, PAUSEo più in generale, un'istruzione che provoca lo stallo della pipeline di istruzioni, impedisce alla CPU di sprecare energia e tempo in questa esecuzione speculativa. Questo perché nel caso in cui la chiamata a retpoline_call_target ritornasse normalmente, LFENCEsarebbe la prossima istruzione da eseguire. Questo è anche ciò che il predittore di filiale prevede in base all'indirizzo di ritorno originale (l'etichetta 2)

Per citare dal manuale di architettura Intel:

Le istruzioni che seguono un LFENCE possono essere recuperate dalla memoria prima di LFENCE, ma non verranno eseguite fino al completamento di LFENCE.

Si noti tuttavia che la specifica non menziona mai che LFENCE e PAUSE causano l'arresto della pipeline, quindi sto leggendo un po 'tra le righe qui.

Ora torniamo alla tua domanda originale: la divulgazione delle informazioni sulla memoria del kernel è possibile grazie alla combinazione di due idee:

  • Anche se l'esecuzione speculativa dovrebbe essere libera da effetti collaterali quando la speculazione era errata, l' esecuzione speculativa influisce comunque sulla gerarchia della cache . Ciò significa che quando un carico di memoria viene eseguito in modo speculativo, potrebbe aver causato l'eliminazione di una riga della cache. Questa modifica nella gerarchia della cache può essere identificata misurando attentamente il tempo di accesso alla memoria mappato sullo stesso set di cache.
    È anche possibile perdere alcuni bit di memoria arbitraria quando l'indirizzo sorgente della memoria letto è stato letto dalla memoria del kernel.

  • Il predittore di diramazione indiretta delle CPU Intel utilizza solo i 12 bit più bassi dell'istruzione sorgente, quindi è facile avvelenare tutte le 2 ^ 12 possibili storie di previsione con indirizzi di memoria controllati dall'utente. Questi possono quindi, quando il salto indiretto è previsto nel kernel, essere speculativamente eseguiti con i privilegi del kernel. Utilizzando il canale laterale di temporizzazione della cache, è quindi possibile perdere la memoria del kernel arbitraria.

AGGIORNAMENTO: Nella mailing list del kernel , c'è una discussione in corso che mi porta a credere che le retpoline non mitigino completamente i problemi di previsione del ramo, come quando il Return Stack Buffer (RSB) gira vuoto, le architetture Intel più recenti (Skylake +) si ritirano al vulnerabile Branch Target Buffer (BTB):

Retpoline come strategia di mitigazione scambia i rami indiretti con i rendimenti, per evitare di usare previsioni che provengono dal BTB, in quanto possono essere avvelenate da un attaccante. Il problema con Skylake + è che un underflow RSB ricade sull'uso di una previsione BTB, che consente all'attaccante di assumere il controllo della speculazione.


Non penso che l'istruzione LFENCE sia importante, l'implementazione di Google utilizza invece un'istruzione PAUSE. support.google.com/faqs/answer/7625886 Nota che la documentazione che hai citato dice che "non eseguirà" non sarà "non verrà eseguita in modo speculativo".
Ross Ridge,

1
Da quella pagina FAQ di Google: "Le istruzioni di pausa nei nostri cicli speculativi sopra non sono richieste per correttezza. Ma significa che l'esecuzione speculativa non produttiva occupa meno unità funzionali sul processore." Quindi non supporta la tua conclusione che LFENCE è la chiave qui.
Ross Ridge,

@RossRidge Sono parzialmente d'accordo, a me sembrano due possibili implementazioni di un ciclo infinito che suggeriscono alla CPU di non eseguire speculativamente il codice dopo PAUSE / LFENCE. Tuttavia, se LFENCE è stato eseguito in modo speculativo e non ripristinato perché la speculazione era corretta, ciò contraddirebbe l'affermazione che verrà eseguita solo una volta terminati i caricamenti di memoria. (Altrimenti, l'intera serie di istruzioni che sono state eseguite in modo speculativo dovrebbe essere ripristinata ed eseguita di nuovo per soddisfare le specifiche)
Tobias Ribizel,

1
Ciò ha il vantaggio di push/ retche non sbilancia lo stack predittore dell'indirizzo di ritorno. C'è un errore (andando lfenceprima che venga utilizzato l'indirizzo di ritorno effettivo), ma usando un call+ che modifica rspbilanciato ret.
Peter Cordes,

1
oops, vantaggio su push / ret(nel mio ultimo commento). ri: la tua modifica: underflow RSB dovrebbe essere impossibile perché la retpoline include a call. Se la prelazione del kernel avesse fatto un cambio di contesto lì, riprenderemmo l'esecuzione con l'RSB innescato dallo callnello scheduler. Ma forse un gestore di interrupt potrebbe terminare con abbastanza rets per svuotare l'RSB.
Peter Cordes,

46

Una retolina è progettata per proteggere dall'exploit dell'iniezione del bersaglio di filiale ( CVE-2017-5715 ). Questo è un attacco in cui viene usata un'istruzione di diramazione indiretta nel kernel per forzare l'esecuzione speculativa di un pezzo arbitrario di codice. Il codice scelto è un "gadget" che è in qualche modo utile all'attaccante. Ad esempio, il codice può essere scelto in modo tale da perdere i dati del kernel attraverso il modo in cui influenza la cache. La retpoline impedisce questo exploit semplicemente sostituendo tutte le istruzioni di diramazione indirette con un'istruzione di ritorno.

Penso che la chiave della retpoline sia solo la parte "ret", che sostituisce il ramo indiretto con un'istruzione di ritorno in modo che la CPU usi il predittore dello stack di ritorno invece del predittore di ramo sfruttabile. Se invece venissero utilizzati un semplice push e un'istruzione return, il codice che verrebbe eseguito in modo speculativo sarebbe il codice al quale la funzione tornerà alla fine, non un gadget utile all'attaccante. Il vantaggio principale della parte del trampolino sembra essere quello di mantenere lo stack di ritorno, quindi quando la funzione effettivamente ritorna al suo chiamante, questo è previsto correttamente.

L'idea di base dietro l'iniezione del target di filiale è semplice. Sfrutta il fatto che la CPU non registra l'indirizzo completo dell'origine e della destinazione dei rami nei suoi buffer di destinazione. Pertanto, l'attaccante può riempire il buffer usando i salti nel proprio spazio di indirizzi, il che provocherà i colpi di previsione quando un determinato salto indiretto viene eseguito nello spazio di indirizzi del kernel.

Si noti che retpoline non impedisce direttamente la divulgazione di informazioni sul kernel, ma impedisce solo l'uso di istruzioni di diramazione indirette per eseguire in modo speculativo un gadget che rivelerebbe informazioni. Se l'attaccante riesce a trovare altri mezzi per eseguire speculativamente il gadget, la retpoline non impedisce l'attacco.

L'articolo Spectre Attacks: Exploiting Speculative Execution di Paul Kocher, Daniel Genkin, Daniel Gruss, Werner Haas, Mike Hamburg, Moritz Lipp, Stefan Mangard, Thomas Prescher, Michael Schwarz e Yuval Yarom offrono la seguente panoramica di come sfruttare i rami indiretti :

Sfruttare i rami indiretti. Attingendo dalla programmazione orientata al ritorno (ROP), in questo metodo l'attaccante sceglie un gadgetdallo spazio degli indirizzi della vittima e influenza la vittima a eseguire il gadget in modo speculativo. A differenza di ROP, l'attaccante non si basa su una vulnerabilità nel codice vittima. Al contrario, l'attaccante addestra il Branch Target Buffer (BTB) per predire erroneamente un ramo da un'istruzione di ramo indiretta all'indirizzo del gadget, determinando un'esecuzione speculativa del gadget. Mentre le istruzioni eseguite in modo speculativo vengono abbandonate, i loro effetti sulla cache non vengono ripristinati. Questi effetti possono essere utilizzati dal gadget per perdere informazioni sensibili. Mostriamo come, con un'attenta selezione di un gadget, questo metodo può essere utilizzato per leggere la memoria arbitraria dalla vittima.

Per controllare il BTB, l'attaccante trova l'indirizzo virtuale del gadget nello spazio degli indirizzi della vittima, quindi esegue diramazioni indirette a questo indirizzo. Questa formazione viene eseguita dallo spazio degli indirizzi dell'attaccante e non importa cosa risiede nell'indirizzo del gadget nello spazio degli indirizzi dell'attaccante; tutto ciò che serve è che il ramo utilizzato per formare i rami utilizzi lo stesso indirizzo virtuale di destinazione. (Di fatto, fintanto che l'attaccante gestisce le eccezioni, l'attacco può funzionare anche se non vi è alcun codice mappato all'indirizzo virtuale del gadget nello spazio degli indirizzi dell'aggressore.) Non è inoltre necessaria una corrispondenza completa dell'indirizzo di origine della filiale utilizzata per la formazione e l'indirizzo della filiale designata. Pertanto, l'attaccante ha una notevole flessibilità nell'impostare l'allenamento.

Un post di blog intitolato Lettura della memoria privilegiata con un canale laterale da parte del team di Project Zero su Google fornisce un altro esempio di come l'iniezione di target di filiali può essere utilizzata per creare un exploit funzionante.


9

Questa domanda è stata posta qualche tempo fa e merita una risposta più recente.

Riepilogo esecutivo :

Le sequenze "Retpoline" sono un costrutto software che consente ai rami indiretti di essere isolati dall'esecuzione speculativa. Questo può essere applicato per proteggere i file binari sensibili (come il sistema operativo o le implementazioni dell'hypervisor) dagli attacchi di iniezione del target branch contro i loro branch indiretti.

La parola " ret poline " è un portmanteau delle parole "ritorno" e "trampolino", proprio come il miglioramento " rel poline " è stato coniato da "richiamo relativo" e "trampolino". Si tratta di un costrutto di trampolino costruito utilizzando operazioni di rimpatrio che assicura anche che qualsiasi esecuzione speculativa associata "rimbalzerà" all'infinito.

Al fine di mitigare la divulgazione della memoria tra kernel o tra processi (l'attacco Spettro), il kernel Linux [1] verrà compilato con una nuova opzione,-mindirect-branch=thunk-extern introdotta in gcc per eseguire chiamate indirette attraverso un cosiddetto retpoline.

[1] Non è specifico di Linux, tuttavia: un costrutto simile o identico sembra essere usato come parte delle strategie di mitigazione su altri sistemi operativi.

L'uso di questa opzione del compilatore protegge solo dallo spettro V2 nei processori interessati che dispongono dell'aggiornamento del microcodice richiesto per CVE-2017-5715. Sarà ' lavoro ' a qualsiasi codice (non solo un kernel), ma solo il codice contenente "segreti" vale la pena di attacco.

Questo sembra essere un termine appena inventato poiché una ricerca su Google rivela solo un uso molto recente (generalmente tutto nel 2018).

Il compilatore LLVM ha avuto un -mretpolinepassaggio da prima del 4 gennaio 2018 . Quella data è quando la vulnerabilità è stata segnalata pubblicamente per la prima volta . GCC ha reso disponibili le loro patch 7 gennaio 2018.

La data CVE suggerisce che la vulnerabilità è stata " scoperta " nel 2017, ma colpisce alcuni dei processori prodotti negli ultimi due decenni (quindi è stato probabilmente scoperto molto tempo fa).

Che cos'è un retpoline e come impedisce i recenti attacchi alla divulgazione di informazioni sul kernel?

Innanzitutto, alcune definizioni:

  • Trampolino : a volte indicato come trampolino di vettori di salto indiretto sono posizioni di memoria che contengono indirizzi che puntano a routine di servizio di interruzione, routine di I / O, ecc. L'esecuzione salta nel trampolino e quindi salta immediatamente fuori o rimbalza, da cui il termine trampolino. GCC ha tradizionalmente supportato le funzioni nidificate creando un trampolino eseguibile in fase di esecuzione quando viene preso l'indirizzo di una funzione nidificata. Questo è un piccolo pezzo di codice che normalmente risiede nello stack, nel frame dello stack della funzione contenitore. Il trampolino carica il registro a catena statico e quindi passa all'indirizzo reale della funzione nidificata.

  • Thunk : un thunk è una subroutine utilizzata per iniettare un calcolo aggiuntivo in un'altra subroutine. I thunk vengono principalmente utilizzati per ritardare un calcolo fino a quando non è necessario il suo risultato o per inserire operazioni all'inizio o alla fine dell'altra subroutine

  • Memoization : una funzione memorizzata "ricorda" i risultati corrispondenti ad alcuni set di input specifici. Le chiamate successive con input ricordati restituiscono il risultato memorizzato piuttosto che ricalcolarlo, eliminando così il costo primario di una chiamata con determinati parametri da tutti tranne la prima chiamata effettuata alla funzione con tali parametri.

Molto approssimativamente, un retpoline è un trampolino con un ritorno da thunk , per " rovinare " la memoizzazione nel predittore del ramo indiretto.

Fonte : la retpoline include un'istruzione PAUSE per Intel, ma un'istruzione LFENCE è necessaria per AMD poiché su quel processore l'istruzione PAUSE non è un'istruzione di serializzazione, quindi il ciclo pause / jmp utilizzerà la potenza in eccesso poiché viene speculato sull'attesa del ritorno sbagliare verso il bersaglio corretto.

Arstechnica ha una semplice spiegazione del problema:

"Ogni processore ha un comportamento architettonico (il comportamento documentato che descrive come funzionano le istruzioni e da cui i programmatori dipendono per scrivere i loro programmi) e un comportamento microarchitetturale (il modo in cui si comporta un'attuazione effettiva dell'architettura). Questi possono divergere in modi sottili. Ad esempio, dal punto di vista architettonico, un programma che carica un valore da un determinato indirizzo in memoria attenderà fino a quando l'indirizzo non sarà noto prima di tentare di eseguire il caricamento. Microarchitettura, tuttavia, il processore potrebbe provare a indovinare speculativamente l'indirizzo in modo che possa iniziare caricamento del valore dalla memoria (che è lento) anche prima che sia assolutamente certo quale indirizzo dovrebbe usare.

Se il processore indovina in modo errato, ignorerà il valore indovinato ed eseguirà nuovamente il carico, questa volta con l'indirizzo corretto. Il comportamento architettonicamente definito viene così preservato. Ma questa ipotesi errata disturberà altre parti del processore, in particolare il contenuto della cache. Questi disturbi microarchitetturali possono essere rilevati e misurati temporizzando il tempo necessario per accedere ai dati che dovrebbero (o non dovrebbero) essere nella cache, consentendo a un programma dannoso di fare inferenze sui valori memorizzati. ".

Dall'articolo di Intel: " Retpoline: A Branch Target Injection Mitigation " ( .PDF ):

"Una sequenza retpoline impedisce all'esecuzione speculativa del processore di utilizzare il" predittore di diramazione indiretta "(un modo per prevedere il flusso del programma) per speculare su un indirizzo controllato da un exploit (soddisfacendo l'elemento 4 dei cinque elementi dell'iniezione del bersaglio di diramazione (Variante Spettro 2 ) sfruttare la composizione sopra elencata). ".

Nota, l'elemento 4 è: "L'exploit deve influenzare con successo questo ramo indiretto a un errore speculativo ed eseguire un gadget. Questo gadget, scelto dall'exploit, perde i dati segreti attraverso un canale laterale, in genere tramite cache-timing".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.