
Exploit per la vulnerabilità del firmware AMD SEV-SNP CVE-2024-21978, che consente la decrittazione di memoria guest arbitraria tramite corruzione della memoria delle pagine di contesto.
Questo repository contiene un exploit per una vulnerabilità nel firmware SEV. L'exploit permette di decifrare la memoria arbitraria di un guest SEV-SNP in esecuzione.
Testato sulla versione 1.55.16 (l'ultima al momento della scrittura).
Il campo nv_paddr del comando SEV_INIT_EX può essere utilizzato per donare un blocco di memoria al firmware, in modo che possa essere usato al posto della memoria flash persistente. Se SEV-SNP è abilitato, questa memoria deve essere nello stato FIRMWARE. Il firmware verifica questo una sola volta durante l'esecuzione del comando SEV_INIT_EX. Da quel momento in poi, il firmware assume che questa memoria sia nello stato FIRMWARE e vi scrive senza ulteriori controlli.
L'assunzione che la memoria sia ancora nello stato FIRMWARE non è sempre corretta, nulla impedisce all'host di riportare lo stato a HYPERVISOR usando il comando SNP_PAGE_RECLAIM. Una volta che le pagine sono nello stato HYPERVISOR possono essere trasferite in altri stati, ad esempio CONTEXT. Anche se le pagine non sono più nello stato FIRMWARE, il firmware scriverà su di esse rompendo l'integrità richiesta da alcuni stati delle pagine.
Possiamo sfruttare questa corruzione della memoria prendendo di mira le pagine CONTEXT. Le pagine CONTEXT sono un bersaglio potente, ma ci sono alcuni problemi:
CONTEXT sono crittografate con una chiave diversa rispetto alla memoria normale. Di conseguenza, non è facile controllare il testo in chiaro anche se potessimo controllare il crittogramma.La corruzione della memoria riempie effettivamente la pagina CONTEXT con dati casuali, quindi non è facile corrompere le pagine CONTEXT in modo utile per l'attaccante. Per aggirare questo, possiamo innescare ripetutamente il bug per causare corruzione e usare il comando SNP_GUEST_STATUS per leggere i campi rilevanti della pagina CONTEXT corrotta finché non osserviamo valori utili.
Il comando SNP_DBG_DECRYPT può essere utilizzato per decifrare la memoria di un guest SEV-SNP con la politica DEBUG abilitata. Se riusciamo a creare un CONTEXT in modo che abbia il flag DEBUG impostato e contenga l'ASID di un altro guest, possiamo usarlo per decifrare la memoria dell'altro guest anche se non ha la politica DEBUG impostata.
Risulta che SNP_DBG_DECRYPT ignori la maggior parte dei campi nella pagina CONTEXT, controlla solo gctx->guest.asid, gctx->guest.policy_snp e gctx->guest.guest_flags. La probabilità che questi campi siano corretti dopo la corruzione della memoria non è alta, ma non è nemmeno impossibile. La buona notizia è anche che possiamo leggere tutti questi campi usando il comando GUEST_STATUS.
In conclusione, possiamo sfruttare il bug con i seguenti passaggi:
nv_paddr nello stato FIRMWARE usando l'istruzione rmpupdate.SEV_INIT_EX.nv_paddr nello stato HYPERVISOR usando il comando SNP_RECLAIM_PAGE.CONTEXT all'indirizzo nv_paddr.nv_paddr usando il comando SEV_PDH_GEN. Questo corrompe le pagine CONTEXT.GUEST_STATUS per verificare se SNP_DBG_DECRYPT avrebbe successo, altrimenti torna al passo 5. Il collo di bottiglia principale è che gli ASID sono memorizzati in un int a 32 bit, ma ci sono molti meno ASID validi (509 o 1006 a seconda della CPU), quindi ci vorranno parecchi tentativi per ottenere il risultato.L'exploit passa la maggior parte del tempo nei passi 5 e 6. Le probabilità di ottenere tutte le condizioni giuste sono circa 1/20.000.000 su un EPYC Milan e possiamo fare circa 100 tentativi al secondo, quindi ci aspettiamo di ottenere le condizioni giuste circa una volta ogni due giorni (Attenzione: i calcoli sono solo approssimativi e potrei aver sbagliato qualcosa, ma aneddoticamente, una volta ogni due giorni sembra giusto). Possiamo accelerare il processo non attaccando una sola pagina CONTEXT alla volta, ma tre pagine CONTEXT a nv_paddr, nv_paddr+4096 e nv_paddr+8192 (SEV_PDH_GEN corromperà tre pagine). Fortunatamente questi passaggi possono essere eseguiti prima di avviare il guest vittima e devono avere successo una sola volta per attaccare un numero arbitrario di guest (nota che il PoC attualmente attacca un solo guest però).
Anche se non ho ancora potuto testarlo, credo che una volta che un attaccante ha usato questa vulnerabilità per divulgare le chiavi di comunicazione della piattaforma della macchina virtuale del guest, possa inviare messaggi guest al firmware per conto del guest e usarli per richiedere report di attestazione. Ciò viola un principio chiave di SEV-SNP secondo cui solo il guest dovrebbe poter richiedere report di attestazione.
Alcuni comandi (ad esempio SNP_RECLAIM_PAGE, SNP_GCTX_CREATE, RING_BUFFER, forse altri?, forse tutti per sicurezza?) che accettano una pagina FIRMWARE dovrebbero verificare se si sovrappone a nv_paddr e fallire in caso affermativo.
C'è un'altra preoccupazione che ho, di cui non sono sicuro se valida e mi piacerebbe sentire la vostra opinione: Se ho capito bene, il firmware SEV può essere aggiornato senza interrompere i guest in esecuzione. Questo mi suggerisce che sarebbe possibile trasferire la pagina CONTEXT corrotta da una vecchia versione vulnerabile del firmware a una nuova versione corretta. Sarebbe possibile iniziare con una versione più vecchia vulnerabile, eseguire l'exploit descritto sopra, aggiornare e impegnare il nuovo firmware corretto, avviare il guest con il nuovo firmware (in modo che la vecchia versione del firmware non appaia nel report di attestazione) e poi usare la pagina CONTEXT corrotta creata con il vecchio firmware per attaccare il guest creato con la nuova versione? Un consumatore dei report di attestazione creati dal nuovo guest sarebbe in grado di capire che la vecchia versione del firmware era in esecuzione in un certo momento prima che il nuovo guest fosse avviato? In caso contrario, sono necessarie ulteriori mitigazioni per evitare che ciò accada?
root@server:~/sev-exploit# cargo run --release
Finished release [optimized] target(s) in 0.12s
Running `target/release/sev-exploit`
Corrupt guest context page so that ASID is in range 1..510
Smallest ASID: 0x0000001f iterations: 14052175 zeros: 10539628 unique asids: 31500727 elapsed time: 1d 19h 20m 31s
Creating VM with same ASID
[03, 00, 00, 00, 00, 00, 00, 00, 11, 0f, a0, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, f0, 51, a5, 03, 3f, 69, 6b, 93, e8, d8, 61, 0d, 2e, 5a, 45, f1, ea, 6d, bf, 49, fe, e4, a9, 2d, 8d, af, 76, 5e, 2e, 56, e0, fa, a9, b3, a7, e0, bc, 09, d9, 4f, 28, 5c, 9f, 84, d2, 7e, 34, eb, ea, 3f, 29, 88, 30, 01, 28, 65, 8b, 73, 3c, 84, 00, ae, 4a, 74, a2, 7a, d1, c7, 4f, 63, 7f, 72, 7b, 3b, 2f, 08, b3, 1a, 8c, 99, 1b, ad, b5, 1d, 42, 0b, 4d, 98, d4, 7d, c1, 0b, d6, 2f, b4, 6c, 6b, 51, a2, 92, 17, 3b, 01, e8, 82, 11, 1e, cb, cb, a2, 8f, c9, b0, 52, 1d, 1d, b7, d2, 25, 8d, 32, a9, 7a, 6f, 86, e4, 40, 44, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 80, 00, 88, 00, 00, 00, 00, ee, ff, 00, 00, f0, ff, ff, ff, ff, ff, ff, ff, ff, 3f, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, <cut off zeros>]
thread 'main' panicked at src/main.rs:170:13:
not yet implemented: use the leaked secrets to send guest messages
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Nota che è normale che l'exploit funzioni per un periodo di tempo significativo (nell'ordine di ore se sei fortunato, giorni se non lo sei). Eseguirlo su un EPYC Genoa sarà probabilmente più veloce perché ci sono quasi il doppio di ASID validi.
Durante i passi 5 e 6, il PoC mostra alcune metriche:
CONTEXT è in uno stato in cui il firmware considera che non abbia ancora un ASID assegnato. In quei casi SNP_GUEST_STATUS restituirà 0 nel campo ASID.Nella maggior parte dei casi, la dismissione delle pagine CONTEXT corrotte causa un crash del firmware (molto probabilmente qui). A quanto pare i crash del firmware causano un riavvio dell'intero sistema. Per evitare tali crash, le patch del kernel impediscono la dismissione delle pagine CONTEXT. Uno svantaggio di ciò è che il modulo del kernel ccp non può essere scaricato. Una volta avviato il PoC, l'intero sistema deve essere riavviato prima di poterlo riavviare (indipendentemente dal fatto che il PoC abbia avuto successo o sia stato interrotto).
CONTEXT corrotta. Tieni traccia della pagina dei segreti. Ciò è possibile perché il firmware SEV tiene traccia internamente degli ASID attivi e non controlla le pagine CONTEXT attive per verificare la presenza di duplicati.CONTEXT corrotta per eseguire SNP_DBG_DECRYPT sulla pagina dei segreti del guest vittima.