From: Zack Rusin <zack.rusin@broadcom.com>
To: Sean Christopherson <seanjc@google.com>,
Paolo Bonzini <pbonzini@redhat.com>,
kvm@vger.kernel.org
Cc: "Doug Covelli" <doug.covelli@broadcom.com>,
"Thomas Gleixner" <tglx@kernel.org>,
"Ingo Molnar" <mingo@redhat.com>,
"Borislav Petkov" <bp@alien8.de>,
"Dave Hansen" <dave.hansen@linux.intel.com>,
x86@kernel.org, "H. Peter Anvin" <hpa@zytor.com>,
"Jonathan Corbet" <corbet@lwn.net>,
"Randy Dunlap" <rdunlap@infradead.org>,
"Shuah Khan" <shuah@kernel.org>,
"Vitaly Kuznetsov" <vkuznets@redhat.com>,
"David Woodhouse" <dwmw2@infradead.org>,
"Paul Durrant" <paul@xen.org>, "Xin Li" <xin@zytor.com>,
"Francesco Lavra" <flavra@baylibre.com>,
"Radim Krčmář" <rkrcmar@ventanamicro.com>,
"Liran Alon" <liran.alon@oracle.com>,
"Arbel Moshe" <arbel.moshe@oracle.com>,
linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org,
bcm-kernel-feedback-list@broadcom.com,
linux-kernel@vger.kernel.org,
"Zack Rusin" <zack.rusin@broadcom.com>
Subject: [PATCH v3 12/12] KVM: selftests: Test nested VMware backdoor routing
Date: Wed, 11 Nov 2026 01:00:01 -0500 [thread overview]
Message-ID: <60dbf049b84e8d48da4b4c6138e759d8e2cfb92b.1794374897.git.zack.rusin@broadcom.com> (raw)
In-Reply-To: <cover.1794374897.git.zack.rusin@broadcom.com>
Add vmware_nested_backdoor_test. With KVM_VMWARE_ENABLE_BACKDOOR and
KVM_VMWARE_ENABLE_NESTED_BACKDOOR enabled, run L2 under an L1 that
intercepts #GP, and verify that a VMware backdoor access from CPL3 in
L2 is handled by L0 and exits to userspace. Verify that an access with
the wrong magic value, one to the first port above the two backdoor
ports, and one from CPL2 still cause a #GP exit to L1, as does a valid
access without the nested flag. With only the nested flag, expect L0
to handle the access only if enable_vmware_backdoor is set.
This is the situation the flag exists for: L1 receives the #GP for the
backdoor access but can't service it, so only the narrow set of
backdoor accesses may be taken away from L1. Exercise VMX and SVM
separately, because KVM routes nested exceptions to L1 through
different paths on the two vendors.
Signed-off-by: Zack Rusin <zack.rusin@broadcom.com>
---
MAINTAINERS | 3 +-
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../kvm/x86/vmware_nested_backdoor_test.c | 223 ++++++++++++++++++
3 files changed, 225 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/kvm/x86/vmware_nested_backdoor_test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 38a735c9221e..3118792a22df 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15050,8 +15050,7 @@ S: Supported
T: git git://git.kernel.org/pub/scm/virt/kvm/kvm.git
T: git https://github.com/kvm-x86/linux.git
F: arch/x86/kvm/vmware.*
-F: tools/testing/selftests/kvm/x86/vmware_backdoor_test.c
-F: tools/testing/selftests/kvm/x86/vmware_hypercall_test.c
+F: tools/testing/selftests/kvm/x86/vmware_*.c
KVM X86 Xen (KVM/Xen)
M: David Woodhouse <dwmw2@infradead.org>
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index fe7ba905facd..6dd1f336da99 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -142,6 +142,7 @@ TEST_GEN_PROGS_x86 += x86/userspace_io_test
TEST_GEN_PROGS_x86 += x86/userspace_msr_exit_test
TEST_GEN_PROGS_x86 += x86/vmware_backdoor_test
TEST_GEN_PROGS_x86 += x86/vmware_hypercall_test
+TEST_GEN_PROGS_x86 += x86/vmware_nested_backdoor_test
TEST_GEN_PROGS_x86 += x86/vmx_apic_access_test
TEST_GEN_PROGS_x86 += x86/vmx_apicv_updates_test
TEST_GEN_PROGS_x86 += x86/vmx_exception_with_invalid_guest_state
diff --git a/tools/testing/selftests/kvm/x86/vmware_nested_backdoor_test.c b/tools/testing/selftests/kvm/x86/vmware_nested_backdoor_test.c
new file mode 100644
index 000000000000..3d12be7906e0
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/vmware_nested_backdoor_test.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025-2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ */
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "svm_util.h"
+#include "test_util.h"
+#include "vmx.h"
+
+#define VMWARE_BACKDOOR_PORT 0x5658
+#define VMWARE_MAGIC 0x564d5868
+#define VMPORT_CMD_GETVERSION 0x0a
+
+#define NESTED_BACKDOOR_FLAGS (KVM_VMWARE_ENABLE_BACKDOOR | \
+ KVM_VMWARE_ENABLE_NESTED_BACKDOOR)
+
+struct nested_backdoor_case {
+ u64 flags;
+ u64 rax;
+ u64 rdx;
+ u32 cpl;
+ bool expect_l0;
+};
+
+static struct nested_backdoor_case guest_case;
+
+static void guest_done_handler(struct ex_regs *regs)
+{
+ GUEST_DONE();
+}
+
+static void l2_guest_code(void)
+{
+ unsigned long rax = guest_case.rax;
+ unsigned long rbx = 0;
+ unsigned long rcx = VMPORT_CMD_GETVERSION;
+ unsigned long rdx = guest_case.rdx;
+
+ asm volatile("inl %%dx, %%eax"
+ : "+a"(rax), "+b"(rbx), "+c"(rcx), "+d"(rdx));
+ GUEST_ASSERT(guest_case.expect_l0);
+ asm volatile("int3");
+}
+
+static void l1_svm_code(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ generic_svm_setup(svm, l2_guest_code);
+ vmcb->control.intercept_exceptions |= BIT_ULL(GP_VECTOR);
+
+ vmcb->save.cpl = guest_case.cpl;
+ vmcb->save.cs.selector = USER_CS_SELECTOR | guest_case.cpl;
+ vmcb->save.cs.attrib &= ~SVM_SELECTOR_DPL_MASK;
+ vmcb->save.cs.attrib |= guest_case.cpl << SVM_SELECTOR_DPL_SHIFT;
+ vmcb->save.ss.selector = USER_DS_SELECTOR | guest_case.cpl;
+ vmcb->save.ss.attrib &= ~SVM_SELECTOR_DPL_MASK;
+ vmcb->save.ss.attrib |= guest_case.cpl << SVM_SELECTOR_DPL_SHIFT;
+ vmcb->save.ds.selector = USER_DS_SELECTOR | guest_case.cpl;
+ vmcb->save.ds.attrib &= ~SVM_SELECTOR_DPL_MASK;
+ vmcb->save.ds.attrib |= guest_case.cpl << SVM_SELECTOR_DPL_SHIFT;
+ vmcb->save.es.selector = USER_DS_SELECTOR | guest_case.cpl;
+ vmcb->save.es.attrib &= ~SVM_SELECTOR_DPL_MASK;
+ vmcb->save.es.attrib |= guest_case.cpl << SVM_SELECTOR_DPL_SHIFT;
+
+ run_guest(vmcb, svm->vmcb_gpa);
+ if (!guest_case.expect_l0) {
+ GUEST_ASSERT_EQ(vmcb->control.exit_code,
+ SVM_EXIT_EXCP_BASE + GP_VECTOR);
+ GUEST_DONE();
+ }
+ GUEST_FAIL("L1 received nested SVM exit 0x%lx",
+ vmcb->control.exit_code);
+}
+
+static void l1_vmx_code(struct vmx_pages *vmx)
+{
+ prepare_for_vmx_operation(vmx);
+ load_vmcs(vmx);
+ prepare_vmcs(vmx, l2_guest_code);
+
+ vmwrite(EXCEPTION_BITMAP, BIT_ULL(GP_VECTOR));
+ vmwrite(GUEST_CS_SELECTOR, USER_CS_SELECTOR | guest_case.cpl);
+ vmwrite(GUEST_CS_AR_BYTES, 0xa09b | guest_case.cpl << 5);
+ vmwrite(GUEST_SS_SELECTOR, USER_DS_SELECTOR | guest_case.cpl);
+ vmwrite(GUEST_SS_AR_BYTES, 0xc093 | guest_case.cpl << 5);
+ vmwrite(GUEST_DS_SELECTOR, USER_DS_SELECTOR | guest_case.cpl);
+ vmwrite(GUEST_DS_AR_BYTES, 0xc093 | guest_case.cpl << 5);
+ vmwrite(GUEST_ES_SELECTOR, USER_DS_SELECTOR | guest_case.cpl);
+ vmwrite(GUEST_ES_AR_BYTES, 0xc093 | guest_case.cpl << 5);
+
+ vmlaunch();
+ if (!guest_case.expect_l0) {
+ GUEST_ASSERT_EQ(vmread(VM_EXIT_REASON) & 0xffff,
+ EXIT_REASON_EXCEPTION_NMI);
+ GUEST_ASSERT_EQ(vmread(VM_EXIT_INTR_INFO) & 0xff,
+ GP_VECTOR);
+ GUEST_DONE();
+ }
+ GUEST_FAIL("L1 received nested VMX exit 0x%lx",
+ vmread(VM_EXIT_REASON));
+}
+
+static void __attribute__((__flatten__)) 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 setup_user_environment(struct kvm_vm *vm)
+{
+ struct idt_entry *idt = addr_gva2hva(vm, vm->arch.idt);
+
+ idt[BP_VECTOR].dpl = 3;
+ vm_install_exception_handler(vm, BP_VECTOR, guest_done_handler);
+ vm_init_user_mode(vm);
+}
+
+static void handle_vmware_port(struct kvm_vcpu *vcpu)
+{
+ struct kvm_run *run = vcpu->run;
+ struct kvm_regs regs;
+ u32 *data;
+
+ TEST_ASSERT_EQ(run->io.direction, KVM_EXIT_IO_IN);
+ TEST_ASSERT_EQ(run->io.size, 4);
+ TEST_ASSERT_EQ(run->io.count, 1);
+ TEST_ASSERT_EQ(run->io.port, VMWARE_BACKDOOR_PORT);
+
+ data = (void *)run + run->io.data_offset;
+ *data = 0;
+
+ vcpu_regs_get(vcpu, ®s);
+ regs.rbx = VMWARE_MAGIC;
+ vcpu_regs_set(vcpu, ®s);
+}
+
+static void run_test(const struct nested_backdoor_case *test_case)
+{
+ gva_t nested_test_data_gva;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ int port_exits = 0;
+
+ vm = vm_create(1);
+ guest_case = *test_case;
+ sync_global_to_guest(vm, guest_case);
+ vm_enable_cap(vm, KVM_CAP_X86_VMWARE, test_case->flags);
+ vcpu = vm_vcpu_add(vm, 0, l1_guest_code);
+
+ if (kvm_cpu_has(X86_FEATURE_SVM))
+ vcpu_alloc_svm(vm, &nested_test_data_gva);
+ else
+ vcpu_alloc_vmx(vm, &nested_test_data_gva);
+ vcpu_args_set(vcpu, 1, nested_test_data_gva);
+ setup_user_environment(vm);
+
+ for (;;) {
+ struct kvm_run *run = vcpu->run;
+ struct ucall uc;
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+ if (run->io.port == VMWARE_BACKDOOR_PORT) {
+ handle_vmware_port(vcpu);
+ port_exits++;
+ continue;
+ }
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_DONE:
+ TEST_ASSERT_EQ(port_exits, test_case->expect_l0);
+ kvm_vm_free(vm);
+ return;
+ default:
+ TEST_FAIL("Unexpected I/O exit on port 0x%x (ucall 0x%lx)",
+ run->io.port, uc.cmd);
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ bool module_default = get_kvm_param_bool("enable_vmware_backdoor");
+ const struct nested_backdoor_case test_cases[] = {
+ { NESTED_BACKDOOR_FLAGS,
+ VMWARE_MAGIC, VMWARE_BACKDOOR_PORT, 3, true },
+ { NESTED_BACKDOOR_FLAGS,
+ 0, VMWARE_BACKDOOR_PORT, 3, false },
+ { NESTED_BACKDOOR_FLAGS,
+ VMWARE_MAGIC, VMWARE_BACKDOOR_PORT + 2, 3, false },
+ { NESTED_BACKDOOR_FLAGS,
+ VMWARE_MAGIC, VMWARE_BACKDOOR_PORT, 2, false },
+ /* L1 keeps the #GP without the nested flag... */
+ { KVM_VMWARE_ENABLE_BACKDOOR,
+ VMWARE_MAGIC, VMWARE_BACKDOOR_PORT, 3, false },
+ /* ...and the nested flag needs the backdoor to be enabled. */
+ { KVM_VMWARE_ENABLE_NESTED_BACKDOOR,
+ VMWARE_MAGIC, VMWARE_BACKDOOR_PORT, 3, module_default },
+ };
+ unsigned int supported;
+ int i;
+
+ supported = kvm_check_cap(KVM_CAP_X86_VMWARE);
+ TEST_REQUIRE((supported & NESTED_BACKDOOR_FLAGS) ==
+ NESTED_BACKDOOR_FLAGS);
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM) ||
+ kvm_cpu_has(X86_FEATURE_VMX));
+
+ for (i = 0; i < ARRAY_SIZE(test_cases); i++)
+ run_test(&test_cases[i]);
+
+ return 0;
+}
--
2.53.0
prev parent reply other threads:[~2026-11-11 6:00 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 5:59 [PATCH v3 00/12] KVM: Improve VMware guest support Zack Rusin
2026-11-11 5:59 ` [PATCH v3 01/12] KVM: x86/pmu: Honor enable_vmware_backdoor for VMware pseudo-PMCs Zack Rusin
2026-11-11 5:59 ` [PATCH v3 02/12] KVM: x86: Move VMware backdoor support to separate files Zack Rusin
2026-11-11 5:59 ` [PATCH v3 03/12] KVM: x86: Encapsulate VMware backdoor checks Zack Rusin
2026-11-11 5:59 ` [PATCH v3 04/12] KVM: x86: Add CONFIG_KVM_VMWARE Zack Rusin
2026-11-11 5:59 ` [PATCH v3 05/12] KVM: x86: Add a unified VMware capability Zack Rusin
2026-11-11 5:59 ` [PATCH v3 06/12] KVM: x86: Forward VMware hypercalls to userspace Zack Rusin
2026-11-11 5:59 ` [PATCH v3 07/12] KVM: x86: Route nested VMware backdoors to KVM Zack Rusin
2026-11-11 5:59 ` [PATCH v3 08/12] KVM: selftests: Add helpers to run x86 guest code in user mode Zack Rusin
2026-11-11 5:59 ` [PATCH v3 09/12] KVM: selftests: Port VMware backdoor coverage Zack Rusin
2026-11-11 5:59 ` [PATCH v3 10/12] KVM: selftests: Test VMware hypercall exits Zack Rusin
2026-11-11 6:00 ` [PATCH v3 11/12] KVM: selftests: Test VMware hypercall exits with nested Hyper-V flushes Zack Rusin
2026-11-11 6:00 ` Zack Rusin [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=60dbf049b84e8d48da4b4c6138e759d8e2cfb92b.1794374897.git.zack.rusin@broadcom.com \
--to=zack.rusin@broadcom.com \
--cc=arbel.moshe@oracle.com \
--cc=bcm-kernel-feedback-list@broadcom.com \
--cc=bp@alien8.de \
--cc=corbet@lwn.net \
--cc=dave.hansen@linux.intel.com \
--cc=doug.covelli@broadcom.com \
--cc=dwmw2@infradead.org \
--cc=flavra@baylibre.com \
--cc=hpa@zytor.com \
--cc=kvm@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=liran.alon@oracle.com \
--cc=mingo@redhat.com \
--cc=paul@xen.org \
--cc=pbonzini@redhat.com \
--cc=rdunlap@infradead.org \
--cc=rkrcmar@ventanamicro.com \
--cc=seanjc@google.com \
--cc=shuah@kernel.org \
--cc=tglx@kernel.org \
--cc=vkuznets@redhat.com \
--cc=x86@kernel.org \
--cc=xin@zytor.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®