
AMD SEV-SNP 펌웨어 취약점 CVE-2024-21978에 대한 익스플로잇으로, 컨텍스트 페이지의 메모리 손상을 통해 임의 게스트 메모리의 복호화를 가능하게 합니다.
이 저장소는 SEV 펌웨어의 취약점에 대한 익스플로잇을 포함합니다. 익스플로잇은 실행 중인 SEV-SNP 게스트의 임의 메모리를 복호화할 수 있습니다.
버전 1.55.16(작성 시점 기준 최신)에서 테스트되었습니다.
SEV_INIT_EX 명령의 nv_paddr 필드는 펌웨어에 메모리 청크를 기증하는 데 사용될 수 있으며, 이를 통해 영구 플래시 대신 사용할 수 있습니다. SEV-SNP가 활성화된 경우 이 메모리는 FIRMWARE 상태여야 합니다. 펌웨어는 SEV_INIT_EX 명령을 실행하는 동안 확인합니다. 그 이후부터 펌웨어는 이 메모리가 FIRMWARE 상태라고 가정하고 추가 확인 없이 쓰기를 수행합니다.
메모리가 여전히 FIRMWARE 상태라는 가정은 항상 올바른 것은 아닙니다. SNP_PAGE_RECLAIM 명령을 사용하여 호스트가 상태를 다시 HYPERVISOR 상태로 변경하는 것을 막을 수 없습니다. 페이지가 HYPERVISOR 상태가 되면 CONTEXT와 같은 다른 상태로 전환될 수 있습니다. 페이지가 더 이상 FIRMWARE 상태가 아니더라도 펌웨어는 해당 페이지에 쓰기를 수행하여 특정 페이지 상태에 필요한 무결성을 깨뜨립니다.
CONTEXT 페이지를 대상으로 이 메모리 손상을 악용할 수 있습니다. CONTEXT 페이지는 강력한 대상이지만 몇 가지 문제가 있습니다:
CONTEXT 페이지는 다른 메모리와 다른 키로 암호화됩니다. 결과적으로 암호문을 제어할 수 있더라도 평문을 제어하기 쉽지 않습니다.메모리 손상은 사실상 CONTEXT 페이지를 무작위 데이터로 채우므로 공격자에게 유용한 방식으로 CONTEXT 페이지를 실제로 손상시키는 것은 쉽지 않습니다. 이 문제를 해결하기 위해 버그를 반복적으로 트리거하여 손상을 일으키고 SNP_GUEST_STATUS 명령을 사용하여 손상된 CONTEXT 페이지의 관련 필드를 읽어 유용한 값이 관찰될 때까지 반복할 수 있습니다.
SNP_DBG_DECRYPT 명령은 DEBUG 정책이 활성화된 SEV-SNP 게스트의 메모리를 복호화하는 데 사용할 수 있습니다. DEBUG 플래그가 설정되고 다른 게스트의 ASID를 포함하도록 CONTEXT를 조작할 수 있다면, 다른 게스트가 DEBUG 정책을 설정하지 않았더라도 해당 게스트의 메모리를 복호화하는 데 사용할 수 있습니다.
알고 보니 SNP_DBG_DECRYPT는 CONTEXT 페이지의 대부분의 필드를 무시하고, gctx->guest.asid, gctx->guest.policy_snp 및 gctx->guest.guest_flags만 확인합니다. 메모리 손상 후 이러한 필드가 올바를 확률은 높지 않지만 불가능한 것도 아닙니다. 좋은 소식은 GUEST_STATUS 명령을 사용하여 이러한 모든 필드를 읽을 수 있다는 것입니다.
결론적으로, 다음 단계를 통해 버그를 악용할 수 있습니다:
rmpupdate 명령어를 사용하여 nv_paddr을 FIRMWARE 상태로 전환합니다.SEV_INIT_EX 명령을 실행합니다.SNP_RECLAIM_PAGE 명령을 사용하여 nv_paddr을 다시 HYPERVISOR 상태로 전환합니다.nv_paddr에 하나 이상의 CONTEXT 페이지를 생성합니다.SEV_PDH_GEN 명령을 사용하여 펌웨어가 nv_paddr에 쓰도록 속입니다. 이렇게 하면 CONTEXT 페이지가 손상됩니다.GUEST_STATUS 명령을 사용하여 SNP_DBG_DECRYPT가 성공할지 확인하고, 실패하면 5단계로 돌아갑니다. 여기서 주요 병목은 ASID가 32비트 정수로 저장되지만 유효한 ASID는 훨씬 적기(CPU에 따라 509 또는 1006) 때문에 올바른 값을 얻기 위해 꽤 많은 시도가 필요하다는 점입니다.CONTEXT 페이지에 있는 손상된 ASID를 사용하여 피해자 게스트를 시작하고(선택적으로 실행) 시크릿 페이지를 추적합니다. 이는 SEV 펌웨어가 내부적으로 활성 ASID를 추적하고 중복 확인을 위해 활성 CONTEXT 페이지를 확인하지 않기 때문에 가능합니다.CONTEXT 페이지를 사용하여 피해자 게스트의 시크릿 페이지에 대해 SNP_DBG_DECRYPT를 실행합니다.익스플로잇은 대부분의 시간을 5단계와 6단계에서 소비합니다. EPYC Milan에서 모든 올바른 조건을 맞출 확률은 약 1/20,000,000이며 초당 약 100회의 시도를 할 수 있으므로 약 이틀에 한 번 정도 올바른 조건을 맞출 것으로 예상됩니다(경고: 계산은 근사치일 뿐이며 제가 뭔가 잘못했을 수도 있지만, 경험적으로 이틀에 한 번 정도가 적절해 보입니다). 한 번에 하나의 CONTEXT 페이지만 공격하는 것이 아니라 nv_paddr, nv_paddr+4096, nv_paddr+8192에 있는 세 개의 CONTEXT 페이지를 공격하여 속도를 높일 수 있습니다(SEV_PDG_GEN은 세 페이지를 손상시킵니다). 편리하게도 이러한 단계는 피해자 게스트를 시작하기 전에 미리 수행할 수 있으며, 한 번만 성공하면 임의의 수의 게스트를 공격할 수 있습니다(단, 현재 PoC는 하나의 게스트만 공격합니다).
아직 테스트해보지는 못했지만, 공격자가 이 취약점을 이용하여 게스트의 가상 머신 플랫폼 통신 키를 유출한 후에는 게스트를 대신하여 펌웨어에 게스트 메시지를 보내고 이를 사용하여 증명 보고서를 요청할 수 있을 것으로 믿습니다. 이는 게스트만이 증명 보고서를 요청할 수 있어야 한다는 SEV-SNP의 핵심 원칙을 위반합니다.
FIRMWARE 페이지를 허용하는 몇몇 명령(예: SNP_RECLAIM_PAGE, SNP_GCTX_CREATE, RING_BUFFER, 더 있을 수 있음? 아니면 안전을 위해 모두?)은 nv_paddr과 겹치는지 확인하고 겹칠 경우 실패해야 합니다.
제가 한 가지 더 우려하는 점이 있는데, 이것이 타당한지 확신이 서지 않아 여러분의 의견을 듣고 싶습니다: 제가 이해하기로는 SEV 펌웨어는 실행 중인 게스트를 중단하지 않고 업그레이드할 수 있습니다. 이는 손상된 CONTEXT 페이지를 이전의 취약한 펌웨어 버전에서 새로 수정된 펌웨어 버전으로 이월할 수 있음을 의미합니다. 이전 취약한 버전에서 시작하여 위에서 설명한 익스플로잇을 수행하고, 새로 수정된 펌웨어로 업그레이드 및 커밋한 후, 새 펌웨어로 게스트를 시작하여(증명 보고서에 이전 펌웨어 버전이 표시되지 않도록) 이전 펌웨어로 생성된 손상된 CONTEXT 페이지를 사용하여 새 버전으로 생성된 게스트를 공격할 수 있을까요? 새 게스트가 생성한 증명 보고서를 소비하는 사람이 새 게스트가 시작되기 전에 이전 펌웨어 버전이 실행 중이었다는 것을 알 수 있을까요? 그렇지 않다면 이러한 일이 발생하지 않도록 추가 완화 조치가 필요한가요?
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
익스플로잇이 상당한 시간 동안 실행되는 것은 정상입니다(운이 좋으면 몇 시간, 그렇지 않으면 며칠). EPYC Genoa에서 실행하면 유효한 ASID가 거의 두 배 더 많기 때문에 더 빠를 것입니다.
5단계와 6단계 동안 PoC는 몇 가지 지표를 표시합니다:
CONTEXT 페이지가 펌웨어에 아직 ASID가 할당되지 않은 상태로 간주됩니다. 이러한 경우 SNP_GUEST_STATUS는 ASID 필드에 0을 반환합니다.대부분의 경우 손상된 CONTEXT 페이지를 폐기하면 펌웨어가 충돌합니다(가장 가능성 있는 위치는 여기). 제가 알기로 펌웨어 충돌은 전체 시스템을 재설정합니다. 이러한 충돌을 방지하기 위해 커널 패치는 CONTEXT 페이지가 폐기되지 않도록 합니다. 이로 인한 단점은 ccp 커널 모듈을 언로드할 수 없다는 것입니다. PoC가 시작되면 (성공 여부나 중단 여부에 관계없이) 다시 시작하려면 전체 시스템을 재부팅해야 합니다.