// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2023 SUSE LLC * Author: Nicolai Stange * LTP port: Martin Doucha */ /*\ * CVE 2021-3656 * * Check that KVM correctly intercepts VMSAVE and VMLOAD instructions * in a nested virtual machine even when the parent guest disables * intercepting either instruction. If KVM does not override the disabled * intercepts, it'll give the nested VM read/write access to a few bytes * of an arbitrary physical memory page. Unauthorized memory access fixed in: * * commit c7dfa4009965a9b2d7b329ee970eb8da0d32f0bc * Author: Maxim Levitsky * Date: Mon Jul 19 16:05:00 2021 +0300 * * KVM: nSVM: always intercept VMLOAD/VMSAVE when nested (CVE-2021-3656) */ #include "kvm_test.h" #ifdef COMPILE_PAYLOAD #if defined(__i386__) || defined(__x86_64__) #include "kvm_x86_svm.h" static void *vmsave_buf; /* Load FS, GS, TR and LDTR state from vmsave_buf */ static int guest_vmload(void) { kvm_svm_vmload(vmsave_buf); return 0; } /* Save current FS, GS, TR and LDTR state to vmsave_buf */ static int guest_vmsave(void) { kvm_svm_vmsave(vmsave_buf); return 0; } static int cmp_descriptor(const struct kvm_vmcb_descriptor *a, const struct kvm_vmcb_descriptor *b) { int ret; ret = a->selector != b->selector; ret = ret || a->attrib != b->attrib; ret = ret || a->limit != b->limit; ret = ret || a->base != b->base; return ret; } /* Return non-zero if the VMCB fields touched by vmsave/vmload differ */ static int cmp_vmcb(const struct kvm_vmcb *a, const struct kvm_vmcb *b) { int ret; ret = cmp_descriptor(&a->fs, &b->fs); ret = ret || cmp_descriptor(&a->gs, &b->gs); ret = ret || cmp_descriptor(&a->tr, &b->tr); ret = ret || cmp_descriptor(&a->ldtr, &b->ldtr); ret = ret || a->kernel_gs_base != b->kernel_gs_base; ret = ret || a->star != b->star; ret = ret || a->lstar != b->lstar; ret = ret || a->cstar != b->cstar; ret = ret || a->sfmask != b->sfmask; ret = ret || a->sysenter_cs != b->sysenter_cs; ret = ret || a->sysenter_esp != b->sysenter_esp; ret = ret || a->sysenter_eip != b->sysenter_eip; return ret; } void main(void) { uint16_t ss; uint64_t rsp; struct kvm_svm_vcpu *vcpu; kvm_init_svm(); vcpu = kvm_create_svm_vcpu(guest_vmload, 1); kvm_vmcb_set_intercept(vcpu->vmcb, SVM_INTERCEPT_VMLOAD, 0); vmsave_buf = kvm_alloc_vmcb(); /* Save allocated stack for later VM reinit */ ss = vcpu->vmcb->ss.selector >> 3; rsp = vcpu->vmcb->rsp; /* Load partial state from vmsave_buf and save it to vcpu->vmcb */ kvm_svm_vmrun(vcpu); if (vcpu->vmcb->exitcode != SVM_EXIT_HLT) tst_brk(TBROK, "Nested VM exited unexpectedly"); if (cmp_vmcb(vcpu->vmcb, vmsave_buf)) { tst_res(TFAIL, "Nested VM can read host memory"); return; } /* Load state from vcpu->vmcb and save it to vmsave_buf */ memset(vmsave_buf, 0xaa, sizeof(struct kvm_vmcb)); kvm_init_guest_vmcb(vcpu->vmcb, 1, ss, (void *)rsp, guest_vmsave); kvm_vmcb_set_intercept(vcpu->vmcb, SVM_INTERCEPT_VMSAVE, 0); kvm_svm_vmrun(vcpu); if (vcpu->vmcb->exitcode != SVM_EXIT_HLT) tst_brk(TBROK, "Nested VM exited unexpectedly"); if (cmp_vmcb(vcpu->vmcb, vmsave_buf)) { tst_res(TFAIL, "Nested VM can overwrite host memory"); return; } tst_res(TPASS, "VMLOAD and VMSAVE were intercepted by kernel"); } #else /* defined(__i386__) || defined(__x86_64__) */ TST_TEST_TCONF("Test supported only on x86"); #endif /* defined(__i386__) || defined(__x86_64__) */ #else /* COMPILE_PAYLOAD */ static struct tst_test test = { .test_all = tst_kvm_run, .setup = tst_kvm_setup, .cleanup = tst_kvm_cleanup, .supported_archs = (const char *const []) { "x86_64", "x86", NULL }, .tags = (struct tst_tag[]){ {"linux-git", "c7dfa4009965"}, {"CVE", "2021-3656"}, {} } }; #endif /* COMPILE_PAYLOAD */