
PoC für CVE-2026-53360: gastausgelöster Heap-Out-of-Bounds-Lese-/Schreibzugriff in der KVM SEV-SNP Page State Change (PSC)-Verarbeitung.
Proof of Concept für einen Heap-Out-of-Bounds-Read und -Write in der SEV-SNP Page State Change (PSC)-Behandlung von KVM. Ein bösartiger SEV-SNP-Gast bringt den Host-Kernel dazu, ein PSC-Eintragsarray über das Ende seiner Slab-Allokation hinaus zu durchlaufen. Dadurch wird das Layout benachbarter kmalloc-cg-32-Objekte preisgegeben, und ein kontrollierter kleiner Wert wird in diese geschrieben. Der Gast kann dies beliebig oft wiederholen.
Ausführlicher Artikel: https://cyberstan.co.uk/sev-snp-oob/
| CVE | CVE-2026-53360 |
| Komponente | KVM SNP Host-Unterstützung, arch/x86/kvm/svm/sev.c |
| Eingeführt | 9b54e248d264 (erste KVM SNP PSC-Behandlung, Mai 2024, ~v6.10) |
| Behebung | db3f219 (Mainline, Mai 2026, Cc: stable), getaggt Fixes: 4af663c |
| Gemeldet | [email protected], 8. April 2026 |
| Betroffen | Nur SEV-SNP-Host-Pfad. KVM aktiviert PSC nicht für normale SEV-ES-Gäste. |
Jeder SEV-SNP-Gast kann den Heap des Host-Kernels korrumpieren und Informationen über dessen Layout auslesen, indem er eine fehlerhafte PSC-Anfrage sendet. Dies ist die Richtung Gast → Host: SEV-SNP soll den Gast vor einem nicht vertrauenswürdigen Host schützen, aber der Host muss sich auch gegen einen bösartigen Gast verteidigen – und dieser Handler tut das nicht.
Hierfür wird echte SEV-SNP-Hardware benötigt. Es kann nicht auf Intel reproduziert werden, und durch verschachtelte Virtualisierung erhält man keinen SNP-Gast.
Hardware:
*.metal, Hetzner AX und ähnliche).Host-Kernel:
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
Host-Benutzerbereich:
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)
Gast:
build-essential und linux-headers-$(uname -r), um das Modul darin bauen zu können.SEV-SNP-Gäste kommunizieren mit dem Host über die GHCB, eine 4-KB-seitige Shared Page. Eine PSC-Anfrage setzt SW_EXITCODE auf SVM_VMGEXIT_PSC (0x80000010), zeigt mit SW_SCRATCH auf einen Deskriptor und legt die Deskriptorlänge in SW_EXITINFO2 ab.
Der Deskriptor ist ein struct psc_buffer: ein 8-Byte-Header, gefolgt von einem Array aus 8-Byte-Einträgen. Es gibt kein explizites Zählfeld. Der Host verarbeitet Einträge von hdr->cur_entry bis hdr->end_entry, beide vom Gast gesteuert.
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 */
Ein GHCB-v2+-Gast soll seinen Scratch-Bereich innerhalb des 2032-Byte Shared Buffers der GHCB halten, damit der Host sein bestehendes Mapping wiederverwenden kann. (2032 - 8) / 8 = 253 Einträge passen hinein – daher kommt das Protokoll-Maximum VMGEXIT_PSC_MAX_COUNT (253). Diese Zahl ergibt nur Sinn, wenn der Puffer wirklich der Shared Buffer ist.
Wenn der Gast den Scratch-Bereich außerhalb der GHCB platziert, kann der Host sein Mapping nicht verwenden, daher allokiert setup_vmgexit_scratch() einen separaten Puffer in der vom Gast angeforderten Größe. SNP sollte diesen Pfad nie nehmen, aber nichts verhindert es:
scratch_va = kvzalloc(len, GFP_KERNEL_ACCOUNT); /* len == exit_info_2, vom Gast gesteuert */
len kommt direkt vom Gast, und GFP_KERNEL_ACCOUNT platziert die Allokation in den Cgroup-kontierten kmalloc-cg-N-Caches. Fordert man exit_info_2 = 24 an, erhält man eine 24-Byte-Allokation im 32-Byte-Slot kmalloc-cg-32: Platz für den Header plus zwei Einträge. Alles jenseits von entries[1] ist der Speicher eines anderen Objekts.
Dann prüft snp_begin_psc() die Anzahl der Einträge gegen die Protokollkonstante, nicht gegen den tatsächlich allokierten Puffer:
idx_end = hdr->end_entry;
if (idx_end >= VMGEXIT_PSC_MAX_COUNT) { /* prüft auf 253, NICHT auf die Puffergröße */
snp_complete_psc(svm, ...);
return 1;
}
for (idx = idx_start; idx <= idx_end; idx++) {
entry_start = entries[idx]; /* OOB sobald idx >= 2 */
...
}
Bei einem 24-Byte-Puffer existieren nur zwei Einträge, aber die Prüfung erlaubt end_entry bis zu 252. Setzt man es auf 252, durchläuft die Schleife etwa 2 KB über die Allokation hinaus, über angrenzende Slab-Objekte hinweg.
Jeder Schritt über das Ende hinaus interpretiert die nächsten 8 Bytes des Slab-Speichers als psc_entry und führt sie durch den PSC-Code. Das ergibt drei Dinge:
entry.gfn und entry.operation aus Speicher, der dem Puffer nie gehörte. Dies ist der Slab-Out-of-Bounds-Read, den KASAN abfängt.KVM_HC_MAP_GPA_RANGE abgesetzt wird, schreibt der Abschlusscode zurück in denselben OOB-Slot: entries[idx].cur_page = entry.pagesize ? 512 : 1. Einer von zwei kleinen Werten in die unteren 12 Bits eines Wortes, das der Gast auswählt – wiederholbar.SW_EXITINFO2 den Index, bei dem abgebrochen wurde. Erhöht man end_entry schrittweise, wird Slot für Slot preisgegeben, ob der benachbarte Speicher als No-Op oder als etwas Fehlschlagendes dekodiert wurde. Das reicht aus, um Objektgrenzen zu finden und Null von Nicht-Null zu unterscheiden.Jeder VMGEXIT allokiert den Scratch-Puffer neu, daher landen wiederholte Anfragen in verschiedenen Freelist-Slots und lassen den Gast über Nachbarn streifen, anstatt bei einem hängenzubleiben. Zusammen ergibt dies Heap-Layout-Offenlegung, den eingeschränkten Write von oben und Use-After-Free über Anfragen hinweg.
trigger.c ist ein Gast-Kernel-Modul. Lädt man es innerhalb eines SEV-SNP-Gasts, durchläuft es ab einem einzigen insmod vier Stufen gegen den Host: