[PATCH v3 08/12] KVM: selftests: Add helpers to run x86 guest code in user mode

From: Zack Rusin

Date: Wed Nov 11 2026 - 01:08:20 EST


Add vm_init_user_mode() and vcpu_enter_user_mode(). The former installs
flat 64-bit user code and data segments, makes the guest memory mapped
so far accessible to user mode, and gives the TSS a kernel stack and no
I/O permission bitmap; the latter switches a vCPU to CPL3.

The selftest infrastructure only sets up CPL0, but testing the VMware
backdoor requires guest code at CPL3, where the I/O permission checks
and CR4.PCE apply. Provide the setup once instead of open coding it in
each test.

Signed-off-by: Zack Rusin <zack.rusin@xxxxxxxxxxxx>
---
.../selftests/kvm/include/x86/processor.h | 7 ++
.../testing/selftests/kvm/lib/x86/processor.c | 87 +++++++++++++++++++
2 files changed, 94 insertions(+)

diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
index 3e4351ccb359..f7fd4aafccae 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -1310,6 +1310,13 @@ void vm_install_exception_handler(struct kvm_vm *vm, int vector,

gva_t vm_alloc_stack(struct kvm_vm *vm, int nr_pages);

+/* Flat 64-bit code and data segments for user mode, see vm_init_user_mode(). */
+#define USER_CS_SELECTOR 0x28
+#define USER_DS_SELECTOR 0x30
+
+void vm_init_user_mode(struct kvm_vm *vm);
+void vcpu_enter_user_mode(struct kvm_vcpu *vcpu);
+
/*
* Exception fixup morphs #DE to an arbitrary magic vector so that '0' can be
* used to signal "no expcetion".
diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c
index ee8a1715d844..6099d386abe9 100644
--- a/tools/testing/selftests/kvm/lib/x86/processor.c
+++ b/tools/testing/selftests/kvm/lib/x86/processor.c
@@ -768,6 +768,93 @@ void vm_install_exception_handler(struct kvm_vm *vm, int vector,
handlers[vector] = (gva_t)handler;
}

+struct tss64 {
+ u32 reserved0;
+ u64 rsp0;
+ u64 rsp1;
+ u64 rsp2;
+ u64 reserved1;
+ u64 ist[7];
+ u64 reserved2;
+ u16 reserved3;
+ u16 iomap_base;
+} __packed;
+
+static void kvm_seg_set_user_code_64bit(struct kvm_segment *segp)
+{
+ kvm_seg_set_kernel_code_64bit(segp);
+ segp->selector = USER_CS_SELECTOR | 3;
+ segp->dpl = 3;
+}
+
+static void kvm_seg_set_user_data_64bit(struct kvm_segment *segp)
+{
+ kvm_seg_set_kernel_data_64bit(segp);
+ segp->selector = USER_DS_SELECTOR | 3;
+ segp->dpl = 3;
+}
+
+static void virt_set_user_pte(struct kvm_vm *vm, gva_t gva)
+{
+ struct kvm_mmu *mmu = &vm->mmu;
+ u64 *pte = &mmu->pgd;
+ int level;
+
+ for (level = mmu->pgtable_levels; level >= PG_LEVEL_4K; level--) {
+ pte = virt_get_pte(vm, mmu, pte, gva, level);
+ TEST_ASSERT(is_present_pte(mmu, pte),
+ "PTE not PRESENT at level %d for gva: 0x%lx",
+ level, gva);
+ *pte |= PTE_USER_MASK(mmu);
+ if (is_huge_pte(mmu, pte))
+ break;
+ }
+}
+
+/*
+ * Prepare @vm for running guest code at CPL3: install flat user-mode code and
+ * data segments, make all memory that is currently mapped accessible to user
+ * mode, and give the TSS a kernel stack so that exceptions from user mode can
+ * be delivered. The TSS has no I/O permission bitmap, i.e. user mode can't
+ * access I/O ports unless IOPL allows it. Call this after allocating all
+ * memory, vCPUs included, that user mode needs. All vCPUs share the kernel
+ * stack.
+ */
+void vm_init_user_mode(struct kvm_vm *vm)
+{
+ struct tss64 *tss = addr_gva2hva(vm, vm->arch.tss);
+ sparsebit_idx_t begin, end, page;
+ struct kvm_segment seg;
+
+ kvm_seg_set_user_code_64bit(&seg);
+ kvm_seg_fill_gdt_64bit(vm, &seg);
+ kvm_seg_set_user_data_64bit(&seg);
+ kvm_seg_fill_gdt_64bit(vm, &seg);
+
+ tss->rsp0 = vm_alloc_stack(vm, 1);
+ tss->iomap_base = sizeof(*tss);
+
+ sparsebit_for_each_set_range(vm->vpages_mapped, begin, end) {
+ for (page = begin; page <= end; page++)
+ virt_set_user_pte(vm, page << vm->page_shift);
+ }
+}
+
+/* Run @vcpu at CPL3 using the segments installed by vm_init_user_mode(). */
+void vcpu_enter_user_mode(struct kvm_vcpu *vcpu)
+{
+ struct kvm_sregs sregs;
+
+ vcpu_sregs_get(vcpu, &sregs);
+ kvm_seg_set_user_code_64bit(&sregs.cs);
+ kvm_seg_set_user_data_64bit(&sregs.ds);
+ sregs.es = sregs.ds;
+ sregs.fs = sregs.ds;
+ sregs.gs = sregs.ds;
+ sregs.ss = sregs.ds;
+ vcpu_sregs_set(vcpu, &sregs);
+}
+
void assert_on_unhandled_exception(struct kvm_vcpu *vcpu)
{
struct ucall uc;
--
2.53.0