[PATCH 2/2] KVM: selftests: Add regression test for KVM writing but not reporting vmcs12

From: Sean Christopherson

Date: Wed Oct 21 2026 - 22:18:41 EST


When saving nested vCPU state, verify that KVM didn't write any more bytes
than it reported, and add a sync+save point in the state test to trigger a
save when L1 is active with a valid shadow VMCS. Before commit <TBD>, KVM
would incorrectly save the shadow VMCS into the userspace buffer without
accounting for the size of the shadow VMCS.

Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx>
---
.../testing/selftests/kvm/lib/x86/processor.c | 6 +++++
tools/testing/selftests/kvm/x86/state_test.c | 24 ++++++++++---------
2 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c
index 8374afd08baf..976d53af6de7 100644
--- a/tools/testing/selftests/kvm/lib/x86/processor.c
+++ b/tools/testing/selftests/kvm/lib/x86/processor.c
@@ -1282,10 +1282,16 @@ struct kvm_x86_state *vcpu_save_state(struct kvm_vcpu *vcpu)
if (nested_size) {
state->nested.size = sizeof(state->nested_);

+ memset(state->nested_, 0xaa, sizeof(state->nested_));
+
vcpu_nested_state_get(vcpu, &state->nested);
TEST_ASSERT(state->nested.size <= nested_size,
"Nested state size too big, %i (KVM_CHECK_CAP gave %i)",
state->nested.size, nested_size);
+
+ for (i = state->nested.size; i < nested_size; i++)
+ TEST_ASSERT((unsigned char)state->nested_[i] == 0xaa,
+ "KVM killed the canary at offset %u", i);
} else {
state->nested.size = 0;
}
diff --git a/tools/testing/selftests/kvm/x86/state_test.c b/tools/testing/selftests/kvm/x86/state_test.c
index 500b09762555..5bd9a04a3911 100644
--- a/tools/testing/selftests/kvm/x86/state_test.c
+++ b/tools/testing/selftests/kvm/x86/state_test.c
@@ -60,13 +60,13 @@ void vmx_l2_guest_code(void)

/* L1 has now set up a shadow VMCS for us. */
GUEST_ASSERT(vmread(GUEST_RIP) == 0xc0ffee);
- GUEST_SYNC(10);
- GUEST_ASSERT(vmread(GUEST_RIP) == 0xc0ffee);
- vmwrite(GUEST_RIP, 0xc0fffee);
GUEST_SYNC(11);
+ GUEST_ASSERT(vmread(GUEST_RIP) == 0xc0ffee);
+ vmwrite(GUEST_RIP, 0xc0fffee);
+ GUEST_SYNC(12);
GUEST_ASSERT(vmread(GUEST_RIP) == 0xc0fffee);
vmwrite(GUEST_RIP, 0xc0ffffee);
- GUEST_SYNC(12);
+ GUEST_SYNC(13);

/* Done, exit to L1 and never come back. */
vmcall();
@@ -107,15 +107,17 @@ static void vmx_l1_guest_code(struct vmx_pages *vmx_pages)

vmwrite(SECONDARY_VM_EXEC_CONTROL, SECONDARY_EXEC_SHADOW_VMCS);
vmwrite(VMCS_LINK_POINTER, vmx_pages->shadow_vmcs_gpa);
-
- vmptrld(vmx_pages->shadow_vmcs_gpa);
- GUEST_ASSERT(__vmlaunch());
GUEST_SYNC(8);
- GUEST_ASSERT(__vmlaunch());
- GUEST_ASSERT(__vmresume());

- vmwrite(GUEST_RIP, 0xc0ffee);
+ vmptrld(vmx_pages->shadow_vmcs_gpa);
+ GUEST_ASSERT(__vmlaunch());
GUEST_SYNC(9);
+
+ GUEST_ASSERT(__vmlaunch());
+ GUEST_ASSERT(__vmresume());
+
+ vmwrite(GUEST_RIP, 0xc0ffee);
+ GUEST_SYNC(10);
GUEST_ASSERT(vmread(GUEST_RIP) == 0xc0ffee);

vmptrld(vmx_pages->vmcs_gpa);
@@ -126,7 +128,7 @@ static void vmx_l1_guest_code(struct vmx_pages *vmx_pages)
GUEST_ASSERT(vmread(GUEST_RIP) == 0xc0ffffee);
GUEST_ASSERT(__vmlaunch());
GUEST_ASSERT(__vmresume());
- GUEST_SYNC(13);
+ GUEST_SYNC(14);
GUEST_ASSERT(vmread(GUEST_RIP) == 0xc0ffffee);
GUEST_ASSERT(__vmlaunch());
GUEST_ASSERT(__vmresume());
--
2.56.0.218.g1ec904b173-goog