
Este repositorio contiene un exploit para una vulnerabilidad en el firmware SEV. El exploit permite descifrar memoria arbitraria de un invitado SEV-SNP en ejecución.
Probado en la versión 1.55.16 (la más reciente en el momento de escribir esto).
El campo nv_paddr del comando SEV_INIT_EX se puede usar para donar un fragmento de memoria al firmware, de modo que pueda usarse en lugar de la memoria flash persistente. Si SEV-SNP está habilitado, esta memoria tiene que estar en el estado FIRMWARE. El firmware comprueba esto una sola vez mientras ejecuta el comando SEV_INIT_EX. A partir de ese momento, el firmware asume que esta memoria está en el estado FIRMWARE y escribe en ella sin comprobaciones adicionales.
La suposición de que la memoria sigue en el estado FIRMWARE no siempre es correcta; nada impide que el host cambie el estado de nuevo al estado HYPERVISOR mediante el comando SNP_PAGE_RECLAIM. Una vez que las páginas están en el estado HYPERVISOR, se pueden transicionar a otros estados, por ejemplo, CONTEXT. Aunque las páginas ya no están en el estado FIRMWARE, el firmware escribirá en ellas, rompiendo así la integridad requerida por ciertos estados de página.
Podemos explotar esta corrupción de memoria apuntando a páginas CONTEXT. Las páginas CONTEXT son un objetivo potente, pero hay algunos problemas:
CONTEXT están cifradas con una clave distinta a la del resto de la memoria. Como resultado, no es fácil controlar el texto plano incluso si pudiéramos controlar el texto cifrado.La corrupción de memoria efectivamente llena la página CONTEXT con datos aleatorios, por lo que no es fácil corromper las páginas CONTEXT de una manera que sea útil para el atacante. Para solucionar eso, podemos disparar el bug repetidamente para causar corrupción y usar el comando SNP_GUEST_STATUS para leer los campos relevantes de la página CONTEXT corrupta hasta que observemos valores que sean útiles.
El comando SNP_DBG_DECRYPT se puede usar para descifrar la memoria de un invitado SEV-SNP con la política DEBUG habilitada. Si podemos manipular un CONTEXT de modo que tenga el flag DEBUG activado y contenga el ASID de otro invitado, podemos usarlo para descifrar la memoria del otro invitado aunque este no tenga la política DEBUG configurada.
Resulta que SNP_DBG_DECRYPT ignora la mayoría de los campos en la página CONTEXT; solo comprueba gctx->guest.asid, gctx->guest.policy_snp y gctx->guest.guest_flags. La probabilidad de que estos campos sean correctos después de la corrupción de memoria no es alta, pero tampoco es imposible. La buena noticia es que también podemos leer todos esos campos usando el comando GUEST_STATUS.
En conclusión, podemos explotar el bug con los siguientes pasos:
nv_paddr al estado FIRMWARE usando la instrucción rmpupdate.SEV_INIT_EX.nv_paddr de nuevo al estado HYPERVISOR usando el comando SNP_RECLAIM_PAGE.CONTEXT en nv_paddr.nv_paddr usando el comando SEV_PDH_GEN. Esto corrompe las páginas CONTEXT.GUEST_STATUS para comprobar si SNP_DBG_DECRYPT tendría éxito; si no es así, volver al paso 5. El principal cuello de botella aquí es que los ASID se almacenan en un int de 32 bits, pero hay muchos menos ASID válidos (509 o 1006 dependiendo de la CPU), por lo que se necesitarán bastantes intentos para que esto salga bien.El exploit pasa la mayor parte del tiempo en los pasos 5 y 6. Las probabilidades de acertar todas las condiciones son de aproximadamente 1/20.000.000 en un EPYC Milan y podemos hacer alrededor de 100 intentos por segundo, por lo que esperamos acertar las condiciones aproximadamente una vez cada dos días (Advertencia: los cálculos son solo aproximaciones y puede que haya metido la pata en algo, pero anecdóticamente, una vez cada dos días parece correcto). Podemos acelerar esto no atacando una sola página CONTEXT a la vez, sino tres páginas CONTEXT en nv_paddr, nv_paddr+4096 y nv_paddr+8192 (SEV_PDG_GEN corromperá tres páginas). Convenientemente, esos pasos se pueden hacer antes de lanzar al invitado víctima y solo tienen que tener éxito una vez para atacar a un número arbitrario de invitados (aunque hay que tener en cuenta que el PoC actualmente ataca solo a un invitado).
Aunque todavía no he podido probar esto, creo que una vez que un atacante haya usado esta vulnerabilidad para filtrar las claves de comunicación de la plataforma de la máquina virtual del invitado, debería poder enviar mensajes de invitado al firmware en nombre del invitado y usar esto para solicitar informes de atestación. Esto viola un principio clave de SEV-SNP en el que solo el invitado debería poder solicitar informes de atestación.
Algunos comandos (por ejemplo, SNP_RECLAIM_PAGE, SNP_GCTX_CREATE, RING_BUFFER, ¿quizás más?, ¿quizás todos para estar seguros?) que aceptan una página FIRMWARE deberían comprobar si se solapa con nv_paddr y fallar si es así.
Tengo una preocupación más, que no estoy seguro de que sea válida y me encantaría escuchar su opinión: Si entiendo correctamente, el firmware SEV se puede actualizar sin interrumpir a los invitados en ejecución. Esto me sugiere que sería posible transferir la página CONTEXT corrupta de una versión antigua vulnerable del firmware a una versión nueva corregida. ¿Sería posible comenzar con una versión antigua vulnerable, hacer el exploit descrito anteriormente, actualizar y confirmar el nuevo firmware corregido, lanzar el invitado con el nuevo firmware (para que la versión antigua del firmware no aparezca en el informe de atestación) y luego usar la página CONTEXT corrupta creada con el firmware antiguo para atacar al invitado creado con la nueva versión? ¿Podría un consumidor de los informes de atestación generados por el nuevo invitado saber que la versión antigua del firmware se estaba ejecutando en algún momento antes de que se lanzara el nuevo invitado? Si no es así, ¿se requieren más mitigaciones para evitar que esto suceda?
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, 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
Ten en cuenta que es normal que el exploit se ejecute durante un tiempo significativo (del orden de horas si tienes suerte, días si no la tienes). Ejecutarlo en un EPYC Genoa probablemente será más rápido porque hay casi el doble de ASID válidos.
Durante los pasos 5 y 6 el PoC muestra algunas métricas:
CONTEXT está en un estado en el que el firmware considera que aún no se le ha asignado un ASID. En esos casos, SNP_GUEST_STATUS devolverá 0 en el campo ASID.En la mayoría de los casos, retirar las páginas CONTEXT corruptas causa un fallo del firmware (muy probablemente aquí). Hasta donde sé, los fallos del firmware provocan un reinicio de todo el sistema. Para evitar tales fallos, los parches del kernel evitan que las páginas CONTEXT puedan ser retiradas. Un inconveniente de esto es que el módulo del kernel ccp no se puede descargar. Una vez que el PoC se ha iniciado, todo el sistema debe reiniciarse antes de poder iniciarlo de nuevo (independientemente de si el PoC tuvo éxito o fue abortado).
CONTEXT corrupta. Llevar un registro de la página de secretos. Esto es posible porque el firmware SEV rastrea los ASID activos internamente y no consulta las páginas CONTEXT activas para comprobar duplicados.CONTEXT corrupta para ejecutar SNP_DBG_DECRYPT sobre la página de secretos del invitado víctima.