* [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes
@ 2026-01-10 0:48 Yosry Ahmed
2026-01-10 0:48 ` [PATCH 1/4] KVM: nSVM: Always use vmcb01 in VMLOAD/VMSAVE emulation Yosry Ahmed
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Yosry Ahmed @ 2026-01-10 0:48 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, Jim Mattson, Kevin Cheng, kvm, linux-kernel, Yosry Ahmed
A couple of fixes for nested VMLOAD/VMSAVE and a selftest that verifies
correct behavior. The test fails without patch 1.
Patch 4 is a proposed added WARNING, I am not sure if such warnings are
generally acceptable and if that's the correct place for it (hence RFC),
but I think it's useful to WARN if VMSAVE/VMLOAD are neither intercepted
nor virtualized by the CPU, because it means that the guest is directly
accessing host memory with them, a massive security hole.
The warning doesn't fire with or without the fixes, but at some point I
thought there might be such a security bug, and having a warning will
give me some peace of mind.
Yosry Ahmed (4):
KVM: nSVM: Always use vmcb01 in VMLOAD/VMSAVE emulation
KVM: SVM: Stop toggling virtual VMSAVE/VMLOAD on intercept recalc
KVM: selftests: Add a selftests for nested VMLOAD/VMSAVE
RFC: KVM: SVM: WARN if VMSAVE/VMLOAD are not intercepted or
virtualized
arch/x86/kvm/svm/svm.c | 23 +-
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/include/x86/processor.h | 1 +
.../kvm/x86/nested_vmsave_vmload_test.c | 197 ++++++++++++++++++
4 files changed, 218 insertions(+), 4 deletions(-)
create mode 100644 tools/testing/selftests/kvm/x86/nested_vmsave_vmload_test.c
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/4] KVM: nSVM: Always use vmcb01 in VMLOAD/VMSAVE emulation
2026-01-10 0:48 [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes Yosry Ahmed
@ 2026-01-10 0:48 ` Yosry Ahmed
2026-01-10 0:48 ` [PATCH 2/4] KVM: SVM: Stop toggling virtual VMSAVE/VMLOAD on intercept recalc Yosry Ahmed
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Yosry Ahmed @ 2026-01-10 0:48 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, Jim Mattson, Kevin Cheng, kvm, linux-kernel,
Yosry Ahmed, Maxim Levitsky, stable
Commit cc3ed80ae69f ("KVM: nSVM: always use vmcb01 to for vmsave/vmload
of guest state") made KVM always use vmcb01 for the fields controlled by
VMSAVE/VMLOAD, but it missed updating the VMLOAD/VMSAVE emulation code
to always use vmcb01.
As a result, if VMSAVE/VMLOAD is executed by an L2 guest and is not
intercepted by L1, KVM will mistakenly use vmcb02. Always use vmcb01
instead of the current VMCB.
Fixes: cc3ed80ae69f ("KVM: nSVM: always use vmcb01 to for vmsave/vmload of guest state")
Cc: Maxim Levitsky <mlevitsk@redhat.com>
Cc: stable@vger.kernel.org
Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
---
arch/x86/kvm/svm/svm.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7041498a8091..4e4439a01828 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -2165,12 +2165,13 @@ static int vmload_vmsave_interception(struct kvm_vcpu *vcpu, bool vmload)
ret = kvm_skip_emulated_instruction(vcpu);
+ /* KVM always performs VMLOAD/VMSAVE on VMCB01 (see __svm_vcpu_run()) */
if (vmload) {
- svm_copy_vmloadsave_state(svm->vmcb, vmcb12);
+ svm_copy_vmloadsave_state(svm->vmcb01.ptr, vmcb12);
svm->sysenter_eip_hi = 0;
svm->sysenter_esp_hi = 0;
} else {
- svm_copy_vmloadsave_state(vmcb12, svm->vmcb);
+ svm_copy_vmloadsave_state(vmcb12, svm->vmcb01.ptr);
}
kvm_vcpu_unmap(vcpu, &map);
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/4] KVM: SVM: Stop toggling virtual VMSAVE/VMLOAD on intercept recalc
2026-01-10 0:48 [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes Yosry Ahmed
2026-01-10 0:48 ` [PATCH 1/4] KVM: nSVM: Always use vmcb01 in VMLOAD/VMSAVE emulation Yosry Ahmed
@ 2026-01-10 0:48 ` Yosry Ahmed
2026-01-10 0:48 ` [PATCH 3/4] KVM: selftests: Add a selftests for nested VMLOAD/VMSAVE Yosry Ahmed
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Yosry Ahmed @ 2026-01-10 0:48 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, Jim Mattson, Kevin Cheng, kvm, linux-kernel, Yosry Ahmed
Virtual VMSAVE/VMLOAD enablement (i.e.
VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK) is set/cleared by
svm_recalc_instruction_intercepts() when the intercepts are cleared/set.
This is unnecessary because the bit is meaningless when intercepts are
set and KVM emulates the instructions. Initialize the bit in vmcb01 base
on vls, and keep it unchanged.
This is similar-ish to how vGIF is handled. It is enabled in init_vmcb()
if vgif=1 and remains unchanged when the STGI intercept is enabled (e.g.
for NMI windows).
This fixes a bug in svm_recalc_instruction_intercepts(). The intercepts
for VMSAVE/VMLOAD are always toggled in vmcb01, but
VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK is toggled in the current VMCB, which
could be vmcb02 instead of vmcb01 if L2 is active.
Virtual VMSAVE/VMLOAD enablement in vmcb02 is separately controlled by
nested_vmcb02_prepare_control() based on the vCPU features and VMCB12,
and if intercepts are needed they are set by recalc_intercepts().
The bug is benign though. Not toggling the bit for vmcb01 is harmless
because it's useless anyway. For vmcb02:
- The bit could be incorrectly cleared when intercepts are set in
vmcb01. This is harmless because VMSAVE/VMLOAD will be emulated by KVM
anyway.
- The bit could be incorrectly set when the intercepts are cleared in
vmcb01. However, if the bit was originally clear in vmcb02, then
recalc_intercepts() will enable in the intercepts in vmcb02 anyway and
VMSAVE/VMLOAD will be emulated by KVM.
Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
---
arch/x86/kvm/svm/svm.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 4e4439a01828..f1b032114406 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -1033,10 +1033,14 @@ static void svm_recalc_instruction_intercepts(struct kvm_vcpu *vcpu)
svm_set_intercept(svm, INTERCEPT_RDTSCP);
}
+ /*
+ * No need to toggle VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK here, it is
+ * always set if vls is enabled. If the intercepts are set, the bit is
+ * meaningless anyway.
+ */
if (guest_cpuid_is_intel_compatible(vcpu)) {
svm_set_intercept(svm, INTERCEPT_VMLOAD);
svm_set_intercept(svm, INTERCEPT_VMSAVE);
- svm->vmcb->control.virt_ext &= ~VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
} else {
/*
* If hardware supports Virtual VMLOAD VMSAVE then enable it
@@ -1045,7 +1049,6 @@ static void svm_recalc_instruction_intercepts(struct kvm_vcpu *vcpu)
if (vls) {
svm_clr_intercept(svm, INTERCEPT_VMLOAD);
svm_clr_intercept(svm, INTERCEPT_VMSAVE);
- svm->vmcb->control.virt_ext |= VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
}
}
@@ -1198,6 +1201,9 @@ static void init_vmcb(struct kvm_vcpu *vcpu, bool init_event)
svm->vmcb->control.int_ctl |= V_GIF_ENABLE_MASK;
}
+ if (vls)
+ svm->vmcb->control.virt_ext |= VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
+
if (vcpu->kvm->arch.bus_lock_detection_enabled)
svm_set_intercept(svm, INTERCEPT_BUSLOCK);
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 3/4] KVM: selftests: Add a selftests for nested VMLOAD/VMSAVE
2026-01-10 0:48 [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes Yosry Ahmed
2026-01-10 0:48 ` [PATCH 1/4] KVM: nSVM: Always use vmcb01 in VMLOAD/VMSAVE emulation Yosry Ahmed
2026-01-10 0:48 ` [PATCH 2/4] KVM: SVM: Stop toggling virtual VMSAVE/VMLOAD on intercept recalc Yosry Ahmed
@ 2026-01-10 0:48 ` Yosry Ahmed
2026-01-10 0:48 ` [PATCH 4/4] RFC: KVM: SVM: WARN if VMSAVE/VMLOAD are not intercepted or virtualized Yosry Ahmed
2026-01-15 18:03 ` [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes Sean Christopherson
4 siblings, 0 replies; 6+ messages in thread
From: Yosry Ahmed @ 2026-01-10 0:48 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, Jim Mattson, Kevin Cheng, kvm, linux-kernel, Yosry Ahmed
Add a test for VMLOAD/VMSAVE in an L2 guest. The test verifies that L1
intercepts for VMSAVE/VMLOAD always work regardless of
VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK.
Then, more interestingly, it makes sure that when L1 does not intercept
VMLOAD/VMSAVE, they work as intended in L2. When
VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK is enabled by L1, VMSAVE/VMLOAD from
L2 should interpret the GPA as an L2 GPA and translate it through the
NPT. When VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK is disabled by L1,
VMSAVE/VMLOAD from L2 should interpret the GPA as an L1 GPA.
To test this, put two VMCBs (0 and 1) in L1's physical address space,
and have a single L2 GPA where:
- L2 VMCB GPA == L1 VMCB(0) GPA
- L2 VMCB GPA maps to L1 VMCB(1) via the NPT in L1.
This setup allows detecting how the GPA is interpreted based on which L1
VMCB is actually accessed.
In both cases, L2 sets KERNEL_GS_BASE (one of the fields handled by
VMSAVE/VMLOAD), and executes VMSAVE to write its value to the VMCB. The
test userspace code then checks that the write was made to the correct
VMCB (based on whether VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK is set by L1),
and writes a new value to that VMCB. L2 then executes VMLOAD to load the
new value and makes sure it's reflected correctly in KERNERL_GS_BASE.
Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../selftests/kvm/include/x86/processor.h | 1 +
.../kvm/x86/nested_vmsave_vmload_test.c | 197 ++++++++++++++++++
3 files changed, 199 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86/nested_vmsave_vmload_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 33ff81606638..482d237b83f8 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -96,6 +96,7 @@ TEST_GEN_PROGS_x86 += x86/nested_invalid_cr3_test
TEST_GEN_PROGS_x86 += x86/nested_set_state_test
TEST_GEN_PROGS_x86 += x86/nested_tsc_adjust_test
TEST_GEN_PROGS_x86 += x86/nested_tsc_scaling_test
+TEST_GEN_PROGS_x86 += x86/nested_vmsave_vmload_test
TEST_GEN_PROGS_x86 += x86/platform_info_test
TEST_GEN_PROGS_x86 += x86/pmu_counters_test
TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
index 8f130e7d7048..6bfffc3b0a33 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -201,6 +201,7 @@ struct kvm_x86_cpu_feature {
#define X86_FEATURE_TSCRATEMSR KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 4)
#define X86_FEATURE_PAUSEFILTER KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 10)
#define X86_FEATURE_PFTHRESHOLD KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 12)
+#define X86_FEATURE_V_VMSAVE_VMLOAD KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 15)
#define X86_FEATURE_VGIF KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 16)
#define X86_FEATURE_IDLE_HLT KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 30)
#define X86_FEATURE_SEV KVM_X86_CPU_FEATURE(0x8000001F, 0, EAX, 1)
diff --git a/tools/testing/selftests/kvm/x86/nested_vmsave_vmload_test.c b/tools/testing/selftests/kvm/x86/nested_vmsave_vmload_test.c
new file mode 100644
index 000000000000..6764a48f9d4d
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/nested_vmsave_vmload_test.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026, Google LLC.
+ */
+#include "kvm_util.h"
+#include "vmx.h"
+#include "svm_util.h"
+#include "kselftest.h"
+
+/*
+ * Allocate two VMCB pages for testing. Both pages have different GVAs (shared
+ * by both L1 and L2) and L1 GPAs. A single L2 GPA is used such that:
+ * - L2 GPA == L1 GPA for VMCB0.
+ * - L2 GPA is mapped to L1 GPA for VMCB1 using NPT in L1.
+ *
+ * This allows testing whether the GPA used by VMSAVE/VMLOAD in L2 is
+ * interpreted as a direct L1 GPA or translated using NPT as an L2 GPA, depends
+ * on which VMCB is accessed.
+ */
+#define TEST_MEM_SLOT_INDEX 1
+#define TEST_MEM_PAGES 2
+#define TEST_MEM_BASE 0xc0000000
+
+#define TEST_GUEST_ADDR(idx) (TEST_MEM_BASE + (idx) * PAGE_SIZE)
+
+#define TEST_VMCB_L1_GPA(idx) TEST_GUEST_ADDR(idx)
+#define TEST_VMCB_GVA(idx) TEST_GUEST_ADDR(idx)
+
+#define TEST_VMCB_L2_GPA TEST_VMCB_L1_GPA(0)
+
+#define L2_GUEST_STACK_SIZE 64
+
+static void l2_guest_code_vmsave(void)
+{
+ asm volatile("vmsave %0" : : "a"(TEST_VMCB_L2_GPA) : "memory");
+}
+
+static void l2_guest_code_vmload(void)
+{
+ asm volatile("vmload %0" : : "a"(TEST_VMCB_L2_GPA) : "memory");
+}
+
+static void l2_guest_code_vmcb(int vmcb_idx)
+{
+ wrmsr(MSR_KERNEL_GS_BASE, 0xaaaa);
+ l2_guest_code_vmsave();
+
+ /* Verify the VMCB used by VMSAVE and update KERNEL_GS_BASE to 0xbbbb */
+ GUEST_SYNC(vmcb_idx);
+
+ l2_guest_code_vmload();
+ GUEST_ASSERT_EQ(rdmsr(MSR_KERNEL_GS_BASE), 0xbbbb);
+
+ /* Reset MSR_KERNEL_GS_BASE */
+ wrmsr(MSR_KERNEL_GS_BASE, 0);
+ l2_guest_code_vmsave();
+
+ vmmcall();
+}
+
+static void l2_guest_code_vmcb0(void)
+{
+ l2_guest_code_vmcb(0);
+}
+
+static void l2_guest_code_vmcb1(void)
+{
+ l2_guest_code_vmcb(1);
+}
+
+static void l1_guest_code(struct svm_test_data *svm)
+{
+ unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
+
+ /* Each test case initializes the guest RIP below */
+ generic_svm_setup(svm, NULL, &l2_guest_stack[L2_GUEST_STACK_SIZE]);
+
+ /* Set VMSAVE/VMLOAD intercepts and make sure they work with.. */
+ svm->vmcb->control.intercept |= (BIT_ULL(INTERCEPT_VMSAVE) |
+ BIT_ULL(INTERCEPT_VMLOAD));
+
+ /* ..VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK cleared.. */
+ svm->vmcb->control.virt_ext &= ~VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
+
+ svm->vmcb->save.rip = (u64)l2_guest_code_vmsave;
+ run_guest(svm->vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMSAVE);
+
+ svm->vmcb->save.rip = (u64)l2_guest_code_vmload;
+ run_guest(svm->vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMLOAD);
+
+ /* ..and VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK set */
+ svm->vmcb->control.virt_ext |= VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
+
+ svm->vmcb->save.rip = (u64)l2_guest_code_vmsave;
+ run_guest(svm->vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMSAVE);
+
+ svm->vmcb->save.rip = (u64)l2_guest_code_vmload;
+ run_guest(svm->vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMLOAD);
+
+ /* Now clear the intercepts to test VMSAVE/VMLOAD behavior */
+ svm->vmcb->control.intercept &= ~(BIT_ULL(INTERCEPT_VMSAVE) |
+ BIT_ULL(INTERCEPT_VMLOAD));
+
+ /*
+ * Without VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK, the GPA will be
+ * interpreted as an L1 GPA, so VMCB0 should be used.
+ */
+ svm->vmcb->save.rip = (u64)l2_guest_code_vmcb0;
+ svm->vmcb->control.virt_ext &= ~VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
+ run_guest(svm->vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMMCALL);
+
+ /*
+ * With VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK, the GPA will be interpeted as
+ * an L2 GPA, and translated through the NPT to VMCB1.
+ */
+ svm->vmcb->save.rip = (u64)l2_guest_code_vmcb1;
+ svm->vmcb->control.virt_ext |= VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
+ run_guest(svm->vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMMCALL);
+
+ GUEST_DONE();
+}
+
+int main(int argc, char *argv[])
+{
+ vm_vaddr_t nested_gva = 0;
+ struct vmcb *test_vmcb[2];
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ int i;
+
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_NPT));
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_V_VMSAVE_VMLOAD));
+
+ vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+ vm_enable_tdp(vm);
+
+ vcpu_alloc_svm(vm, &nested_gva);
+ vcpu_args_set(vcpu, 1, nested_gva);
+
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+ TEST_MEM_BASE, TEST_MEM_SLOT_INDEX,
+ TEST_MEM_PAGES, 0);
+
+ for (i = 0; i <= 1; i++) {
+ virt_map(vm, TEST_VMCB_GVA(i), TEST_VMCB_L1_GPA(i), 1);
+ test_vmcb[i] = (struct vmcb *)addr_gva2hva(vm, TEST_VMCB_GVA(i));
+ }
+
+ tdp_identity_map_default_memslots(vm);
+
+ /*
+ * L2 GPA == L1_GPA(0), but map it to L1_GPA(1), to allow testing
+ * whether the L2 GPA is interpreted as an L1 GPA or translated through
+ * the NPT.
+ */
+ TEST_ASSERT_EQ(TEST_VMCB_L2_GPA, TEST_VMCB_L1_GPA(0));
+ tdp_map(vm, TEST_VMCB_L2_GPA, TEST_VMCB_L1_GPA(1), PAGE_SIZE);
+
+ for (;;) {
+ struct ucall uc;
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ case UCALL_SYNC:
+ i = uc.args[1];
+ TEST_ASSERT(i == 0 || i == 1, "Unexpected VMCB idx: %d", i);
+
+ /*
+ * Check that only the expected VMCB has KERNEL_GS_BASE
+ * set to 0xaaaa, and update it to 0xbbbb.
+ */
+ TEST_ASSERT_EQ(test_vmcb[i]->save.kernel_gs_base, 0xaaaa);
+ TEST_ASSERT_EQ(test_vmcb[1-i]->save.kernel_gs_base, 0);
+ test_vmcb[i]->save.kernel_gs_base = 0xbbbb;
+ break;
+ case UCALL_DONE:
+ goto done;
+ default:
+ TEST_FAIL("Unknown ucall %lu", uc.cmd);
+ }
+ }
+
+done:
+ kvm_vm_free(vm);
+ return 0;
+}
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 4/4] RFC: KVM: SVM: WARN if VMSAVE/VMLOAD are not intercepted or virtualized
2026-01-10 0:48 [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes Yosry Ahmed
` (2 preceding siblings ...)
2026-01-10 0:48 ` [PATCH 3/4] KVM: selftests: Add a selftests for nested VMLOAD/VMSAVE Yosry Ahmed
@ 2026-01-10 0:48 ` Yosry Ahmed
2026-01-15 18:03 ` [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes Sean Christopherson
4 siblings, 0 replies; 6+ messages in thread
From: Yosry Ahmed @ 2026-01-10 0:48 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, Jim Mattson, Kevin Cheng, kvm, linux-kernel, Yosry Ahmed
Signed-off-by: Yosry Ahmed <yosry.ahmed@linux.dev>
---
arch/x86/kvm/svm/svm.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index f1b032114406..983ae98133e9 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -1387,6 +1387,14 @@ static void svm_prepare_switch_to_guest(struct kvm_vcpu *vcpu)
if (svm->guest_state_loaded)
return;
+ /*
+ * Without virtual VMSAVE/VMLOAD, the instruction would directly access
+ * host physical addresses, so make sure they are intercepted.
+ */
+ if (!(svm->vmcb->control.virt_ext & VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK))
+ WARN_ON_ONCE(!svm_is_intercept(svm, INTERCEPT_VMSAVE) ||
+ !svm_is_intercept(svm, INTERCEPT_VMLOAD));
+
/*
* Save additional host state that will be restored on VMEXIT (sev-es)
* or subsequent vmload of host save area.
--
2.52.0.457.g6b5491de43-goog
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes
2026-01-10 0:48 [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes Yosry Ahmed
` (3 preceding siblings ...)
2026-01-10 0:48 ` [PATCH 4/4] RFC: KVM: SVM: WARN if VMSAVE/VMLOAD are not intercepted or virtualized Yosry Ahmed
@ 2026-01-15 18:03 ` Sean Christopherson
4 siblings, 0 replies; 6+ messages in thread
From: Sean Christopherson @ 2026-01-15 18:03 UTC (permalink / raw)
To: Sean Christopherson, Yosry Ahmed
Cc: Paolo Bonzini, Jim Mattson, Kevin Cheng, kvm, linux-kernel
On Sat, 10 Jan 2026 00:48:17 +0000, Yosry Ahmed wrote:
> A couple of fixes for nested VMLOAD/VMSAVE and a selftest that verifies
> correct behavior. The test fails without patch 1.
>
> Patch 4 is a proposed added WARNING, I am not sure if such warnings are
> generally acceptable and if that's the correct place for it (hence RFC),
> but I think it's useful to WARN if VMSAVE/VMLOAD are neither intercepted
> nor virtualized by the CPU, because it means that the guest is directly
> accessing host memory with them, a massive security hole.
Sanity checks in KVM are definitely acceptable/encourage, but this particular
check goes too far. There are sooooo many things that KVM _must_ get right;
asserting on every single one without strong evidence that we're likely to
screw up without noticing isn't something I want to encourage.
In other words, for me, "the consequences are really bad" isn't sufficient
justification on its own, there also needs to be some amount of "we've botched
this multiple times in the past", "it's tricky to get right", and/or "this is a
low level API that makes assumptions about how the rest of KVM works".
> [...]
Applied 1-2 to kvm-x86 svm, and 3 to kvm-x86 selftests due to its dependency
on the selftests NPT support. Note, this means running the selftests branch
without the fixes from the svm branch will fail. Far from ideal, but IMO it's
not worth doing a merge for something that is unlikely to ever be a problem in
practice (and now I've jinxed myself).
[1/4] KVM: nSVM: Always use vmcb01 in VMLOAD/VMSAVE emulation
https://github.com/kvm-x86/linux/commit/127ccae2c185
[2/4] KVM: SVM: Stop toggling virtual VMSAVE/VMLOAD on intercept recalc
https://github.com/kvm-x86/linux/commit/55780d8a1dcc
[3/4] KVM: selftests: Add a selftests for nested VMLOAD/VMSAVE
https://github.com/kvm-x86/linux/commit/55058e32151f
[4/4] RFC: KVM: SVM: WARN if VMSAVE/VMLOAD are not intercepted or virtualized
[DROPPED]
--
https://github.com/kvm-x86/linux/tree/next
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-01-15 18:07 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-10 0:48 [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes Yosry Ahmed
2026-01-10 0:48 ` [PATCH 1/4] KVM: nSVM: Always use vmcb01 in VMLOAD/VMSAVE emulation Yosry Ahmed
2026-01-10 0:48 ` [PATCH 2/4] KVM: SVM: Stop toggling virtual VMSAVE/VMLOAD on intercept recalc Yosry Ahmed
2026-01-10 0:48 ` [PATCH 3/4] KVM: selftests: Add a selftests for nested VMLOAD/VMSAVE Yosry Ahmed
2026-01-10 0:48 ` [PATCH 4/4] RFC: KVM: SVM: WARN if VMSAVE/VMLOAD are not intercepted or virtualized Yosry Ahmed
2026-01-15 18:03 ` [PATCH 0/4] KVM: nSVM: nested VMSAVE/VMLOAD fixes Sean Christopherson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®