Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-53360-POC — PoC for CVE-2026-53360: guest-triggered heap out-of-bounds read/write in KVM SEV-SNP Page State Change (PSC) handling. | Kitploit
Tools/GitHubGitHub/0xcyberstan/cve-2026-53360-poc
Memory ForensicsVulnerability AnalysisExploitationHardware SecurityBinary Exploitation
GitHub0xcyberstan/cve-2026-53360-poc

CVE-2026-53360-POC

PoC for CVE-2026-53360: guest-triggered heap out-of-bounds read/write in KVM SEV-SNP Page State Change (PSC) handling.

View Repository
132 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-53360: KVM SEV-SNP PSC heap out-of-bounds

Proof of concept for a heap out-of-bounds read and write in KVM's SEV-SNP Page State Change (PSC) handling. A malicious SEV-SNP guest makes the host kernel walk a PSC entry array off the end of its slab allocation. That leaks the layout of neighbouring kmalloc-cg-32 objects and writes a controlled small value into them, and the guest can repeat it as often as it likes.

Full writeup: https://cyberstan.co.uk/sev-snp-oob/

CVECVE-2026-53360
ComponentKVM SNP host support, arch/x86/kvm/svm/sev.c
Introduced9b54e248d264 (first KVM SNP PSC handling, May 2024, ~v6.10)
Fixeddb3f219 (mainline, May 2026, Cc: stable), tagged Fixes: 4af663c
Reported[email protected], 8 April 2026
AffectedSEV-SNP host path only. KVM does not enable PSC for plain SEV-ES guests.

Impact

Any SEV-SNP guest can corrupt the host kernel's heap and read back information about its layout by sending a malformed PSC request. This is the guest to host direction: SEV-SNP is built to protect the guest from an untrusted host, but the host still has to defend itself against a malicious guest, and this handler does not.

Requirements

This needs real SEV-SNP hardware. It cannot be reproduced on Intel, and nested virt will not give you an SNP guest.

Hardware:

  • An AMD EPYC server chip with SEV-SNP: Milan (7003) or newer, meaning Genoa (9004), Bergamo, Siena, or Turin. SEV-SNP is EPYC-only silicon. It is not on Ryzen or Threadripper, and there is no Intel equivalent here (Intel uses TDX).
  • Bare metal. A bare-metal cloud instance works too (Vultr, AWS *.metal, Hetzner AX, and similar).
  • In BIOS, enable SEV, SEV-ES, SEV-SNP, SME, IOMMU, and SVM. Managed bare-metal clouds usually ship these on already.

Host kernel:

  • Build it with KASAN so the out-of-bounds accesses get reported. Without KASAN the bug still corrupts host memory, it just is not printed. Tested on 6.11.11.
    root@kitploit:~
    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
    
  • Boot with SNP enabled and KASAN in multi-shot mode so every hit is logged:
    root@kitploit:~
    kvm_amd.sev=1 kvm_amd.sev_es=1 kvm_amd.sev_snp=1 kasan_multi_shot
    
  • Confirm the host is ready:
    root@kitploit:~
    cat /sys/module/kvm_amd/parameters/sev_snp     # Y
    ls /dev/sev                                     # /dev/sev
    

Host userspace:

  • An SNP-capable QEMU. Stock QEMU does not do SNP, so build the AMD fork:
    root@kitploit:~
    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)
    
  • SNP OVMF firmware from https://github.com/AMDESE/AMDSEV/releases.

Guest:

  • Any Linux guest that boots under SNP, with build-essential and linux-headers-$(uname -r) installed so you can build the module in it.

The bug

SEV-SNP guests talk to the host through the GHCB, a 4 KB shared page. A PSC request sets SW_EXITCODE to SVM_VMGEXIT_PSC (0x80000010), points SW_SCRATCH at a descriptor, and puts the descriptor length in SW_EXITINFO2.

The descriptor is a struct psc_buffer: an 8-byte header followed by an array of 8-byte entries. There is no explicit count field. The host processes entries from hdr->cur_entry to hdr->end_entry, both guest controlled.

root@kitploit:~
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 */

A GHCB v2+ guest is supposed to keep its scratch area inside the GHCB's 2032-byte Shared Buffer, so the host can reuse its existing mapping. (2032 - 8) / 8 = 253 entries fit there, which is where the protocol maximum VMGEXIT_PSC_MAX_COUNT (253) comes from. That number only makes sense when the buffer really is the Shared Buffer.

If the guest points the scratch area outside the GHCB, the host cannot use its mapping, so setup_vmgexit_scratch() allocates a separate buffer of the size the guest asked for. SNP should never take this path, but nothing stops it:

root@kitploit:~
scratch_va = kvzalloc(len, GFP_KERNEL_ACCOUNT);   /* len == exit_info_2, guest-controlled */

len comes straight from the guest, and GFP_KERNEL_ACCOUNT puts the allocation in the cgroup-accounted kmalloc-cg-N caches. Ask for exit_info_2 = 24 and you get a 24-byte allocation in the 32-byte kmalloc-cg-32 slot: room for the header plus two entries. Everything past entries[1] is another object's memory.

Then snp_begin_psc() checks the entry count against the protocol constant, not against the buffer it actually allocated:

root@kitploit:~
idx_end = hdr->end_entry;

