
Exploit para a vulnerabilidade de firmware AMD SEV-SNP CVE-2024-21978, permitindo descriptografia de memória arbitrária de convidados via corrupção de memória de páginas de contexto.
Este repositório contém um exploit para uma vulnerabilidade no firmware SEV. O exploit permite descriptografar memória arbitrária de um convidado SEV-SNP em execução.
Testado na versão 1.55.16 (mais recente no momento em que este texto foi escrito).
O campo nv_paddr do comando SEV_INIT_EX pode ser usado para doar um bloco de memória ao firmware, para que ele possa ser usado no lugar da flash persistente. Se o SEV-SNP estiver habilitado, essa memória precisa estar no estado FIRMWARE. O firmware verifica isso uma vez durante a execução do comando SEV_INIT_EX. A partir daí, o firmware presume que essa memória está no estado FIRMWARE e grava nela sem verificações adicionais.
A suposição de que a memória ainda está no estado FIRMWARE nem sempre é correta; nada impede o host de alterar o estado de volta para HYPERVISOR usando o comando SNP_PAGE_RECLAIM. Depois que as páginas estão no estado HYPERVISOR, elas podem ser transicionadas para outros estados, por exemplo, CONTEXT. Mesmo que as páginas não estejam mais no estado FIRMWARE, o firmware grava nessas páginas, quebrando assim a integridade exigida por determinados estados de página.
Podemos explorar essa corrupção de memória mirando em páginas CONTEXT. Páginas CONTEXT são um alvo poderoso, mas há alguns problemas:
CONTEXT são criptografadas com uma chave diferente de outras memórias. Como resultado, não é fácil controlar o texto claro, mesmo que pudéssemos controlar o texto cifrado.A corrupção de memória efetivamente preenche a página CONTEXT com dados aleatórios, então não é fácil corromper páginas CONTEXT de uma forma que seja útil para o atacante. Para contornar isso, podemos acionar o bug repetidamente para causar corrupção e usar o comando SNP_GUEST_STATUS para ler de volta os campos relevantes da página CONTEXT corrompida até observarmos valores úteis.
O comando SNP_DBG_DECRYPT pode ser usado para descriptografar a memória de um convidado SEV-SNP com a política DEBUG habilitada. Se conseguirmos criar um CONTEXT de modo que ele tenha o flag DEBUG definido e contenha o ASID de outro convidado, podemos usá-lo para descriptografar a memória do outro convidado mesmo que ele não tenha a política DEBUG definida.
Acontece que SNP_DBG_DECRYPT ignora a maioria dos campos na página CONTEXT; ele verifica apenas gctx->guest.asid, gctx->guest.policy_snp e gctx->guest.guest_flags. A chance de esses campos estarem corretos após a corrupção de memória não é alta, mas também não está fora do reino da possibilidade. A boa notícia é que também podemos ler todos esses campos usando o comando GUEST_STATUS.
Em conclusão, podemos explorar o bug com as seguintes etapas:
nv_paddr para o estado FIRMWARE usando a instrução rmpupdate.SEV_INIT_EX.nv_paddr de volta para o estado HYPERVISOR usando o comando SNP_RECLAIM_PAGE.CONTEXT em nv_paddr.nv_paddr usando o comando SEV_PDH_GEN. Isso corrompe as páginas CONTEXT.GUEST_STATUS para verificar se SNP_DBG_DECRYPT teria sucesso; se não, volte para a etapa 5. O principal gargalo aqui é que os ASIDs são armazenados em um int de 32 bits, mas há muito menos ASIDs válidos (509 ou 1006 dependendo da CPU), então serão necessárias muitas tentativas até acertar.O exploit passa a maior parte do tempo nas etapas 5 e 6. As chances de encontrar todas as condições certas são de cerca de 1/20.000.000 em um EPYC Milan, e podemos fazer cerca de 100 tentativas por segundo, então esperamos encontrar as condições certas cerca de uma vez a cada dois dias (Aviso: os cálculos são apenas aproximações e posso ter errado em algo, mas, anedoticamente, uma vez a cada dois dias parece razoável). Podemos acelerar isso atacando não apenas uma página CONTEXT por vez, mas três páginas CONTEXT em nv_paddr, nv_paddr+4096 e nv_paddr+8192 (SEV_PDG_GEN corromperá três páginas). Convenientemente, essas etapas podem ser feitas antes de iniciar o convidado vítima e só precisam ter sucesso uma vez para atacar um número arbitrário de convidados (observe que o PoC atualmente ataca apenas um convidado).
Embora eu ainda não tenha conseguido testar isso, acredito que, uma vez que um atacante tenha usado essa vulnerabilidade para vazar as chaves de comunicação da plataforma da máquina virtual do convidado, ele deverá ser capaz de enviar mensagens do convidado ao firmware em nome do convidado e usar isso para solicitar relatórios de atestação. Isso viola um princípio fundamental do SEV-SNP, segundo o qual apenas o convidado deveria poder solicitar relatórios de atestação.
Alguns comandos (por exemplo, SNP_RECLAIM_PAGE, SNP_GCTX_CREATE, RING_BUFFER, talvez mais?, talvez todos apenas por segurança?) que aceitam uma página FIRMWARE devem verificar se ela se sobrepõe a nv_paddr e falhar caso isso ocorra.
Tenho mais uma preocupação, da qual não tenho certeza se é válida e adoraria ouvir a opinião de vocês: se entendi corretamente, o firmware SEV pode ser atualizado sem interromper convidados em execução. Isso me leva a crer que seria possível carregar a página CONTEXT corrompida de uma versão antiga e vulnerável do firmware para uma nova versão corrigida. Seria possível começar em uma versão antiga e vulnerável, executar o exploit descrito acima, atualizar e fazer o commit do novo firmware corrigido, iniciar o convidado com o novo firmware (para que a versão antiga do firmware não apareça no relatório de atestação) e então usar a página CONTEXT corrompida criada com o firmware antigo para atacar o convidado criado com a nova versão? Um consumidor dos relatórios de atestação criados pelo novo convidado conseguiria perceber que a versão antiga do firmware estava em execução em algum momento antes de o novo convidado ser iniciado? Se não, são necessárias mitigações adicionais para evitar que isso aconteça?
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, 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
Observe que é normal o exploit ser executado por um período de tempo significativo (na ordem de horas, se você tiver sorte; dias, se não tiver). Executar em um EPYC Genoa provavelmente será mais rápido, pois há quase duas vezes mais ASIDs válidos.
Durante as etapas 5 e 6, o PoC exibe algumas métricas:
CONTEXT está em um estado no qual o firmware considera que ainda não há um ASID atribuído. Nesses casos, SNP_GUEST_STATUS retornará 0 no campo ASID.Na maioria dos casos, descomissionar páginas CONTEXT corrompidas causa uma falha no firmware (muito provavelmente aqui). Até onde sei, falhas no firmware causam um reset de todo o sistema. Para evitar essas falhas, os patches do kernel impedem que páginas CONTEXT sejam descomissionadas. Uma desvantagem disso é que o módulo do kernel ccp não pode ser descarregado. Depois que o PoC é iniciado, todo o sistema precisa ser reinicializado antes que ele possa ser iniciado novamente (independentemente de o PoC ter sido bem-sucedido ou abortado).
CONTEXT corrompida. Acompanhe a página de segredos. Isso é possível porque o firmware SEV rastreia ASIDs ativos internamente e não verifica as páginas CONTEXT ativas em busca de duplicatas.CONTEXT corrompida para executar SNP_DBG_DECRYPT na página de segredos do convidado vítima.