KVM: selftests: Trigger L2->L1 exits stress save+restore and #PF test

Extend the testing coverage in L2 by forcing a nested VM-Exit from L2 to
L1 right after restore on every other iteration. Forcing a nested
VM-Exit while L0 has control (e.g. without explicitly running L2 and
making a hypercall) is valuable, as it often happens during live
migration (e.g. L1 timer interrupt fires by the time the VM lands on the
destination).

To force the nested VM-Exit inject a #UD in to the saved vCPU state, and
intercept #UD from L1.

With this change, the test reliably reproduces the CR2 bug fixed by
commit 5c247d08bc ("KVM: nSVM: Use vcpu->arch.cr2 when updating vmcb12
on nested #VMEXIT") -- at least on Milan, Genoa, and Turin CPUs.

Assisted-by: Gemini:gemini-3.1-pro
Signed-off-by: Yosry Ahmed <yosry@kernel.org>
Link: https://patch.msgid.link/20260728174232.2423257-14-yosry@kernel.org
Signed-off-by: Sean Christopherson <seanjc@google.com>
This commit is contained in:
Yosry Ahmed 2026-07-28 17:42:32 +00:00 committed by Sean Christopherson
parent 1494b3d17c
commit 583ad2052d
2 changed files with 45 additions and 4 deletions

View File

@ -956,6 +956,11 @@ struct kvm_x86_state *vcpu_save_state(struct kvm_vcpu *vcpu);
void vcpu_load_state(struct kvm_vcpu *vcpu, struct kvm_x86_state *state);
void kvm_x86_state_cleanup(struct kvm_x86_state *state);
static inline bool kvm_x86_state_is_guest_mode(struct kvm_x86_state *state)
{
return state->nested.size && (state->nested.flags & KVM_STATE_NESTED_GUEST_MODE);
}
const struct kvm_msr_list *kvm_get_msr_index_list(void);
const struct kvm_msr_list *kvm_get_feature_msr_index_list(void);
bool kvm_msr_is_in_save_restore_list(u32 msr_index);

View File

@ -87,8 +87,13 @@ static void guest_access_memory(void *arg)
static void l1_svm_code(struct svm_test_data *svm)
{
generic_svm_setup(svm, guest_access_memory);
run_guest(svm->vmcb, svm->vmcb_gpa);
GUEST_ASSERT(false);
svm->vmcb->control.intercept_exceptions |= BIT(UD_VECTOR);
while (1) {
run_guest(svm->vmcb, svm->vmcb_gpa);
GUEST_ASSERT_EQ(svm->vmcb->control.exit_code,
(SVM_EXIT_EXCP_BASE + UD_VECTOR));
}
}
static void l1_vmx_code(struct vmx_pages *vmx)
@ -97,8 +102,14 @@ static void l1_vmx_code(struct vmx_pages *vmx)
GUEST_ASSERT(load_vmcs(vmx));
prepare_vmcs(vmx, guest_access_memory);
GUEST_ASSERT(!vmwrite(EXCEPTION_BITMAP, BIT(UD_VECTOR)));
GUEST_ASSERT(!vmlaunch());
GUEST_ASSERT(false);
while (1) {
GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_EXCEPTION_NMI);
GUEST_ASSERT_EQ(vmreadz(VM_EXIT_INTR_INFO) & 0xff, UD_VECTOR);
GUEST_ASSERT(!vmresume());
}
}
static void l1_guest_code(void *test_data)
@ -136,6 +147,19 @@ static void vcpu_sigusr_ignore(void)
sigaction(SIGUSR1, &sa, NULL);
}
static void kvm_x86_state_queue_ud(struct kvm_x86_state *state)
{
if (state->events.exception.pending || state->events.exception.injected)
return;
state->events.flags |= KVM_VCPUEVENT_VALID_PAYLOAD;
state->events.exception.pending = true;
state->events.exception.injected = false;
state->events.exception.nr = UD_VECTOR;
state->events.exception.has_error_code = false;
state->events.exception_has_payload = false;
}
static void run_test(bool nested)
{
struct kvm_x86_state *state;
@ -153,6 +177,7 @@ static void run_test(bool nested)
vm_install_exception_handler(vm, PF_VECTOR, guest_pf_handler);
if (nested) {
vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, -2ul);
if (kvm_cpu_has(X86_FEATURE_SVM))
vcpu_alloc_svm(vm, &gva);
else
@ -218,8 +243,17 @@ static void run_test(bool nested)
state = vcpu_save_state(vcpu);
/*
* If the vCPU is in guest mode, inject a #UD to trigger an
* L2->L1 VM-Exit every other iteration.
*/
if (kvm_x86_state_is_guest_mode(state) && i % 2 == 0)
kvm_x86_state_queue_ud(state);
kvm_vm_release(vm);
vcpu = vm_recreate_with_one_vcpu(vm);
if (nested)
vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, -2ul);
vcpu_load_state(vcpu, state);
kvm_x86_state_cleanup(state);
@ -241,7 +275,9 @@ int main(int argc, char *argv[])
pr_info("Running save+restore stress test...\n");
run_test(/*nested=*/false);
if (!kvm_cpu_has(X86_FEATURE_SVM) && !kvm_cpu_has(X86_FEATURE_VMX)) {
if (!kvm_has_cap(KVM_CAP_EXCEPTION_PAYLOAD) ||
!kvm_has_cap(KVM_CAP_NESTED_STATE) ||
(!kvm_cpu_has(X86_FEATURE_SVM) && !kvm_cpu_has(X86_FEATURE_VMX))) {
pr_info("Nested virtualization not supported, skipping nested test\n");
return 0;
}