From 3f339b70bb59d92d81d2853f905e1124276a9802 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Tue, 7 Jul 2026 08:07:05 -0700 Subject: [PATCH 01/18] KVM: selftests: Drop superfluous use of pthread_attr_setaffinity_np() In the steal time test, don't explicitly set the CPU affinity mask of the worker child and instead rely on the child inheriting the affinity of the main thread. Per the pthread_create()[1] and pthread_setaffinity_np()[2] documentation, new threads inherit the parent's affinity mask. Linux-specific details The new thread inherits copies of the calling thread's capability sets (see capabilities(7)) and CPU affinity mask (see sched_setaffinity(2)). Out of an abundance of caution, assert that the child did indeed inherit the CPU affinity mask, as the test will hang indefinitely if the system is under light load. Dropping use of pthread_attr_setaffinity_np() allows building the steal time test against non-glibc C libraries that don't implement that GNU extension. Link: https://man7.org/linux/man-pages/man3/pthread_setaffinity_np.3.html [1] Link: https://man7.org/linux/man-pages/man3/pthread_create.3.html [1] Cc: Hisam Mehboob Reported-by: Aqib Faruqui Closes: https://lore.kernel.org/all/20250829142556.72577-4-aqibaf@amazon.com Link: https://patch.msgid.link/20260707150706.1198541-2-seanjc@google.com Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/steal_time.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/kvm/steal_time.c b/tools/testing/selftests/kvm/steal_time.c index 76fcdd1fd3cb..a244bf9f701f 100644 --- a/tools/testing/selftests/kvm/steal_time.c +++ b/tools/testing/selftests/kvm/steal_time.c @@ -508,7 +508,6 @@ int main(int ac, char **av) { struct kvm_vcpu *vcpus[NR_VCPUS]; struct kvm_vm *vm; - pthread_attr_t attr; pthread_t thread; cpu_set_t cpuset; unsigned int gpages; @@ -522,8 +521,6 @@ int main(int ac, char **av) /* Set CPU affinity so we can force preemption of the VCPU */ CPU_ZERO(&cpuset); CPU_SET(0, &cpuset); - pthread_attr_init(&attr); - pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset); pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); /* Create a VM and an identity mapped memslot for the steal time structure */ @@ -558,7 +555,11 @@ int main(int ac, char **av) /* Steal time from the VCPU. The steal time thread has the same CPU affinity as the VCPUs. */ run_delay = get_run_delay(); - pthread_create(&thread, &attr, do_steal_time, NULL); + pthread_create(&thread, NULL, do_steal_time, NULL); + pthread_getaffinity_np(thread, sizeof(cpuset), &cpuset); + TEST_ASSERT(CPU_COUNT(&cpuset) == 1 && CPU_ISSET(0, &cpuset), + "Worker failed to inherit parent's CPU affinity"); + do sched_yield(); while (get_run_delay() - run_delay < MIN_RUN_DELAY_NS); From 2351e814e1e1ef8e10698c6b51c86eceedc0ce86 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Tue, 7 Jul 2026 08:07:06 -0700 Subject: [PATCH 02/18] KVM: selftests: Randomize pCPU in steal time test Pin the steal time test's tasks to a random pCPU in the system instead of hardcoding the test to always run on pCPU0 as a cheap way of increasing test coverage, and to do the "right thing" if the parent task of the test doesn't have pCPU0 in its CPU affinity mask. Link: https://patch.msgid.link/20260707150706.1198541-3-seanjc@google.com Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/steal_time.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/kvm/steal_time.c b/tools/testing/selftests/kvm/steal_time.c index a244bf9f701f..92e7ffcd68b7 100644 --- a/tools/testing/selftests/kvm/steal_time.c +++ b/tools/testing/selftests/kvm/steal_time.c @@ -514,14 +514,12 @@ int main(int ac, char **av) long stolen_time; long run_delay; bool verbose; - int i; + int i, cpu; verbose = ac > 1 && (!strncmp(av[1], "-v", 3) || !strncmp(av[1], "--verbose", 10)); /* Set CPU affinity so we can force preemption of the VCPU */ - CPU_ZERO(&cpuset); - CPU_SET(0, &cpuset); - pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); + cpu = pin_self_to_any_cpu(); /* Create a VM and an identity mapped memslot for the steal time structure */ vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus); @@ -557,7 +555,7 @@ int main(int ac, char **av) run_delay = get_run_delay(); pthread_create(&thread, NULL, do_steal_time, NULL); pthread_getaffinity_np(thread, sizeof(cpuset), &cpuset); - TEST_ASSERT(CPU_COUNT(&cpuset) == 1 && CPU_ISSET(0, &cpuset), + TEST_ASSERT(CPU_COUNT(&cpuset) == 1 && CPU_ISSET(cpu, &cpuset), "Worker failed to inherit parent's CPU affinity"); do From 5ef3668bcea2eacc4a06204a46602af34b22a7f3 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Thu, 2 Jul 2026 09:57:39 +0800 Subject: [PATCH 03/18] KVM: selftests: Fix a spelling error in an xapic_ipi_test comment Fix typo "usefull" -> "useful" in xAPIC IPI test comment. Signed-off-by: Wang Yan Link: https://patch.msgid.link/20260702015739.367597-1-wangyan01@kylinos.cn Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/x86/xapic_ipi_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c index 39ce9a9369f5..3a326c5e74ca 100644 --- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c +++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c @@ -17,7 +17,7 @@ * amongst the available numa nodes on the machine. * * Migration is a command line option. When used on non-numa machines will - * exit with error. Test is still usefull on non-numa for testing IPIs. + * exit with error. Test is still useful on non-numa for testing IPIs. */ #include #include From 653857a5af46237eacbf886338af9972574cd781 Mon Sep 17 00:00:00 2001 From: Shivank Sharma Date: Fri, 17 Jul 2026 21:58:38 +0530 Subject: [PATCH 04/18] KVM: selftests: Fix typos in x86 and riscv tests Fix spelling typos found by an automated checker in the KVM selftests for x86 and RISC-V. Signed-off-by: Shivank Sharma Link: https://patch.msgid.link/20260717162838.1562808-1-shivanksharma2376543@gmail.com Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/riscv/sbi_pmu_test.c | 4 ++-- tools/testing/selftests/kvm/x86/hyperv_clock.c | 4 ++-- tools/testing/selftests/kvm/x86/hyperv_evmcs.c | 2 +- .../selftests/kvm/x86/vmx_invalid_nested_guest_state.c | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/kvm/riscv/sbi_pmu_test.c b/tools/testing/selftests/kvm/riscv/sbi_pmu_test.c index e56a3dd6a51e..20388f0b959d 100644 --- a/tools/testing/selftests/kvm/riscv/sbi_pmu_test.c +++ b/tools/testing/selftests/kvm/riscv/sbi_pmu_test.c @@ -492,7 +492,7 @@ static void test_pmu_events_snaphost(void) struct riscv_pmu_snapshot_data *snapshot_data = snapshot_gva; int i; - /* Verify presence of SBI PMU and minimum requrired SBI version */ + /* Verify presence of SBI PMU and minimum required SBI version */ verify_sbi_requirement_assert(); snapshot_set_shmem(snapshot_gpa, 0); @@ -518,7 +518,7 @@ static void test_pmu_events_overflow(void) { int num_counters = 0, i = 0; - /* Verify presence of SBI PMU and minimum requrired SBI version */ + /* Verify presence of SBI PMU and minimum required SBI version */ verify_sbi_requirement_assert(); snapshot_set_shmem(snapshot_gpa, 0); diff --git a/tools/testing/selftests/kvm/x86/hyperv_clock.c b/tools/testing/selftests/kvm/x86/hyperv_clock.c index c083cea546dc..d5d779623cc6 100644 --- a/tools/testing/selftests/kvm/x86/hyperv_clock.c +++ b/tools/testing/selftests/kvm/x86/hyperv_clock.c @@ -56,7 +56,7 @@ static inline void check_tsc_msr_rdtsc(void) tsc_freq = rdmsr(HV_X64_MSR_TSC_FREQUENCY); GUEST_ASSERT(tsc_freq > 0); - /* For increased accuracy, take mean rdtsc() before and afrer rdmsr() */ + /* For increased accuracy, take mean rdtsc() before and after rdmsr() */ r1 = rdtsc(); t1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT); r1 = (r1 + rdtsc()) / 2; @@ -181,7 +181,7 @@ static void host_check_tsc_msr_rdtsc(struct kvm_vcpu *vcpu) tsc_freq = vcpu_get_msr(vcpu, HV_X64_MSR_TSC_FREQUENCY); TEST_ASSERT(tsc_freq > 0, "TSC frequency must be nonzero"); - /* For increased accuracy, take mean rdtsc() before and afrer ioctl */ + /* For increased accuracy, take mean rdtsc() before and after ioctl */ r1 = rdtsc(); t1 = vcpu_get_msr(vcpu, HV_X64_MSR_TIME_REF_COUNT); r1 = (r1 + rdtsc()) / 2; diff --git a/tools/testing/selftests/kvm/x86/hyperv_evmcs.c b/tools/testing/selftests/kvm/x86/hyperv_evmcs.c index 1bda2cd3f739..63ea1533e4ea 100644 --- a/tools/testing/selftests/kvm/x86/hyperv_evmcs.c +++ b/tools/testing/selftests/kvm/x86/hyperv_evmcs.c @@ -125,7 +125,7 @@ void guest_code(struct vmx_pages *vmx_pages, struct hyperv_test_pages *hv_pages, /* * NMI forces L2->L1 exit, resuming L2 and hope that EVMCS is * up-to-date (RIP points where it should and not at the beginning - * of l2_guest_code(). GUEST_SYNC(9) checkes that. + * of l2_guest_code(). GUEST_SYNC(9) checks that. */ GUEST_ASSERT(!vmresume()); diff --git a/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c b/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c index 6d88c54f69fa..578283893ab3 100644 --- a/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c +++ b/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c @@ -77,7 +77,7 @@ int main(int argc, char *argv[]) ARBITRARY_IO_PORT, run->io.port); /* - * Stuff invalid guest state for L2 by making TR unusuable. The next + * Stuff invalid guest state for L2 by making TR unusable. The next * KVM_RUN should induce a TRIPLE_FAULT in L2 as KVM doesn't support * emulating invalid guest state for L2. */ From 9be54a82656cfda5eab0f4f25354be6075f8467d Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:20 +0000 Subject: [PATCH 05/18] KVM: selftests: Use __stringify() instead of custom XSTR() macros Drop the custom defined XSTR() macros in KVM selftests and use __stringify() instead. Include stringify.h in test_util.h to make it available for all tests instead of including it in all the tests that need it, as more tests will start using it. No functional change intended. Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-2-yosry@kernel.org Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/include/test_util.h | 1 + tools/testing/selftests/kvm/x86/evmcs_smm_controls_test.c | 5 +---- tools/testing/selftests/kvm/x86/fix_hypercall_test.c | 1 - tools/testing/selftests/kvm/x86/smm_test.c | 5 +---- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/kvm/include/test_util.h b/tools/testing/selftests/kvm/include/test_util.h index a56271c237ae..93d589906fa0 100644 --- a/tools/testing/selftests/kvm/include/test_util.h +++ b/tools/testing/selftests/kvm/include/test_util.h @@ -23,6 +23,7 @@ #include #include +#include #define msecs_to_usecs(msec) ((msec) * 1000ULL) diff --git a/tools/testing/selftests/kvm/x86/evmcs_smm_controls_test.c b/tools/testing/selftests/kvm/x86/evmcs_smm_controls_test.c index 77ce87c41a86..aa7f3b405fd3 100644 --- a/tools/testing/selftests/kvm/x86/evmcs_smm_controls_test.c +++ b/tools/testing/selftests/kvm/x86/evmcs_smm_controls_test.c @@ -22,9 +22,6 @@ #define SYNC_PORT 0xe -#define STR(x) #x -#define XSTR(s) STR(s) - /* * SMI handler: runs in real-address mode. * Reports SMRAM_STAGE via port IO, then does RSM. @@ -37,7 +34,7 @@ static u8 smi_handler[] = { static inline void sync_with_host(u64 phase) { - asm volatile("in $" XSTR(SYNC_PORT) ", %%al \n" + asm volatile("in $" __stringify(SYNC_PORT) ", %%al \n" : "+a" (phase)); } diff --git a/tools/testing/selftests/kvm/x86/fix_hypercall_test.c b/tools/testing/selftests/kvm/x86/fix_hypercall_test.c index 753a0e730ea8..4931ec22768e 100644 --- a/tools/testing/selftests/kvm/x86/fix_hypercall_test.c +++ b/tools/testing/selftests/kvm/x86/fix_hypercall_test.c @@ -6,7 +6,6 @@ */ #include #include -#include #include #include "kvm_test_harness.h" diff --git a/tools/testing/selftests/kvm/x86/smm_test.c b/tools/testing/selftests/kvm/x86/smm_test.c index e2542f4ced60..d1edafd5af75 100644 --- a/tools/testing/selftests/kvm/x86/smm_test.c +++ b/tools/testing/selftests/kvm/x86/smm_test.c @@ -22,9 +22,6 @@ #define SMRAM_GPA 0x1000000 #define SMRAM_STAGE 0xfe -#define STR(x) #x -#define XSTR(s) STR(s) - #define SYNC_PORT 0xe #define DONE 0xff @@ -42,7 +39,7 @@ u8 smi_handler[] = { static inline void sync_with_host(u64 phase) { - asm volatile("in $" XSTR(SYNC_PORT)", %%al \n" + asm volatile("in $" __stringify(SYNC_PORT)", %%al \n" : "+a" (phase)); } From 53f4ad49d76b516f5d235ea10b727b2d548b0218 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:21 +0000 Subject: [PATCH 06/18] KVM: selftests: Fix RAX and RFLAGS VMCB offsets when running L2 The offsets used (0x170 and 0x1f8) are offsets within vmcb_save_area, not vmcb. The correct offsets should include the base of vmcb_save_area within vmcb (which is 0x400 -- so 0x570 and 0x5f8). Instead of just correcting the offsets, use vmcb->save.rax and vmcb->save.rflags as parameters to the asm block and avoid hardcoding offsets completely. While at it, also use guest_regs.rax directly instead of assuming it's at offset 0 of guest_regs. Note: "+m" must be used for vmcb_rax and vmcb_rflags, as caching those fields in registers would be wrong as the underlying KVM will update them in memory. The same problem was recently fixed (differently) for kvm-unit-tests [1]. [1]https://lore.kernel.org/all/20260521092311.86030-1-pbonzini@redhat.com/ Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260518202514.2037078-1-yosry%40kernel.org?part=1 Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-3-yosry@kernel.org Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/lib/x86/svm.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/kvm/lib/x86/svm.c b/tools/testing/selftests/kvm/lib/x86/svm.c index 1445b890986f..766d15f1d534 100644 --- a/tools/testing/selftests/kvm/lib/x86/svm.c +++ b/tools/testing/selftests/kvm/lib/x86/svm.c @@ -164,19 +164,22 @@ void run_guest(struct vmcb *vmcb, u64 vmcb_gpa) { asm volatile ( "vmload %[vmcb_gpa]\n\t" - "mov rflags, %%r15\n\t" // rflags - "mov %%r15, 0x170(%[vmcb])\n\t" - "mov guest_regs, %%r15\n\t" // rax - "mov %%r15, 0x1f8(%[vmcb])\n\t" + "mov rflags, %%r15\n\t" + "mov %%r15, %[vmcb_rflags]\n\t" + "mov %[guest_regs_rax], %%r15\n\t" + "mov %%r15, %[vmcb_rax]\n\t" LOAD_GPR_C "vmrun %[vmcb_gpa]\n\t" SAVE_GPR_C - "mov 0x170(%[vmcb]), %%r15\n\t" // rflags + "mov %[vmcb_rflags], %%r15\n\t" "mov %%r15, rflags\n\t" - "mov 0x1f8(%[vmcb]), %%r15\n\t" // rax - "mov %%r15, guest_regs\n\t" + "mov %[vmcb_rax], %%r15\n\t" // rax + "mov %%r15, %[guest_regs_rax]\n\t" "vmsave %[vmcb_gpa]\n\t" - : : [vmcb] "r" (vmcb), [vmcb_gpa] "a" (vmcb_gpa) + : [vmcb_rflags] "+m" (vmcb->save.rflags), + [vmcb_rax] "+m" (vmcb->save.rax), + [guest_regs_rax] "+rm" (guest_regs.rax) + : [vmcb_gpa] "a" (vmcb_gpa) : "r15", "memory"); } From 546455d9c724bd2c927e4902f240a6ceb66ca5e4 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:22 +0000 Subject: [PATCH 07/18] KVM: selftests: Rework GPR registers switching for SVM (and fix offsets) The assembly code defined by SAVE_GPR_C uses the wrong offsets for some registers in guest_regs. For example, the offset of RCX should be 0x08 not 0x10. Also, the last offset in the struct (R15) is 0x78, not 0x80, so the code actually saves and restore beyond the end of gpr64_regs. Eliminate hardcoded offsets by dynamically generating offsets using offset_of() and using macros to pass the offsets to assembly as asm constraints. To avoid register conflicts in inline assembly (since almost all GPRs are context-switched), access guest_regs via absolute symbol addressing (guest_regs + offset) rather than using a base register which could get overwritten mid-assembly. While at it, rename SAVE_GPR_C and LOAD_GPR_C to a single macro, SVM_SWITCH_GPRS_ASM, rename gpr64_regs to guest_regs, and expose it in processor.h (in preparation for reusing it for VMX). Assisted-by: Gemini:Gemini-Next Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-4-yosry@kernel.org Signed-off-by: Sean Christopherson --- .../selftests/kvm/include/x86/processor.h | 31 +++++++++++- .../testing/selftests/kvm/lib/x86/processor.c | 2 + tools/testing/selftests/kvm/lib/x86/svm.c | 49 +++++++++---------- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h index 7d3a27bc0d84..922e607f38f1 100644 --- a/tools/testing/selftests/kvm/include/x86/processor.h +++ b/tools/testing/selftests/kvm/include/x86/processor.h @@ -396,8 +396,7 @@ static inline unsigned int x86_model(unsigned int eax) #define PTE_GET_PA(pte) ((pte) & PHYSICAL_PAGE_MASK) #define PTE_GET_PFN(pte) (PTE_GET_PA(pte) >> PAGE_SHIFT) -/* General Registers in 64-Bit Mode */ -struct gpr64_regs { +struct guest_regs { u64 rax; u64 rcx; u64 rdx; @@ -416,6 +415,34 @@ struct gpr64_regs { u64 r15; }; +extern struct guest_regs guest_regs; + +#define GUEST_REG_OFFSET(name) \ + [off_##name] "i" (offsetof(struct guest_regs, name)) + +#define GUEST_REGS_OFFSETS \ + GUEST_REG_OFFSET(rax), \ + GUEST_REG_OFFSET(rcx), \ + GUEST_REG_OFFSET(rdx), \ + GUEST_REG_OFFSET(rbx), \ + GUEST_REG_OFFSET(rsp), \ + GUEST_REG_OFFSET(rbp), \ + GUEST_REG_OFFSET(rsi), \ + GUEST_REG_OFFSET(rdi), \ + GUEST_REG_OFFSET(r8), \ + GUEST_REG_OFFSET(r9), \ + GUEST_REG_OFFSET(r10), \ + GUEST_REG_OFFSET(r11), \ + GUEST_REG_OFFSET(r12), \ + GUEST_REG_OFFSET(r13), \ + GUEST_REG_OFFSET(r14), \ + GUEST_REG_OFFSET(r15) + +#define GUEST_REG(name) "guest_regs + %c[off_" #name "]" + +#define GUEST_SWITCH_GPR_ASM(name) \ + "xchg %%" #name ", " GUEST_REG(name) "\n\t" + struct desc64 { u16 limit0; u16 base0; diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index ef56dcefe011..1f9201590f5b 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -29,6 +29,8 @@ bool host_cpu_is_amd_compatible; bool is_forced_emulation_enabled; u64 guest_tsc_khz; +struct guest_regs guest_regs; + const char *ex_str(int vector) { switch (vector) { diff --git a/tools/testing/selftests/kvm/lib/x86/svm.c b/tools/testing/selftests/kvm/lib/x86/svm.c index 766d15f1d534..7db38faded89 100644 --- a/tools/testing/selftests/kvm/lib/x86/svm.c +++ b/tools/testing/selftests/kvm/lib/x86/svm.c @@ -13,7 +13,6 @@ #define SEV_DEV_PATH "/dev/sev" -struct gpr64_regs guest_regs; u64 rflags; /* Allocate memory regions for nested SVM tests. @@ -137,27 +136,25 @@ void generic_svm_setup(struct svm_test_data *svm, void *guest_rip) * save/restore 64-bit general registers except rax, rip, rsp * which are directly handed through the VMCB guest processor state */ -#define SAVE_GPR_C \ - "xchg %%rbx, guest_regs+0x20\n\t" \ - "xchg %%rcx, guest_regs+0x10\n\t" \ - "xchg %%rdx, guest_regs+0x18\n\t" \ - "xchg %%rbp, guest_regs+0x30\n\t" \ - "xchg %%rsi, guest_regs+0x38\n\t" \ - "xchg %%rdi, guest_regs+0x40\n\t" \ - "xchg %%r8, guest_regs+0x48\n\t" \ - "xchg %%r9, guest_regs+0x50\n\t" \ - "xchg %%r10, guest_regs+0x58\n\t" \ - "xchg %%r11, guest_regs+0x60\n\t" \ - "xchg %%r12, guest_regs+0x68\n\t" \ - "xchg %%r13, guest_regs+0x70\n\t" \ - "xchg %%r14, guest_regs+0x78\n\t" \ - "xchg %%r15, guest_regs+0x80\n\t" - -#define LOAD_GPR_C SAVE_GPR_C +#define SVM_SWITCH_GPRS_ASM \ + GUEST_SWITCH_GPR_ASM(rbx) \ + GUEST_SWITCH_GPR_ASM(rcx) \ + GUEST_SWITCH_GPR_ASM(rdx) \ + GUEST_SWITCH_GPR_ASM(rbp) \ + GUEST_SWITCH_GPR_ASM(rsi) \ + GUEST_SWITCH_GPR_ASM(rdi) \ + GUEST_SWITCH_GPR_ASM(r8) \ + GUEST_SWITCH_GPR_ASM(r9) \ + GUEST_SWITCH_GPR_ASM(r10) \ + GUEST_SWITCH_GPR_ASM(r11) \ + GUEST_SWITCH_GPR_ASM(r12) \ + GUEST_SWITCH_GPR_ASM(r13) \ + GUEST_SWITCH_GPR_ASM(r14) \ + GUEST_SWITCH_GPR_ASM(r15) /* * selftests do not use interrupts so we dropped clgi/sti/cli/stgi - * for now. registers involved in LOAD/SAVE_GPR_C are eventually + * for now. Registers involved in SVM_SWITCH_GPRS_ASM are eventually * unmodified so they do not need to be in the clobber list. */ void run_guest(struct vmcb *vmcb, u64 vmcb_gpa) @@ -166,20 +163,20 @@ void run_guest(struct vmcb *vmcb, u64 vmcb_gpa) "vmload %[vmcb_gpa]\n\t" "mov rflags, %%r15\n\t" "mov %%r15, %[vmcb_rflags]\n\t" - "mov %[guest_regs_rax], %%r15\n\t" + "mov " GUEST_REG(rax) ", %%r15\n\t" "mov %%r15, %[vmcb_rax]\n\t" - LOAD_GPR_C + SVM_SWITCH_GPRS_ASM "vmrun %[vmcb_gpa]\n\t" - SAVE_GPR_C + SVM_SWITCH_GPRS_ASM "mov %[vmcb_rflags], %%r15\n\t" "mov %%r15, rflags\n\t" "mov %[vmcb_rax], %%r15\n\t" // rax - "mov %%r15, %[guest_regs_rax]\n\t" + "mov %%r15, " GUEST_REG(rax) "\n\t" "vmsave %[vmcb_gpa]\n\t" : [vmcb_rflags] "+m" (vmcb->save.rflags), - [vmcb_rax] "+m" (vmcb->save.rax), - [guest_regs_rax] "+rm" (guest_regs.rax) - : [vmcb_gpa] "a" (vmcb_gpa) + [vmcb_rax] "+m" (vmcb->save.rax) + : [vmcb_gpa] "a" (vmcb_gpa), + GUEST_REGS_OFFSETS : "r15", "memory"); } From 9547309406d506a1dc2f36918ddf4173451ff257 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:23 +0000 Subject: [PATCH 08/18] KVM: selftests: Handle rflags save/restore for SVM in guest_regs Instead of handling rflags separately, add it to guest_regs. No functional change intended. Assisted-by: Gemini:Gemini-Next Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-5-yosry@kernel.org Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/include/x86/processor.h | 4 +++- tools/testing/selftests/kvm/lib/x86/svm.c | 6 ++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h index 922e607f38f1..3344547a3ae4 100644 --- a/tools/testing/selftests/kvm/include/x86/processor.h +++ b/tools/testing/selftests/kvm/include/x86/processor.h @@ -413,6 +413,7 @@ struct guest_regs { u64 r13; u64 r14; u64 r15; + u64 rflags; }; extern struct guest_regs guest_regs; @@ -436,7 +437,8 @@ extern struct guest_regs guest_regs; GUEST_REG_OFFSET(r12), \ GUEST_REG_OFFSET(r13), \ GUEST_REG_OFFSET(r14), \ - GUEST_REG_OFFSET(r15) + GUEST_REG_OFFSET(r15), \ + GUEST_REG_OFFSET(rflags) #define GUEST_REG(name) "guest_regs + %c[off_" #name "]" diff --git a/tools/testing/selftests/kvm/lib/x86/svm.c b/tools/testing/selftests/kvm/lib/x86/svm.c index 7db38faded89..b05be50f075d 100644 --- a/tools/testing/selftests/kvm/lib/x86/svm.c +++ b/tools/testing/selftests/kvm/lib/x86/svm.c @@ -13,8 +13,6 @@ #define SEV_DEV_PATH "/dev/sev" -u64 rflags; - /* Allocate memory regions for nested SVM tests. * * Input Args: @@ -161,7 +159,7 @@ void run_guest(struct vmcb *vmcb, u64 vmcb_gpa) { asm volatile ( "vmload %[vmcb_gpa]\n\t" - "mov rflags, %%r15\n\t" + "mov " GUEST_REG(rflags) ", %%r15\n\t" "mov %%r15, %[vmcb_rflags]\n\t" "mov " GUEST_REG(rax) ", %%r15\n\t" "mov %%r15, %[vmcb_rax]\n\t" @@ -169,7 +167,7 @@ void run_guest(struct vmcb *vmcb, u64 vmcb_gpa) "vmrun %[vmcb_gpa]\n\t" SVM_SWITCH_GPRS_ASM "mov %[vmcb_rflags], %%r15\n\t" - "mov %%r15, rflags\n\t" + "mov %%r15, " GUEST_REG(rflags) "\n\t" "mov %[vmcb_rax], %%r15\n\t" // rax "mov %%r15, " GUEST_REG(rax) "\n\t" "vmsave %[vmcb_gpa]\n\t" From 3d31fea7b6d1a11ab6e7bea61c0442d1ec2fea5a Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:24 +0000 Subject: [PATCH 09/18] KVM: selftests: Reuse GPR switching logic for nVMX Reuse the GPR switching logic for nVMX by defining VMX_SWITCH_GPRS_ASM, which is essentially the same as SVM_SWITCH_GPRS_ASM but also switches RAX and doesn't switch RFLAGS, replacing the push/pop of a subset of the registers. The long clobber list of registers is no longer needed as registers are saved and restored appropriately (and not clobbered by L2). Define VMX_SWITCH_GPRS_ASM before including evmcs.h, such that it can be used by evmcs_vmlaunch() and evmcs_vmresume(). This replaces the apparently thread-safe push/pop sequence with the global GPR switching logic used by SVM, which isn't thread-safe at all. However this is still an improvement because: - The VMX logic is half-baked and prompts the UCALL clobber hack as it doesn't properly save/restore everything. Reusing the GPR switching logic used by SVM allows for dropping that hack. - Hitting a problem due to half-baked GPR save/restore logic is arguably more likely than thread-safety. Evidently, adding more involved stress tests fails on VMX with the existing push/pop sequence. OTOH, there are no known failures on SVM due to lack of thread-safety fo save/restore. Only one test currently uses more than one vCPU with nested (the memstress test). The logical next step is to move the guest_regs to be per-vCPU, making it thread-safe for both VMX and SVM in a proper way. Assisted-by: Gemini:gemini-3.1-pro Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-6-yosry@kernel.org Signed-off-by: Sean Christopherson --- .../testing/selftests/kvm/include/x86/evmcs.h | 46 +++++-------- tools/testing/selftests/kvm/include/x86/vmx.h | 69 +++++++++---------- 2 files changed, 49 insertions(+), 66 deletions(-) diff --git a/tools/testing/selftests/kvm/include/x86/evmcs.h b/tools/testing/selftests/kvm/include/x86/evmcs.h index be79bda024bf..82a8ea6b661f 100644 --- a/tools/testing/selftests/kvm/include/x86/evmcs.h +++ b/tools/testing/selftests/kvm/include/x86/evmcs.h @@ -1207,30 +1207,23 @@ static inline int evmcs_vmlaunch(void) current_evmcs->hv_clean_fields = 0; - __asm__ __volatile__("push %%rbp;" - "push %%rcx;" - "push %%rdx;" - "push %%rsi;" - "push %%rdi;" - "push $0;" + __asm__ __volatile__("push $0;" "mov %%rsp, (%[host_rsp]);" "lea 1f(%%rip), %%rax;" "mov %%rax, (%[host_rip]);" + VMX_SWITCH_GPRS_ASM "vmlaunch;" "incq (%%rsp);" - "1: pop %%rax;" - "pop %%rdi;" - "pop %%rsi;" - "pop %%rdx;" - "pop %%rcx;" - "pop %%rbp;" + "1: ;" + VMX_SWITCH_GPRS_ASM + "pop %%rax;" : [ret]"=&a"(ret) : [host_rsp]"r" ((u64)¤t_evmcs->host_rsp), [host_rip]"r" - ((u64)¤t_evmcs->host_rip) - : "memory", "cc", "rbx", "r8", "r9", "r10", - "r11", "r12", "r13", "r14", "r15"); + ((u64)¤t_evmcs->host_rip), + GUEST_REGS_OFFSETS + : "memory", "cc"); return ret; } @@ -1246,30 +1239,23 @@ static inline int evmcs_vmresume(void) /* HOST_RSP */ current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER; - __asm__ __volatile__("push %%rbp;" - "push %%rcx;" - "push %%rdx;" - "push %%rsi;" - "push %%rdi;" - "push $0;" + __asm__ __volatile__("push $0;" "mov %%rsp, (%[host_rsp]);" "lea 1f(%%rip), %%rax;" "mov %%rax, (%[host_rip]);" + VMX_SWITCH_GPRS_ASM "vmresume;" "incq (%%rsp);" - "1: pop %%rax;" - "pop %%rdi;" - "pop %%rsi;" - "pop %%rdx;" - "pop %%rcx;" - "pop %%rbp;" + "1: ;" + VMX_SWITCH_GPRS_ASM + "pop %%rax;" : [ret]"=&a"(ret) : [host_rsp]"r" ((u64)¤t_evmcs->host_rsp), [host_rip]"r" - ((u64)¤t_evmcs->host_rip) - : "memory", "cc", "rbx", "r8", "r9", "r10", - "r11", "r12", "r13", "r14", "r15"); + ((u64)¤t_evmcs->host_rip), + GUEST_REGS_OFFSETS + : "memory", "cc"); return ret; } diff --git a/tools/testing/selftests/kvm/include/x86/vmx.h b/tools/testing/selftests/kvm/include/x86/vmx.h index 4bcfd60e3aec..04f5e34dea3a 100644 --- a/tools/testing/selftests/kvm/include/x86/vmx.h +++ b/tools/testing/selftests/kvm/include/x86/vmx.h @@ -290,6 +290,23 @@ struct vmx_msr_entry { u64 value; } __attribute__ ((aligned(16))); +#define VMX_SWITCH_GPRS_ASM \ + GUEST_SWITCH_GPR_ASM(rax) \ + GUEST_SWITCH_GPR_ASM(rbx) \ + GUEST_SWITCH_GPR_ASM(rcx) \ + GUEST_SWITCH_GPR_ASM(rdx) \ + GUEST_SWITCH_GPR_ASM(rbp) \ + GUEST_SWITCH_GPR_ASM(rsi) \ + GUEST_SWITCH_GPR_ASM(rdi) \ + GUEST_SWITCH_GPR_ASM(r8) \ + GUEST_SWITCH_GPR_ASM(r9) \ + GUEST_SWITCH_GPR_ASM(r10) \ + GUEST_SWITCH_GPR_ASM(r11) \ + GUEST_SWITCH_GPR_ASM(r12) \ + GUEST_SWITCH_GPR_ASM(r13) \ + GUEST_SWITCH_GPR_ASM(r14) \ + GUEST_SWITCH_GPR_ASM(r15) + #include "evmcs.h" static inline int vmxon(u64 phys) @@ -363,9 +380,6 @@ static inline u64 vmptrstz(void) return value; } -/* - * No guest state (e.g. GPRs) is established by this vmlaunch. - */ static inline int vmlaunch(void) { int ret; @@ -373,34 +387,24 @@ static inline int vmlaunch(void) if (enable_evmcs) return evmcs_vmlaunch(); - __asm__ __volatile__("push %%rbp;" - "push %%rcx;" - "push %%rdx;" - "push %%rsi;" - "push %%rdi;" - "push $0;" + __asm__ __volatile__("push $0;" "vmwrite %%rsp, %[host_rsp];" "lea 1f(%%rip), %%rax;" "vmwrite %%rax, %[host_rip];" + VMX_SWITCH_GPRS_ASM "vmlaunch;" "incq (%%rsp);" - "1: pop %%rax;" - "pop %%rdi;" - "pop %%rsi;" - "pop %%rdx;" - "pop %%rcx;" - "pop %%rbp;" + "1: ;" + VMX_SWITCH_GPRS_ASM + "pop %%rax;" : [ret]"=&a"(ret) : [host_rsp]"r"((u64)HOST_RSP), - [host_rip]"r"((u64)HOST_RIP) - : "memory", "cc", "rbx", "r8", "r9", "r10", - "r11", "r12", "r13", "r14", "r15"); + [host_rip]"r"((u64)HOST_RIP), + GUEST_REGS_OFFSETS + : "memory", "cc"); return ret; } -/* - * No guest state (e.g. GPRs) is established by this vmresume. - */ static inline int vmresume(void) { int ret; @@ -408,28 +412,21 @@ static inline int vmresume(void) if (enable_evmcs) return evmcs_vmresume(); - __asm__ __volatile__("push %%rbp;" - "push %%rcx;" - "push %%rdx;" - "push %%rsi;" - "push %%rdi;" - "push $0;" + __asm__ __volatile__("push $0;" "vmwrite %%rsp, %[host_rsp];" "lea 1f(%%rip), %%rax;" "vmwrite %%rax, %[host_rip];" + VMX_SWITCH_GPRS_ASM "vmresume;" "incq (%%rsp);" - "1: pop %%rax;" - "pop %%rdi;" - "pop %%rsi;" - "pop %%rdx;" - "pop %%rcx;" - "pop %%rbp;" + "1: ;" + VMX_SWITCH_GPRS_ASM + "pop %%rax;" : [ret]"=&a"(ret) : [host_rsp]"r"((u64)HOST_RSP), - [host_rip]"r"((u64)HOST_RIP) - : "memory", "cc", "rbx", "r8", "r9", "r10", - "r11", "r12", "r13", "r14", "r15"); + [host_rip]"r"((u64)HOST_RIP), + GUEST_REGS_OFFSETS + : "memory", "cc"); return ret; } From 2bdef77c6019c3439e7a07a82ed76dde2747633f Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:25 +0000 Subject: [PATCH 10/18] KVM: selftests: Drop HORRIFIC_L2_UCALL_CLOBBER_HACK Now that nVMX test codes preserves GPRs across nested VM-Exits (specifically RBP, RDX, and RDI among others), drop the ucall-specific hack to avoid clobbering these registers. Assisted-by: Gemini:gemini-3.1-pro Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-7-yosry@kernel.org Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/lib/x86/ucall.c | 32 ++------------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/tools/testing/selftests/kvm/lib/x86/ucall.c b/tools/testing/selftests/kvm/lib/x86/ucall.c index e7dd5791959b..38050c60a067 100644 --- a/tools/testing/selftests/kvm/lib/x86/ucall.c +++ b/tools/testing/selftests/kvm/lib/x86/ucall.c @@ -10,36 +10,8 @@ void ucall_arch_do_ucall(gva_t uc) { - /* - * FIXME: Revert this hack (the entire commit that added it) once nVMX - * preserves L2 GPRs across a nested VM-Exit. If a ucall from L2, e.g. - * to do a GUEST_SYNC(), lands the vCPU in L1, any and all GPRs can be - * clobbered by L1. Save and restore non-volatile GPRs (clobbering RBP - * in particular is problematic) along with RDX and RDI (which are - * inputs), and clobber volatile GPRs. *sigh* - */ -#define HORRIFIC_L2_UCALL_CLOBBER_HACK \ - "rcx", "rsi", "r8", "r9", "r10", "r11" - - asm volatile("push %%rbp\n\t" - "push %%r15\n\t" - "push %%r14\n\t" - "push %%r13\n\t" - "push %%r12\n\t" - "push %%rbx\n\t" - "push %%rdx\n\t" - "push %%rdi\n\t" - "in %[port], %%al\n\t" - "pop %%rdi\n\t" - "pop %%rdx\n\t" - "pop %%rbx\n\t" - "pop %%r12\n\t" - "pop %%r13\n\t" - "pop %%r14\n\t" - "pop %%r15\n\t" - "pop %%rbp\n\t" - : : [port] "d" (UCALL_PIO_PORT), "D" (uc) : "rax", "memory", - HORRIFIC_L2_UCALL_CLOBBER_HACK); + asm volatile("in %[port], %%al" + : : [port] "d" (UCALL_PIO_PORT), "D" (uc) : "rax", "memory"); } void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu) From 12f4d8b06a530599ef17123c5278dc1e90f93567 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:26 +0000 Subject: [PATCH 11/18] KVM: selftests: Add a blank line before logging assertion failures Add a blank line for visual separation, which is especially useful for tests that use carriage returns for same-line printing, in case a failure occurs before a blank line is ever logged by the test. Suggested-by: Sean Christopherson Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-8-yosry@kernel.org Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/lib/assert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/kvm/lib/assert.c b/tools/testing/selftests/kvm/lib/assert.c index 8be0d09ecf0f..781a07099782 100644 --- a/tools/testing/selftests/kvm/lib/assert.c +++ b/tools/testing/selftests/kvm/lib/assert.c @@ -78,7 +78,7 @@ test_assert(bool exp, const char *exp_str, if (!(exp)) { va_start(ap, fmt); - fprintf(stderr, "==== Test Assertion Failure ====\n" + fprintf(stderr, "\n==== Test Assertion Failure ====\n" " %s:%u: %s\n" " pid=%d tid=%d errno=%d - %s\n", file, line, exp_str, getpid(), _gettid(), From e21d4dc0ca63cbb0be2a22c0aed42100b47940b7 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:27 +0000 Subject: [PATCH 12/18] KVM: selftests: Expose PTE masks to guests as part of an MMU Expose a guest_mmu to the guest to allow guest code to use the PTE masks for page table manipulation. Since guest page tables are not mapped in the guest by default, zero the PGD in guest_mmu in an attempt to make it more difficult for new tests to shoot themselves in the foot and assume that page tables can be immediately used by guest code. Ultimately, guest code can read CR3 any way, so guest_mmu.pgd doesn't add a lot of value. Suggested-by: Sean Christopherson Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-9-yosry@kernel.org Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/include/x86/processor.h | 1 + tools/testing/selftests/kvm/lib/x86/processor.c | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h index 3344547a3ae4..cd86f487bce2 100644 --- a/tools/testing/selftests/kvm/include/x86/processor.h +++ b/tools/testing/selftests/kvm/include/x86/processor.h @@ -24,6 +24,7 @@ extern bool host_cpu_is_amd; extern bool host_cpu_is_hygon; extern bool host_cpu_is_amd_compatible; extern u64 guest_tsc_khz; +extern struct kvm_mmu guest_mmu; #ifndef MAX_NR_CPUID_ENTRIES #define MAX_NR_CPUID_ENTRIES 100 diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index 1f9201590f5b..d31fa81ea075 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -28,6 +28,7 @@ bool host_cpu_is_hygon; bool host_cpu_is_amd_compatible; bool is_forced_emulation_enabled; u64 guest_tsc_khz; +struct kvm_mmu guest_mmu; struct guest_regs guest_regs; @@ -831,6 +832,17 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus) TEST_ASSERT(r > 0, "KVM_GET_TSC_KHZ did not provide a valid TSC frequency."); guest_tsc_khz = r; sync_global_to_guest(vm, guest_tsc_khz); + + /* + * The guest MMU is just a placeholder to provide access to PTE masks + * (for now). The guest does not have mappings for its own page tables + * by default, so any meaningful use of guest page tables requires + * explicit setup by the test. Zero the PGD to make it obvious the guest + * page tables are not immediately usable by guest code. + */ + guest_mmu = vm->mmu; + guest_mmu.pgd = 0; + sync_global_to_guest(vm, guest_mmu); } void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code) From 7317f5b143aafef9e75d9e8af8b25cb9a9f3bee6 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:28 +0000 Subject: [PATCH 13/18] KVM: selftests: Do not intercept #PF by default in nVMX tests init_vmcs_control_fields() sets PFEC_MASK and PFEC_MATCH so that they never match, which reverses the meaning of the PF_VECTOR bit in EXCEPTION_BITMAP (which is zeroed), effectively enabling #PF interception by default. The relevant part of the SDM describes this: When a page fault occurs, a processor consults (1) bit 14 of the exception bitmap; (2) the error code produced with the page fault [PFEC]; (3) the page-fault error-code mask field [PFEC_MASK]; and (4) the page-fault error-code match field [PFEC_MATCH]. It checks if PFEC & PFEC_MASK = PFEC_MATCH. If there is equality, the specification of bit 14 in the exception bitmap is followed (for example, a VM exit occurs if that bit is set). If there is inequality, the meaning of that bit is reversed (for example, a VM exit occurs if that bit is clear). Clear PFEC_MATCH such that there is equality, and the #PF bit in the exception bitmap is followed, so that #PFs are not intercepted by default, same as every other exception. This also allows tests to set the #PF bit in the exception bitmap if they want to intercept #PFs, without having to muck with the PFEC_{MASK,MATCH} fields. Suggested-by: Sean Christopherson Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-10-yosry@kernel.org Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/lib/x86/vmx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/kvm/lib/x86/vmx.c b/tools/testing/selftests/kvm/lib/x86/vmx.c index cd09c9de4485..089e1a8af53f 100644 --- a/tools/testing/selftests/kvm/lib/x86/vmx.c +++ b/tools/testing/selftests/kvm/lib/x86/vmx.c @@ -232,7 +232,7 @@ static inline void init_vmcs_control_fields(struct vmx_pages *vmx) vmwrite(EXCEPTION_BITMAP, 0); vmwrite(PAGE_FAULT_ERROR_CODE_MASK, 0); - vmwrite(PAGE_FAULT_ERROR_CODE_MATCH, -1); /* Never match */ + vmwrite(PAGE_FAULT_ERROR_CODE_MATCH, 0); vmwrite(CR3_TARGET_COUNT, 0); vmwrite(VM_EXIT_CONTROLS, rdmsr(MSR_IA32_VMX_EXIT_CTLS) | VM_EXIT_HOST_ADDR_SPACE_SIZE); /* 64-bit host */ From d24dde74f72f3448c4020ba67f6218ecbd1e6d8d Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:29 +0000 Subject: [PATCH 14/18] KVM: selftests: Add basic stress test for save+restore and #PF handling Add a basic stress test for handling #PFs in a guest while the host is doing save+restore cycles. The guest periodically accesses non-present memory causing a #PF, and the #PF handler walks the page tables and updates the PTE to be present, like a proper #PF handler. After every access (and #PF), the guest triggers a sync and the test performs save+restore of the VM. This is not very meaningful as save+restore are performed after the access and #PF handling complete, but following changes will change that. Assisted-by: Gemini:gemini-3.1-pro Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-11-yosry@kernel.org Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/include/x86/processor.h | 13 ++ .../kvm/x86/save_restore_pf_stress_test.c | 160 ++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 4ace12606e93..b5870d37533e 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -106,6 +106,7 @@ TEST_GEN_PROGS_x86 += x86/pmu_counters_test TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test TEST_GEN_PROGS_x86 += x86/private_mem_conversions_test TEST_GEN_PROGS_x86 += x86/private_mem_kvm_exits_test +TEST_GEN_PROGS_x86 += x86/save_restore_pf_stress_test TEST_GEN_PROGS_x86 += x86/set_boot_cpu_id TEST_GEN_PROGS_x86 += x86/set_sregs_test TEST_GEN_PROGS_x86 += x86/smaller_maxphyaddr_emulation_test diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h index cd86f487bce2..0b29cdec62eb 100644 --- a/tools/testing/selftests/kvm/include/x86/processor.h +++ b/tools/testing/selftests/kvm/include/x86/processor.h @@ -612,6 +612,14 @@ static inline void set_cr0(u64 val) __asm__ __volatile__("mov %0, %%cr0" : : "r" (val) : "memory"); } +static inline u64 get_cr2(void) +{ + u64 cr2; + + __asm__ __volatile__("mov %%cr2, %[cr2]" : [cr2]"=r"(cr2)); + return cr2; +} + static inline u64 get_cr3(void) { u64 cr3; @@ -907,6 +915,11 @@ static inline void write_sse_reg(int reg, const sse128_t *data) } } +static inline void invlpg(u64 addr) +{ + __asm__ __volatile__("invlpg (%0)" : : "r"(addr) : "memory"); +} + static inline void cpu_relax(void) { asm volatile("rep; nop" ::: "memory"); diff --git a/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c new file mode 100644 index 000000000000..1f7e142e5cfe --- /dev/null +++ b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include + +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" + +#define NR_ITERATIONS 500 + +#define PTRS_PER_PTE 512 +#define PXD_INDEX(vaddr, level) (((vaddr) >> PG_LEVEL_SHIFT(level)) & (PTRS_PER_PTE - 1)) + +#define TEST_MEM_BASE_GVA 0xc0000000ULL +#define TEST_PGTABLE_GVA_OFFSET 0xd0000000ULL +#define PATTERN 0xabcdefabcdefabcdULL + +static u64 expected_vaddr; +static u64 guest_faults; + +static u64 *guest_get_pte(u64 vaddr) +{ + u64 pgtable_pa, pte; + u64 *pgtable; + int level; + + level = (get_cr4() & X86_CR4_LA57) ? PG_LEVEL_256T : PG_LEVEL_512G; + + pgtable_pa = get_cr3() & PHYSICAL_PAGE_MASK; + for (; level > PG_LEVEL_4K; level--) { + pgtable = (u64 *)(pgtable_pa + TEST_PGTABLE_GVA_OFFSET); + pte = pgtable[PXD_INDEX(vaddr, level)]; + GUEST_ASSERT(pte & PTE_PRESENT_MASK(&guest_mmu)); + GUEST_ASSERT(!(pte & PTE_HUGE_MASK(&guest_mmu))); + pgtable_pa = PTE_GET_PA(pte); + } + + pgtable = (u64 *)(pgtable_pa + TEST_PGTABLE_GVA_OFFSET); + return &pgtable[PXD_INDEX(vaddr, PG_LEVEL_4K)]; +} + +static void guest_pf_handler(struct ex_regs *regs) +{ + u64 fault_addr; + u64 *ptep; + + fault_addr = get_cr2(); + GUEST_ASSERT_EQ(fault_addr, READ_ONCE(expected_vaddr)); + + ptep = guest_get_pte(fault_addr); + GUEST_ASSERT(ptep); + GUEST_ASSERT(!(*ptep & PTE_PRESENT_MASK(&guest_mmu))); + + *ptep |= PTE_PRESENT_MASK(&guest_mmu); + guest_faults++; +} + +static void guest_access_memory(void *arg) +{ + u64 vaddr, val; + int i; + + for (i = 0; ; i++) { + vaddr = TEST_MEM_BASE_GVA + (i % PTRS_PER_PTE) * PAGE_SIZE; + WRITE_ONCE(expected_vaddr, vaddr); + + /* Read to trigger #PF */ + val = READ_ONCE(*(u64 *)vaddr); + GUEST_ASSERT_EQ(val, PATTERN); + + /* Clear the present bit again so it faults next time */ + *guest_get_pte(vaddr) &= ~PTE_PRESENT_MASK(&guest_mmu); + invlpg(vaddr); + + GUEST_SYNC(guest_faults); + } +} + +int main(int argc, char *argv[]) +{ + struct kvm_x86_state *state; + int r, i, level; + gpa_t gpa, pgtable_gpa; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + u64 *pgtable; + gva_t gva; + u64 pte; + + vm = vm_create_with_one_vcpu(&vcpu, guest_access_memory); + vm_install_exception_handler(vm, PF_VECTOR, guest_pf_handler); + + /* Allocate a page and write the pattern to it */ + gva = vm_alloc_page(vm); + *(u64 *)addr_gva2hva(vm, gva) = PATTERN; + gpa = addr_gva2gpa(vm, gva); + + /* + * Map all virtual addresses to the pattern page and clear the present + * bit such that guest accesses will cause a #PF. + */ + for (i = 0; i < PTRS_PER_PTE; i++) { + gva = TEST_MEM_BASE_GVA + i * getpagesize(); + virt_pg_map(vm, gva, gpa); + *vm_get_pte(vm, gva) &= ~PTE_PRESENT_MASK(&vm->mmu); + } + + /* + * Now create mappings for the page tables created above so that the + * guest #PF handler can walk them. All PTEs for test virtual addresses + * should lie on the same PTE page, so one page is mapped for each page + * table level. + * + * Use an offset for the GVA instead of creating identity mappings to + * avoid collision with existing mappings at low GVAs (e.g. ELF). + */ + pgtable_gpa = vm->mmu.pgd; + for (level = vm->mmu.pgtable_levels; level >= PG_LEVEL_4K; level--) { + virt_map(vm, pgtable_gpa + TEST_PGTABLE_GVA_OFFSET, pgtable_gpa, 1); + pgtable = addr_gpa2hva(vm, pgtable_gpa); + pte = pgtable[PXD_INDEX(TEST_MEM_BASE_GVA, level)]; + pgtable_gpa = PTE_GET_PA(pte); + } + + for (i = 1; i <= NR_ITERATIONS; i++) { + r = __vcpu_run(vcpu); + TEST_ASSERT(!r, "vcpu_run failed"); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + + get_ucall(vcpu, &uc); + if (uc.cmd == UCALL_ABORT) { + REPORT_GUEST_ASSERT(uc); + break; + } + TEST_ASSERT_EQ(uc.cmd, UCALL_SYNC); + TEST_ASSERT_EQ(uc.args[1], i); + + state = vcpu_save_state(vcpu); + + kvm_vm_release(vm); + vcpu = vm_recreate_with_one_vcpu(vm); + vcpu_load_state(vcpu, state); + kvm_x86_state_cleanup(state); + + pr_info("\rSave+restore iterations: %d", i); + } + pr_info("\n"); + + sync_global_from_guest(vm, guest_faults); + pr_info("Guest page faults: %lu\n", guest_faults); + + kvm_vm_free(vm); + return 0; +} From d99ad3c842f0837ef8dcc622ff88473a700c187f Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:30 +0000 Subject: [PATCH 15/18] KVM: selftests: Trigger save+restore randomly in the #PF stress test Instead of an explicit GUEST_SYNC() after each access+#PF, run another thread that keeps sending SIGUSR to the vCPU thread, essentially triggering exits to userspace and save+restore on random points in guest execution. This makes the test a lot more meaningful as it opens the door to exercising race conditions between #PF handling in the guest and save+restore in the host. The signals are ignored using SIG_IGN outside of __vcpu_run() to avoid interrupting other ioctls/sysctls performed by the test. Assisted-by: Gemini:gemini-3.1-pro Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-12-yosry@kernel.org Signed-off-by: Sean Christopherson --- .../kvm/x86/save_restore_pf_stress_test.c | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c index 1f7e142e5cfe..4b5accec642c 100644 --- a/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c +++ b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include "test_util.h" @@ -76,15 +78,41 @@ static void guest_access_memory(void *arg) /* Clear the present bit again so it faults next time */ *guest_get_pte(vaddr) &= ~PTE_PRESENT_MASK(&guest_mmu); invlpg(vaddr); - - GUEST_SYNC(guest_faults); } } +static void *sigusr_thread_fn(void *arg) +{ + pthread_t vcpu_thread = (pthread_t)arg; + + for (;;) { + pthread_testcancel(); + pthread_kill(vcpu_thread, SIGUSR1); + usleep(msecs_to_usecs(1)); + } + return NULL; +} + +static void dummy_signal_handler(int signo) {} +static struct sigaction sa; + +static void vcpu_sigusr_listen(void) +{ + sa.sa_handler = dummy_signal_handler; + sigaction(SIGUSR1, &sa, NULL); +} + +static void vcpu_sigusr_ignore(void) +{ + sa.sa_handler = SIG_IGN; + sigaction(SIGUSR1, &sa, NULL); +} + int main(int argc, char *argv[]) { struct kvm_x86_state *state; int r, i, level; + pthread_t sigusr_thread; gpa_t gpa, pgtable_gpa; struct kvm_vcpu *vcpu; struct kvm_vm *vm; @@ -128,18 +156,29 @@ int main(int argc, char *argv[]) pgtable_gpa = PTE_GET_PA(pte); } - for (i = 1; i <= NR_ITERATIONS; i++) { - r = __vcpu_run(vcpu); - TEST_ASSERT(!r, "vcpu_run failed"); - TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + /* Initialize the thread sending SIGUSR and install the handler */ + vcpu_sigusr_ignore(); + r = pthread_create(&sigusr_thread, NULL, sigusr_thread_fn, + (void *)pthread_self()); + TEST_ASSERT(!r, "pthread_create() failed: %d", r); - get_ucall(vcpu, &uc); - if (uc.cmd == UCALL_ABORT) { + for (i = 1; i <= NR_ITERATIONS; i++) { + /* + * Only handle SIGUSR while the vCPU is running, otherwise + * ignore it to avoid interrupting other ioctls/syscalls. + */ + vcpu_sigusr_listen(); + r = __vcpu_run(vcpu); + TEST_ASSERT(!r || errno == EINTR, "Expected success or SIGUSR1"); + vcpu_sigusr_ignore(); + + /* The guest only exits due to a signal or failed assertion */ + if (!r) { + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_ABORT); REPORT_GUEST_ASSERT(uc); break; } - TEST_ASSERT_EQ(uc.cmd, UCALL_SYNC); - TEST_ASSERT_EQ(uc.args[1], i); state = vcpu_save_state(vcpu); @@ -153,8 +192,11 @@ int main(int argc, char *argv[]) pr_info("\n"); sync_global_from_guest(vm, guest_faults); + TEST_ASSERT(guest_faults, "No guest page faults triggered"); pr_info("Guest page faults: %lu\n", guest_faults); + pthread_cancel(sigusr_thread); + pthread_join(sigusr_thread, NULL); kvm_vm_free(vm); return 0; } From 1494b3d17c958f00258ef6d6efa9d3a33d373db4 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:31 +0000 Subject: [PATCH 16/18] KVM: selftests: Support running stress save+restore and #PF test in L2 Extend the stress test to allow running the access+#PF code in L2 instead of L1 by adding proper L1 guest code to bootstrap L2. By default, the test runs in L2 after running in L1 if nested is supported. Assisted-by: Gemini:gemini-3.1-pro Signed-off-by: Yosry Ahmed Link: https://patch.msgid.link/20260728174232.2423257-13-yosry@kernel.org Signed-off-by: Sean Christopherson --- .../kvm/x86/save_restore_pf_stress_test.c | 56 ++++++++++++++++++- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c index 4b5accec642c..7418e5fb879b 100644 --- a/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c +++ b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c @@ -8,10 +8,13 @@ #include #include #include +#include #include "test_util.h" #include "kvm_util.h" #include "processor.h" +#include "svm_util.h" +#include "vmx.h" #define NR_ITERATIONS 500 @@ -81,6 +84,31 @@ 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); +} + +static void l1_vmx_code(struct vmx_pages *vmx) +{ + GUEST_ASSERT(prepare_for_vmx_operation(vmx)); + GUEST_ASSERT(load_vmcs(vmx)); + prepare_vmcs(vmx, guest_access_memory); + + GUEST_ASSERT(!vmlaunch()); + GUEST_ASSERT(false); +} + +static void l1_guest_code(void *test_data) +{ + if (this_cpu_has(X86_FEATURE_SVM)) + l1_svm_code(test_data); + else + l1_vmx_code(test_data); +} + static void *sigusr_thread_fn(void *arg) { pthread_t vcpu_thread = (pthread_t)arg; @@ -108,7 +136,7 @@ static void vcpu_sigusr_ignore(void) sigaction(SIGUSR1, &sa, NULL); } -int main(int argc, char *argv[]) +static void run_test(bool nested) { struct kvm_x86_state *state; int r, i, level; @@ -121,9 +149,17 @@ int main(int argc, char *argv[]) gva_t gva; u64 pte; - vm = vm_create_with_one_vcpu(&vcpu, guest_access_memory); + vm = vm_create_with_one_vcpu(&vcpu, nested ? l1_guest_code : guest_access_memory); vm_install_exception_handler(vm, PF_VECTOR, guest_pf_handler); + if (nested) { + if (kvm_cpu_has(X86_FEATURE_SVM)) + vcpu_alloc_svm(vm, &gva); + else + vcpu_alloc_vmx(vm, &gva); + vcpu_args_set(vcpu, 1, gva); + } + /* Allocate a page and write the pattern to it */ gva = vm_alloc_page(vm); *(u64 *)addr_gva2hva(vm, gva) = PATTERN; @@ -193,10 +229,24 @@ int main(int argc, char *argv[]) sync_global_from_guest(vm, guest_faults); TEST_ASSERT(guest_faults, "No guest page faults triggered"); - pr_info("Guest page faults: %lu\n", guest_faults); + pr_info("Guest page faults%s: %lu\n", nested ? " (in L2)" : "", guest_faults); pthread_cancel(sigusr_thread); pthread_join(sigusr_thread, NULL); kvm_vm_free(vm); +} + +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)) { + pr_info("Nested virtualization not supported, skipping nested test\n"); + return 0; + } + + pr_info("Running save+restore stress test with a nested guest...\n"); + run_test(/*nested=*/true); return 0; } From 583ad2052ddd6f84674b7122d9e39085d1781459 Mon Sep 17 00:00:00 2001 From: Yosry Ahmed Date: Tue, 28 Jul 2026 17:42:32 +0000 Subject: [PATCH 17/18] 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 5c247d08bc81 ("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 Link: https://patch.msgid.link/20260728174232.2423257-14-yosry@kernel.org Signed-off-by: Sean Christopherson --- .../selftests/kvm/include/x86/processor.h | 5 +++ .../kvm/x86/save_restore_pf_stress_test.c | 44 +++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h index 0b29cdec62eb..a645262e4767 100644 --- a/tools/testing/selftests/kvm/include/x86/processor.h +++ b/tools/testing/selftests/kvm/include/x86/processor.h @@ -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); diff --git a/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c index 7418e5fb879b..507391ab2c93 100644 --- a/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c +++ b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c @@ -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; } From b18ee21055a77eb1fb6070cb3f819cd3372c466e Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 10 Jul 2026 10:34:42 +0530 Subject: [PATCH 18/18] KVM: selftests: Add a test for KVM_CREATE_VM VM type enforcement KVM_CAP_VM_TYPES advertises the bitmap of VM types that KVM_CREATE_VM accepts, but nothing verified that the ioctl actually enforces it: that every advertised type can be created and every non-advertised type is rejected. sev_init2_tests carried a TODO for this ("check that unsupported types cannot be created. Probably a separate selftest"), but the check is not specific to SEV or KVM_SEV_INIT2, and not even to x86. Add a standalone test that walks the type space and, for each value, asserts that KVM_CREATE_VM succeeds iff the corresponding bit is set in KVM_CAP_VM_TYPES, and otherwise fails with -EINVAL. The walk extends past bit 31 so that out-of-range type values, which can never be advertised in the u32 bitmap, are also confirmed to be rejected. The test only depends on KVM_CAP_VM_TYPES, so it lives in the common set and is skipped on architectures that don't advertise the capability. Drop the now-addressed TODO from sev_init2_tests.c. Tested on an AMD SEV-SNP capable host. With KVM_CAP_VM_TYPES=0x15 (DEFAULT/SEV/SNP), only the advertised types are created and everything else is rejected: $ strace -e trace=ioctl ./vm_types_test 2>&1 | grep KVM_CREATE_VM ioctl(3, KVM_CREATE_VM, 0) = 4 # DEFAULT ioctl(3, KVM_CREATE_VM, 0x1) = -1 EINVAL # SW_PROTECTED ioctl(3, KVM_CREATE_VM, 0x2) = 4 # SEV ioctl(3, KVM_CREATE_VM, 0x3) = -1 EINVAL # SEV-ES ioctl(3, KVM_CREATE_VM, 0x4) = 4 # SNP ioctl(3, KVM_CREATE_VM, 0x5) = -1 EINVAL # TDX ... 0x6..0x3f all -1 EINVAL ... Reloading kvm_amd with sev_snp=0 drops the bitmap to 0x5 and only types 0 and 2 are then created, confirming the test tracks the advertised set rather than hard-coded types. Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260710050442.826777-1-hemanth.selam@gmail.com Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/Makefile.kvm | 1 + tools/testing/selftests/kvm/vm_types_test.c | 48 +++++++++++++++++++ .../selftests/kvm/x86/sev_init2_tests.c | 4 -- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tools/testing/selftests/kvm/vm_types_test.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index b5870d37533e..d23a3d29ce22 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -66,6 +66,7 @@ TEST_GEN_PROGS_COMMON += kvm_page_table_test TEST_GEN_PROGS_COMMON += set_memory_region_test TEST_GEN_PROGS_COMMON += memslot_modification_stress_test TEST_GEN_PROGS_COMMON += memslot_perf_test +TEST_GEN_PROGS_COMMON += vm_types_test # Compiled test targets TEST_GEN_PROGS_x86 = $(TEST_GEN_PROGS_COMMON) diff --git a/tools/testing/selftests/kvm/vm_types_test.c b/tools/testing/selftests/kvm/vm_types_test.c new file mode 100644 index 000000000000..6c421871e74b --- /dev/null +++ b/tools/testing/selftests/kvm/vm_types_test.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Verify that KVM_CREATE_VM accepts exactly the VM types enumerated by + * KVM_CAP_VM_TYPES, and rejects every other type with -EINVAL. + */ +#include +#include +#include + +#include + +#include "kvm_util.h" +#include "test_util.h" + +int main(void) +{ + unsigned long type, supported_types; + int kvm_fd; + + TEST_REQUIRE(kvm_has_cap(KVM_CAP_VM_TYPES)); + + kvm_fd = open_kvm_dev_path_or_exit(); + supported_types = kvm_check_cap(KVM_CAP_VM_TYPES); + pr_info("Supported VM types: 0x%lx\n", supported_types); + + /* + * For compatibility with 32-bit kernels, KVM_CHECK_EXTENSION restricts + * its return to 32-bit values, i.e. only types 0..31 can be advertised. + * Walk past that range as well to confirm that any out-of-range type is + * rejected rather than silently accepted (or truncated). + */ + for (type = 0; type < BITS_PER_TYPE(supported_types); type++) { + int fd = __kvm_ioctl(kvm_fd, KVM_CREATE_VM, (void *)type); + + if (supported_types & BIT(type)) { + TEST_ASSERT(fd >= 0, + "KVM_CREATE_VM(%lu) should succeed, supported types = 0x%lx", + type, supported_types); + kvm_close(fd); + } else { + TEST_ASSERT(fd < 0 && errno == EINVAL, + "KVM_CREATE_VM(%lu) should fail with EINVAL, supported types = 0x%lx", + type, supported_types); + } + } + + return 0; +} diff --git a/tools/testing/selftests/kvm/x86/sev_init2_tests.c b/tools/testing/selftests/kvm/x86/sev_init2_tests.c index 8db88c355f16..d4227dc922ab 100644 --- a/tools/testing/selftests/kvm/x86/sev_init2_tests.c +++ b/tools/testing/selftests/kvm/x86/sev_init2_tests.c @@ -77,10 +77,6 @@ void test_vm_types(void) { test_init2(KVM_X86_SEV_VM, &(struct kvm_sev_init){}); - /* - * TODO: check that unsupported types cannot be created. Probably - * a separate selftest. - */ if (have_sev_es) test_init2(KVM_X86_SEV_ES_VM, &(struct kvm_sev_init){});