
CVE-2026-53360에 대한 PoC: KVM SEV-SNP 페이지 상태 변경(PSC) 처리에서 게스트가 트리거하는 힙 out-of-bounds 읽기/쓰기
KVM의 SEV-SNP 페이지 상태 변경(PSC) 처리에서 발생하는 힙 읽기 및 쓰기 오버플로우에 대한 개념 증명입니다. 악의적인 SEV-SNP 게스트가 호스트 커널로 하여금 PSC 항목 배열을 슬랩 할당 영역 끝까지 탐색하게 만듭니다. 이로 인해 인접한 kmalloc-cg-32 객체의 레이아웃이 유출되고, 제어된 작은 값이 그 객체에 기록되며, 게스트는 이를 원하는 만큼 반복할 수 있습니다.
전체 분석: https://cyberstan.co.uk/sev-snp-oob/
| CVE | CVE-2026-53360 |
| 구성 요소 | KVM SNP 호스트 지원, arch/x86/kvm/svm/sev.c |
| 도입 시점 | 9b54e248d264 (최초 KVM SNP PSC 처리, 2024년 5월, ~v6.10) |
| 수정 시점 | db3f219 (메인라인, 2026년 5월, Cc: stable), 태그 Fixes: 4af663c |
| 보고일 | [email protected], 2026년 4월 8일 |
| 영향 범위 | SEV-SNP 호스트 경로에만 해당. 일반 SEV-ES 게스트에 대해 KVM은 PSC를 활성화하지 않습니다. |
모든 SEV-SNP 게스트는 잘못된 형식의 PSC 요청을 전송하여 호스트 커널의 힙을 손상시키고 그 레이아웃에 대한 정보를 읽어낼 수 있습니다. 이는 게스트에서 호스트 방향입니다. SEV-SNP는 신뢰할 수 없는 호스트로부터 게스트를 보호하기 위해 설계되었지만, 호스트는 여전히 악의적인 게스트로부터 자신을 방어해야 하며, 이 핸들러는 그렇게 하지 못합니다.
실제 SEV-SNP 하드웨어가 필요합니다. Intel에서는 재현할 수 없으며, 중첩 가상화는 SNP 게스트를 제공하지 않습니다.
하드웨어:
*.metal, Hetzner AX 등).호스트 커널:
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_KVM=y
CONFIG_KVM_AMD=y
CONFIG_KVM_AMD_SEV=y
CONFIG_CRYPTO_DEV_SP_PSP=y
kvm_amd.sev=1 kvm_amd.sev_es=1 kvm_amd.sev_snp=1 kasan_multi_shot
cat /sys/module/kvm_amd/parameters/sev_snp # Y
ls /dev/sev # /dev/sev
호스트 사용자 공간:
git clone https://github.com/AMDESE/qemu.git
cd qemu && git checkout snp-latest
mkdir build && cd build
../configure --target-list=x86_64-softmmu && make -j$(nproc)
게스트:
build-essential과 linux-headers-$(uname -r)이 설치되어 있어야 합니다.SEV-SNP 게스트는 GHCB(4KB 공유 페이지)를 통해 호스트와 통신합니다. PSC 요청은 SW_EXITCODE를 SVM_VMGEXIT_PSC (0x80000010)로 설정하고, SW_SCRATCH가 디스크립터를 가리키게 하고, 디스크립터 길이를 SW_EXITINFO2에 넣습니다.
디스크립터는 struct psc_buffer입니다: 8바이트 헤더와 8바이트 항목들의 배열로 구성됩니다. 명시적인 개수 필드는 없습니다. 호스트는 hdr->cur_entry부터 hdr->end_entry까지의 항목을 처리하며, 둘 다 게스트가 제어합니다.
struct psc_hdr {
u16 cur_entry;
u16 end_entry;
u32 reserved;
} __packed; /* 8 bytes */
struct psc_entry {
u64 cur_page : 12;
u64 gfn : 40;
u64 operation : 4;
u64 pagesize : 1;
u64 reserved : 7;
} __packed; /* 8 bytes */
GHCB v2+ 게스트는 스크래치 영역을 GHCB의 2032바이트 공유 버퍼 내에 유지해야 하므로, 호스트는 기존 매핑을 재사용할 수 있습니다. 거기에는 (2032 - 8) / 8 = 253개의 항목이 들어가며, 이것이 프로토콜 최대값 VMGEXIT_PSC_MAX_COUNT (253)의 유래입니다. 이 숫자는 버퍼가 실제로 공유 버퍼일 때만 의미가 있습니다.
게스트가 스크래치 영역을 GHCB 외부로 지정하면 호스트는 자신의 매핑을 사용할 수 없으므로, setup_vmgexit_scratch()는 게스트가 요청한 크기의 별도 버퍼를 할당합니다. SNP는 이 경로를 절대 사용하지 않아야 하지만, 이를 막는 것은 없습니다:
scratch_va = kvzalloc(len, GFP_KERNEL_ACCOUNT); /* len == exit_info_2, 게스트가 제어 */
len은 게스트로부터 직접 오며, GFP_KERNEL_ACCOUNT는 cgroup-accounted kmalloc-cg-N 캐시에 할당을 넣습니다. exit_info_2 = 24를 요청하면 32바이트 kmalloc-cg-32 슬롯에 24바이트 할당이 이루어집니다: 헤더와 두 개의 항목을 위한 공간. entries[1] 이후의 모든 것은 다른 객체의 메모리입니다.
그런 다음 snp_begin_psc()는 항목 개수를 프로토콜 상수와 비교하지만, 실제로 할당된 버퍼의 크기는 고려하지 않습니다:
idx_end = hdr->end_entry;
if (idx_end >= VMGEXIT_PSC_MAX_COUNT) { /* 253을 확인하지만 버퍼 크기는 아님 */
snp_complete_psc(svm, ...);
return 1;
}
for (idx = idx_start; idx <= idx_end; idx++) {
entry_start = entries[idx]; /* idx >= 2이면 범위 초과 */
...
}
24바이트 버퍼에는 항목이 두 개만 있지만, 확인 코드는 end_entry를 최대 252까지 허용합니다. 이를 252로 설정하면 루프는 할당된 영역을 약 2KB 지나 인접한 슬랩 객체들을 탐색합니다.
범위를 벗어난 각 단계에서 다음 8바이트의 슬랩 메모리를 psc_entry로 재해석하여 PSC 코드를 통해 실행합니다. 이는 세 가지를 제공합니다:
entry.gfn과 entry.operation을 추출합니다. KASAN이 잡는 슬랩 범위 초과 읽기입니다.KVM_HC_MAP_GPA_RANGE로 전달되면, 완료 코드가 동일한 OOB 슬롯에 다시 씁니다: entries[idx].cur_page = entry.pagesize ? 512 : 1. 게스트가 선택한 워드의 하위 12비트에 두 개의 작은 값 중 하나가 기록되며, 반복 가능합니다.SW_EXITINFO2의 응답이 중단된 인덱스를 보고합니다. end_entry를 하나씩 증가시키면 인접 메모리가 no-op으로 디코딩되었는지, 아니면 실패한 것으로 디코딩되었는지 슬롯별로 누출되어, 객체 경계를 찾고 0과 0이 아닌 값을 구별하기에 충분합니다.각 VMGEXIT는 스크래치 버퍼를 다시 할당하므로, 반복된 요청은 다른 freelist 슬롯에 배치되어 게스트가 하나에 고정되지 않고 이웃을 훑을 수 있습니다. 이를 종합하면 힙 레이아웃 공개, 위의 제한된 쓰기, 그리고 요청 간 use-after-free가 가능합니다.
trigger.c는 게스트 커널 모듈입니다. SEV-SNP 게스트 내에서 로드하면 단일 insmod로 호스트에 대해 네 단계를 실행합니다:
cur_page를 0인 이웃에 쓰고, 이후 요청에서 이를 건너뛰는지 확인합니다.end_entry=200으로 하나의 요청을 보내고, 0이 아닌 데이터에 도달할 때까지 OOB 읽기가 얼마나 멀리 도달하는지 측정합니다.entries[3..10]이 범위를 벗어난 상태로 200개의 요청을 보내며, 각각 호스트에서 KASAN 보고서를 발생시킵니다.모듈은 페이지를 할당하고, set_memory_decrypted()로 암호 해제 표시한 후, 이를 스크래치 영역으로 사용하고 GHCB PSC 요청을 수동으로 구성합니다. -EAGAIN으로 종료되어 로드된 상태로 남지 않습니다.
OVMF 및 디스크 경로를 설정에 맞게 조정하십시오:
qemu-system-x86_64 \
-enable-kvm -cpu EPYC-v4 \
-machine q35,confidential-guest-support=sev0,memory-backend=ram1 \
-object memory-backend-memfd,id=ram1,size=4G \
-object sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,policy=0x30000 \
-smp 4 -m 4G \
-bios OVMF_SNP.fd \
-drive file=guest.qcow2,format=qcow2,if=virtio \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0 \
-nographic
trigger.c와 Makefile을 게스트에 복사한 후:
make
insmod trigger.ko
모듈은 먼저 CPUID를 통해 SEV-SNP를 확인하고 다른 곳에서는 실행을 거부합니다. 네 단계를 실행하고 스스로 언로드됩니다 (init이 -EAGAIN을 반환하므로 상주하지 않습니다).
호스트에서:
dmesg | grep -E "KASAN|BUG|snp_begin_psc"
예상 출력:
BUG: KASAN: slab-out-of-bounds in snp_begin_psc+0x126/0x890
Read of size 8 at addr ffff888219ffb5e0 by task qemu-system-x86/2199
BUG: KASAN: slab-out-of-bounds in snp_begin_psc+0x468/0x890
Write of size 8 at addr ffff888351566648 by task qemu-system-x86/2199
The buggy address belongs to the object at ffff888XXXXXXXXX
which belongs to the cache kmalloc-cg-32 of size 32
테스트 호스트에서 단일 insmod로 73개의 KASAN 보고서가 생성되었습니다 (62개의 슬랩 범위 초과, 7개의 슬랩 use-after-free, 4개의 use-after-free). 모두 kmalloc-cg-32 대상입니다. 테스트 호스트: AMD EPYC 7443P, Ubuntu 24.04.4, 커널 6.11.11 (KASAN 포함), 게스트는 AMDESE QEMU (snp-latest).
업스트림 수정은 GHCB v2 이상에서 GHCB 외부 스크래치 영역을 setup_vmgexit_scratch()에서 거부하여 버퍼를 고정된 알려진 크기로 고정시켜 루프가 끝을 넘어 실행되지 않도록 합니다:
} else {
+ /* GHCB v2 requires the scratch area to be within the GHCB. */
+ if (to_kvm_sev_info(svm->vcpu.kvm)->ghcb_version >= 2)
+ goto e_scratch;
+
/*
* The guest memory must be read into a kernel buffer, so
* limit the size
이 네 줄이 db3f219입니다. 이는 실제 버퍼 크기에 대해 항목 개수를 제한하고 READ_ONCE()를 통해 디스크립터를 다시 읽는 더 큰 시리즈의 일부로, 동일한 핸들러에서 버퍼 내 오프셋 변형과 time-of-check/time-of-use 레이스를 막습니다.
| 파일 | 설명 |
|---|---|
trigger.c | 네 가지 PoC 단계를 실행하는 게스트 커널 모듈 |
Makefile | 실행 중인 게스트 커널에 대해 trigger.ko를 빌드 |
LICENSE | GPL-2.0, 모듈의 와 일치 |
not an SEV-SNP guest: QEMU가 sev-snp-guest로 실행되지 않았거나 호스트 SNP가 꺼져 있습니다.SEV-SNP not supported: /sys/module/kvm_amd/parameters/sev_snp, BIOS 설정, 부팅 매개변수를 확인하십시오.LAUNCH_START failed: PSP가 초기화되지 않았습니다. dmesg | grep psp와 CONFIG_CRYPTO_DEV_SP_PSP=y를 확인하십시오.CONFIG_KASAN=y와 kasan_multi_shot를 확인하십시오.이것은 호스트 커널 힙 메모리를 손상시키고 KASAN을 트리거하며 호스트를 충돌시킬 수 있습니다. 자신이 제어하는 폐기 가능한 테스트 호스트와 소유한 VM에서만 실행하십시오. 공유 또는 프로덕션 인프라에서 실행하지 마십시오.
db3f219 커밋 추적)db3f219, 작성자 Mike Roth, 검토자 Tom Lendacky, 커밋자 Paolo Bonzini9b54e248d264; 수정 태그 Fixes: 4af663ctrigger.c는 GPL-2.0이며, MODULE_LICENSE와 일치합니다. LICENSE를 참조하십시오.
MODULE_LICENSE