if (idx_end >= VMGEXIT_PSC_MAX_COUNT) {   /* checks 253, NOT the buffer size */
        snp_complete_psc(svm, ...);
        return 1;
}

for (idx = idx_start; idx <= idx_end; idx++) {
        entry_start = entries[idx];       /* OOB once idx >= 2 */
        ...
}

With a 24-byte buffer only two entries exist, but the check allows end_entry up to 252. Set it to 252 and the loop walks about 2 KB past the allocation, across neighbouring slab objects.

Primitives

Each step past the end reinterprets the next 8 bytes of slab memory as a psc_entry and runs it through the PSC code. That gives three things:

  1. Read oracle. The host reads the adjacent qword just to decode it, pulling entry.gfn and entry.operation out of memory the buffer never owned. This is the slab-out-of-bounds read KASAN catches.
  2. Constrained write. If the decoded entry looks valid and gets dispatched as a KVM_HC_MAP_GPA_RANGE, the completion code writes back into the same OOB slot: entries[idx].cur_page = entry.pagesize ? 512 : 1. One of two small values into the low 12 bits of a word the guest picks, repeatable.
  3. Failure oracle. If the entry does not validate, the response in SW_EXITINFO2 reports the index it stopped at. Bumping end_entry one at a time leaks, slot by slot, whether adjacent memory decoded to a no-op or to something that failed, which is enough to find object boundaries and tell zero from non-zero.

Each VMGEXIT re-allocates the scratch buffer, so repeated requests land in different freelist slots and let the guest sweep across neighbours rather than being stuck with one. Put together this yields heap layout disclosure, the constrained write above, and use-after-free across requests.

What the PoC does

trigger.c is a guest kernel module. Load it inside an SEV-SNP guest and it drives four stages against the host from a single insmod:

  • Stage 1 probes 48 out-of-bounds entries one at a time and builds a map of the host heap (zero vs non-zero neighbouring memory).
  • Stage 2 proves the OOB write persists across VMGEXITs by writing cur_page into a zero neighbour and confirming a later request skips it.
  • Stage 3 fires one request with end_entry=200 and measures how far the OOB read reaches before hitting non-zero data.
  • Stage 4 fires 200 requests with entries[3..10] out of bounds, each of which trips a KASAN report on the host.

The module allocates a page, marks it decrypted with set_memory_decrypted(), uses it as the scratch area, and hand-builds the GHCB PSC request. It exits with -EAGAIN so it does not stay loaded.

Building and running

1. Launch an SNP guest

Adjust the OVMF and disk paths to your setup:

root@kitploit:~
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

2. Build and load in the guest

Copy trigger.c and Makefile into the guest, then:

root@kitploit:~
make
insmod trigger.ko

The module checks for SEV-SNP via CPUID first and refuses to run anywhere else. It runs its four stages and unloads itself (init returns -EAGAIN, so it never stays resident).

3. Watch the host

On the host:

root@kitploit:~
dmesg | grep -E "KASAN|BUG|snp_begin_psc"

Expected output:

root@kitploit:~
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

A single insmod produced 73 KASAN reports on the test host (62 slab-out-of-bounds, 7 slab-use-after-free, 4 use-after-free), all against kmalloc-cg-32. Test host: AMD EPYC 7443P, Ubuntu 24.04.4, kernel 6.11.11 with KASAN, guest under AMDESE QEMU (snp-latest).

The fix

The upstream fix rejects any out-of-GHCB scratch area for GHCB v2 and later in setup_vmgexit_scratch(), which pins the buffer to a fixed, known size so the loop can never run off the end:

root@kitploit:~
  } 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

Those four lines are db3f219. They landed as part of a larger series that also bounds the entry count against the real buffer size and rereads the descriptor once through READ_ONCE(), which closes an offset-into-the-buffer variant and a time-of-check/time-of-use race in the same handler.

Files

FileDescription
trigger.cGuest kernel module that drives the four PoC stages
MakefileBuilds trigger.ko against the running guest kernel

Troubleshooting

  • not an SEV-SNP guest: QEMU was not launched with sev-snp-guest, or host SNP is off.
  • QEMU SEV-SNP not supported: check /sys/module/kvm_amd/parameters/sev_snp, the BIOS settings, and the boot params.
  • QEMU LAUNCH_START failed: the PSP is not initialized. Check dmesg | grep psp and CONFIG_CRYPTO_DEV_SP_PSP=y.
  • No KASAN output: verify CONFIG_KASAN=y and kasan_multi_shot on the host cmdline.

Warning

This corrupts host kernel heap memory, trips KASAN, and can crash the host. Run it only against a disposable test host you control, inside a VM you own. Do not run it against shared or production infrastructure.

References

  • Writeup: https://cyberstan.co.uk/sev-snp-oob/
  • CVE-2026-53360 (track commit db3f219 on linux-cve-announce)
  • Fix: db3f219, author Mike Roth, reviewed by Tom Lendacky, committed by Paolo Bonzini
  • Introduced: 9b54e248d264; fix tagged Fixes: 4af663c
  • GHCB specification, section 2.1 (SW_SCRATCH must be within the GHCB shared buffer)

License

trigger.c is GPL-2.0, matching its MODULE_LICENSE. See LICENSE.

Download Tool
LICENSE
GPL-2.0, matching the module's MODULE_LICENSE