
PoC para CVE-2026-53360: leitura/escrita fora dos limites do heap acionada pelo convidado no tratamento de Mudança de Estado de Página (PSC) do KVM SEV-SNP.
Prova de conceito para uma leitura e escrita fora dos limites do heap no tratamento da alteração de estado de página (PSC) do SEV-SNP do KVM. Um convidado SEV-SNP malicioso faz com que o kernel anfitrião percorra um array de entradas PSC além do final da sua alocação slab. Isso vaza o layout dos objetos kmalloc-cg-32 vizinhos e escreve neles um valor pequeno controlado, e o convidado pode repetir o processo quantas vezes quiser.
Artigo completo: https://cyberstan.co.uk/sev-snp-oob/
| CVE | CVE-2026-53360 |
| Componente | Suporte a host SNP do KVM, arch/x86/kvm/svm/sev.c |
| Introduzido | 9b54e248d264 (primeiro tratamento PSC do KVM SNP, maio de 2024, ~v6.10) |
| Corrigido | db3f219 (mainline, maio de 2026, Cc: stable), marcado Fixes: 4af663c |
| Relatado | [email protected], 8 de abril de 2026 |
| Afetado | Apenas o caminho do host SEV-SNP. O KVM não habilita PSC para convidados SEV-ES comuns. |
Qualquer convidado SEV-SNP pode corromper o heap do kernel anfitrião e ler informações sobre seu layout enviando uma solicitação PSC malformada. Esta é a direção convidado para anfitrião: o SEV-SNP foi criado para proteger o convidado de um anfitrião não confiável, mas o anfitrião ainda precisa se defender contra um convidado malicioso, e este tratador não o faz.
Isso requer hardware SEV-SNP real. Não pode ser reproduzido em Intel, e virtualização aninhada não fornecerá um convidado SNP.
Hardware:
*.metal, Hetzner AX e similares).Kernel do host:
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
Userspace do host:
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)
Convidado:
build-essential e linux-headers-$(uname -r) instalados para que você possa construir o módulo dentro dele.Convidados SEV-SNP se comunicam com o host através do GHCB, uma página compartilhada de 4 KB. Uma solicitação PSC define SW_EXITCODE como SVM_VMGEXIT_PSC (0x80000010), aponta SW_SCRATCH para um descritor e coloca o comprimento do descritor em SW_EXITINFO2.
O descritor é um struct psc_buffer: um cabeçalho de 8 bytes seguido por um array de entradas de 8 bytes. Não há um campo de contagem explícito. O host processa as entradas de hdr->cur_entry até hdr->end_entry, ambos controlados pelo convidado.
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 */
Um convidado GHCB v2+ deve manter sua área de rascunho dentro do Buffer Compartilhado de 2032 bytes do GHCB, para que o host possa reutilizar seu mapeamento existente. (2032 - 8) / 8 = 253 entradas cabem ali, que é de onde vem o máximo do protocolo VMGEXIT_PSC_MAX_COUNT (253). Esse número só faz sentido quando o buffer é realmente o Buffer Compartilhado.
Se o convidado apontar a área de rascunho para fora do GHCB, o host não pode usar seu mapeamento, então setup_vmgexit_scratch() aloca um buffer separado do tamanho solicitado pelo convidado. SNP nunca deve seguir esse caminho, mas nada o impede:
scratch_va = kvzalloc(len, GFP_KERNEL_ACCOUNT); /* len == exit_info_2, controlado pelo convidado */
len vem diretamente do convidado, e GFP_KERNEL_ACCOUNT coloca a alocação nos caches kmalloc-cg-N contabilizados por cgroup. Solicite exit_info_2 = 24 e você obtém uma alocação de 24 bytes no slot de 32 bytes kmalloc-cg-32: espaço para o cabeçalho mais duas entradas. Tudo além de entries[1] é memória de outro objeto.
Então snp_begin_psc() verifica a contagem de entradas em relação à constante do protocolo, não em relação ao buffer que realmente alocou:
idx_end = hdr->end_entry;
if (idx_end >= VMGEXIT_PSC_MAX_COUNT) { /* verifica 253, NÃO o tamanho do buffer */
snp_complete_psc(svm, ...);
return 1;
}
for (idx = idx_start; idx <= idx_end; idx++) {
entry_start = entries[idx]; /* OOB quando idx >= 2 */
...
}
Com um buffer de 24 bytes existem apenas duas entradas, mas a verificação permite end_entry até 252. Defina-o como 252 e o loop percorre cerca de 2 KB após a alocação, atravessando objetos slab vizinhos.
Cada passo além do fim reinterpreta os próximos 8 bytes da memória slab como um psc_entry e o processa através do código PSC. Isso fornece três coisas:
entry.gfn e entry.operation da memória que o buffer nunca possuiu. Esta é a leitura slab-out-of-bounds que o KASAN captura.KVM_HC_MAP_GPA_RANGE, o código de conclusão escreve de volta no mesmo slot OOB: entries[idx].cur_page = entry.pagesize ? 512 : 1. Um de dois valores pequenos nos 12 bits baixos de uma palavra escolhida pelo convidado, repetível.SW_EXITINFO2 relata o índice em que parou. Incrementar end_entry um de cada vez vaza, slot por slot, se a memória adjacente decodificou como um no-op ou como algo que falhou, o que é suficiente para encontrar limites de objetos e distinguir zero de não zero.Cada VMGEXIT realoca o buffer de rascunho, então solicitações repetidas caem em diferentes slots da freelist e permitem que o convidado percorra vizinhos em vez de ficar preso em um. Juntos, isso produz divulgação do layout do heap, a escrita restrita acima e use-after-free entre solicitações.
trigger.c é um módulo de kernel do convidado. Carregue-o dentro de um convidado SEV-SNP e ele conduz quatro estágios contra o host a partir de um único insmod:
cur_page em um vizinho zero e confirmando que uma solicitação posterior o ignora.end_entry=200 e mede até onde a leitura OOB alcança antes de atingir dados não zero.entries[3..10] fora dos limites, cada uma das quais aciona um relatório KASAN no host.