
PoC para CVE-2026-53360: lectura/escritura fuera de los límites del montón desencadenada por el invitado en el manejo de Page State Change (PSC) de KVM SEV-SNP.
Prueba de concepto de una lectura y escritura fuera de los límites del montón en el manejo del Cambio de Estado de Página (PSC) de SEV-SNP de KVM. Un invitado SEV-SNP malicioso hace que el kernel anfitrión recorra un array de entradas PSC más allá del final de su asignación de slab. Esto filtra el diseño de los objetos kmalloc-cg-32 vecinos y escribe un valor pequeño controlado en ellos, y el invitado puede repetirlo tantas veces como desee.
Artículo completo: https://cyberstan.co.uk/sev-snp-oob/
| CVE | CVE-2026-53360 |
| Component | Soporte anfitrión SNP de KVM, arch/x86/kvm/svm/sev.c |
| Introducido | 9b54e248d264 (primer manejo de PSC SNP de KVM, mayo 2024, ~v6.10) |
| Corregido | db3f219 (mainline, mayo 2026, Cc: stable), etiquetado Fixes: 4af663c |
| Reportado | [email protected], 8 de abril de 2026 |
| Afectado | Solo ruta anfitrión SEV-SNP. KVM no habilita PSC para invitados SEV-ES simples. |
Cualquier invitado SEV-SNP puede corromper el montón del kernel anfitrión y leer información sobre su diseño enviando una solicitud PSC malformada. Esta es la dirección invitado a anfitrión: SEV-SNP está diseñado para proteger al invitado de un anfitrión no confiable, pero el anfitrión aún tiene que defenderse contra un invitado malicioso, y este manejador no lo hace.
Esto requiere hardware SEV-SNP real. No se puede reproducir en Intel, y la virtualización anidada no le dará un invitado SNP.
Hardware:
*.metal, Hetzner AX y similares).Kernel anfitrión:
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
Espacio de usuario anfitrión:
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)
Invitado:
build-essential y linux-headers-$(uname -r) instalados para poder construir el módulo en él.Los invitados SEV-SNP se comunican con el anfitrión a través del GHCB, una página compartida de 4 KB. Una solicitud PSC establece SW_EXITCODE en SVM_VMGEXIT_PSC (0x80000010), apunta SW_SCRATCH a un descriptor y coloca la longitud del descriptor en SW_EXITINFO2.
El descriptor es un struct psc_buffer: un encabezado de 8 bytes seguido de un array de entradas de 8 bytes. No hay un campo de conteo explícito. El anfitrión procesa las entradas desde hdr->cur_entry hasta hdr->end_entry, ambos controlados por el invitado.
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 */
Se supone que un invitado GHCB v2+ mantiene su área de scratch dentro del Búfer Compartido de 2032 bytes del GHCB, para que el anfitrión pueda reutilizar su mapeo existente. Allí caben (2032 - 8) / 8 = 253 entradas, de donde proviene el máximo de protocolo VMGEXIT_PSC_MAX_COUNT (253). Ese número solo tiene sentido cuando el búfer es realmente el Búfer Compartido.
Si el invitado apunta el área de scratch fuera del GHCB, el anfitrión no puede usar su mapeo, por lo que setup_vmgexit_scratch() asigna un búfer separado del tamaño que el invitado solicitó. SNP nunca debería tomar este camino, pero nada lo impide:
scratch_va = kvzalloc(len, GFP_KERNEL_ACCOUNT); /* len == exit_info_2, controlado por el invitado */
len proviene directamente del invitado, y GFP_KERNEL_ACCOUNT coloca la asignación en los cachés kmalloc-cg-N contabilizados por cgroup. Solicite exit_info_2 = 24 y obtendrá una asignación de 24 bytes en el slot de 32 bytes de kmalloc-cg-32: espacio para el encabezado más dos entradas. Todo más allá de entries[1] es memoria de otro objeto.
Luego snp_begin_psc() verifica el conteo de entradas contra la constante del protocolo, no contra el búfer que realmente asignó:
idx_end = hdr->end_entry;
if (idx_end >= VMGEXIT_PSC_MAX_COUNT) { /* verifica 253, NO el tamaño del búfer */
snp_complete_psc(svm, ...);
return 1;
}
for (idx = idx_start; idx <= idx_end; idx++) {
entry_start = entries[idx]; /* OOB una vez idx >= 2 */
...
}
Con un búfer de 24 bytes solo existen dos entradas, pero la verificación permite end_entry hasta 252. Establézcalo en 252 y el bucle recorre unos 2 KB más allá de la asignación, a través de objetos slab vecinos.
Cada paso más allá del final reinterpreta los siguientes 8 bytes de memoria slab como un psc_entry y los procesa a través del código PSC. Esto proporciona tres cosas:
entry.gfn y entry.operation de memoria que el búfer nunca poseyó. Esta es la lectura fuera de los límites del slab que KASAN detecta.KVM_HC_MAP_GPA_RANGE, el código de finalización escribe de vuelta en el mismo slot OOB: entries[idx].cur_page = entry.pagesize ? 512 : 1. Uno de dos valores pequeños en los 12 bits bajos de una palabra que el invitado elige, repetible.SW_EXITINFO2 informa el índice en el que se detuvo. Incrementar end_entry de a uno filtra, slot por slot, si la memoria adyacente se decodificó como un no-op o como algo que falló, lo cual es suficiente para encontrar límites de objetos y distinguir cero de no cero.Cada VMGEXIT reasigna el búfer de scratch, por lo que las solicitudes repetidas caen en diferentes slots de la lista libre y permiten al invitado barrer los vecinos en lugar de quedarse atascado con uno. En conjunto, esto produce divulgación del diseño del montón, la escritura restringida anterior y use-after-free entre solicitudes.
trigger.c es un módulo del kernel del invitado. Cárguelo dentro de un invitado SEV-SNP y ejecuta cuatro etapas contra el anfitrión desde un solo insmod: