[LTP] [PATCH 10/10] KVM: Add functional test for emulated VMREAD/VMWRITE instructions
Martin Doucha
mdoucha@suse.cz
Tue Jan 21 17:44:24 CET 2025
Signed-off-by: Martin Doucha <mdoucha@suse.cz>
---
testcases/kernel/kvm/kvm_vmx01.c | 282 +++++++++++++++++++++++++++++++
1 file changed, 282 insertions(+)
create mode 100644 testcases/kernel/kvm/kvm_vmx01.c
diff --git a/testcases/kernel/kvm/kvm_vmx01.c b/testcases/kernel/kvm/kvm_vmx01.c
new file mode 100644
index 000000000..c413b4148
--- /dev/null
+++ b/testcases/kernel/kvm/kvm_vmx01.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024 SUSE LLC <mdoucha@suse.cz>
+ */
+
+/*\
+ * Basic functional test for VMREAD/VMWRITE instructions in KVM environment.
+ * Verify that VMWRITE instruction changes the contents of current VMCS and
+ * the values written into shadow VMCS can be read in both parent and nested
+ * VM.
+ */
+
+#include "kvm_test.h"
+
+#ifdef COMPILE_PAYLOAD
+#if defined(__i386__) || defined(__x86_64__)
+
+#include "kvm_x86_vmx.h"
+
+#define GUEST_READ_ERROR 1
+#define GUEST_WRITE_ERROR 2
+#define SHADOW_DATA_LENGTH 37
+#define VMCS_FIELD(x) x, #x
+
+struct vmcs_field_table {
+ unsigned long field_id;
+ const char *name;
+ uint64_t value;
+};
+
+/* Data written into shadow VMCS by the parent VM and read by the nested VM */
+static struct vmcs_field_table host_data[SHADOW_DATA_LENGTH] = {
+ {VMCS_FIELD(VMX_VMCS_GUEST_ES), 0xe5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_CS), 0xc5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SS), 0x55},
+ {VMCS_FIELD(VMX_VMCS_GUEST_DS), 0xd5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_FS), 0xf5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_GS), 0x65},
+ {VMCS_FIELD(VMX_VMCS_GUEST_LDTR), 0x1d72},
+ {VMCS_FIELD(VMX_VMCS_GUEST_TR), 0x72},
+ {VMCS_FIELD(VMX_VMCS_HOST_ES), 0x5e},
+ {VMCS_FIELD(VMX_VMCS_HOST_CS), 0x5c},
+ {VMCS_FIELD(VMX_VMCS_HOST_SS), 0x55},
+ {VMCS_FIELD(VMX_VMCS_HOST_DS), 0x5d},
+ {VMCS_FIELD(VMX_VMCS_HOST_FS), 0x5f},
+ {VMCS_FIELD(VMX_VMCS_HOST_GS), 0x56},
+ {VMCS_FIELD(VMX_VMCS_HOST_TR), 0x27},
+ {VMCS_FIELD(VMX_VMCS_GUEST_ES_LIMIT), 0xe51},
+ {VMCS_FIELD(VMX_VMCS_GUEST_CS_LIMIT), 0xc51},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SS_LIMIT), 0x551},
+ {VMCS_FIELD(VMX_VMCS_GUEST_DS_LIMIT), 0xd51},
+ {VMCS_FIELD(VMX_VMCS_GUEST_FS_LIMIT), 0xf51},
+ {VMCS_FIELD(VMX_VMCS_GUEST_GS_LIMIT), 0x651},
+ {VMCS_FIELD(VMX_VMCS_GUEST_LDTR_LIMIT), 0x1d721},
+ {VMCS_FIELD(VMX_VMCS_GUEST_ES_ACCESS), 0xa0e5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_CS_ACCESS), 0xa0c5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SS_ACCESS), 0xa055},
+ {VMCS_FIELD(VMX_VMCS_GUEST_DS_ACCESS), 0xa0d5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_FS_ACCESS), 0xa0f5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_GS_ACCESS), 0xa065},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SYSENTER_CS), 0x65c},
+ {VMCS_FIELD(VMX_VMCS_HOST_SYSENTER_CS), 0x45c},
+ {VMCS_FIELD(VMX_VMCS_GUEST_ES_BASE), 0xe5b},
+ {VMCS_FIELD(VMX_VMCS_GUEST_CS_BASE), 0xc5b},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SS_BASE), 0x55b},
+ {VMCS_FIELD(VMX_VMCS_GUEST_DS_BASE), 0xd5b},
+ {VMCS_FIELD(VMX_VMCS_GUEST_FS_BASE), 0xf5b},
+ {VMCS_FIELD(VMX_VMCS_GUEST_GS_BASE), 0x65b},
+ {VMCS_FIELD(VMX_VMCS_GUEST_LDTR_BASE), 0x1d72b}
+};
+
+/* Data written into shadow VMCS by the nested VM and read by the parent VM */
+static struct vmcs_field_table guest_data[SHADOW_DATA_LENGTH] = {
+ {VMCS_FIELD(VMX_VMCS_GUEST_ES), 0x5e},
+ {VMCS_FIELD(VMX_VMCS_GUEST_CS), 0x5c},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SS), 0x55},
+ {VMCS_FIELD(VMX_VMCS_GUEST_DS), 0x5d},
+ {VMCS_FIELD(VMX_VMCS_GUEST_FS), 0x5f},
+ {VMCS_FIELD(VMX_VMCS_GUEST_GS), 0x56},
+ {VMCS_FIELD(VMX_VMCS_GUEST_LDTR), 0x721d},
+ {VMCS_FIELD(VMX_VMCS_GUEST_TR), 0x27},
+ {VMCS_FIELD(VMX_VMCS_HOST_ES), 0xe5},
+ {VMCS_FIELD(VMX_VMCS_HOST_CS), 0xc5},
+ {VMCS_FIELD(VMX_VMCS_HOST_SS), 0x55},
+ {VMCS_FIELD(VMX_VMCS_HOST_DS), 0xd5},
+ {VMCS_FIELD(VMX_VMCS_HOST_FS), 0xf5},
+ {VMCS_FIELD(VMX_VMCS_HOST_GS), 0x65},
+ {VMCS_FIELD(VMX_VMCS_HOST_TR), 0x72},
+ {VMCS_FIELD(VMX_VMCS_GUEST_ES_LIMIT), 0x1e5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_CS_LIMIT), 0x1c5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SS_LIMIT), 0x155},
+ {VMCS_FIELD(VMX_VMCS_GUEST_DS_LIMIT), 0x1d5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_FS_LIMIT), 0x1f5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_GS_LIMIT), 0x165},
+ {VMCS_FIELD(VMX_VMCS_GUEST_LDTR_LIMIT), 0x11d72},
+ {VMCS_FIELD(VMX_VMCS_GUEST_ES_ACCESS), 0xa05e},
+ {VMCS_FIELD(VMX_VMCS_GUEST_CS_ACCESS), 0xa05c},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SS_ACCESS), 0xa055},
+ {VMCS_FIELD(VMX_VMCS_GUEST_DS_ACCESS), 0xa05d},
+ {VMCS_FIELD(VMX_VMCS_GUEST_FS_ACCESS), 0xa05f},
+ {VMCS_FIELD(VMX_VMCS_GUEST_GS_ACCESS), 0xa056},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SYSENTER_CS), 0x5c6},
+ {VMCS_FIELD(VMX_VMCS_HOST_SYSENTER_CS), 0x5c4},
+ {VMCS_FIELD(VMX_VMCS_GUEST_ES_BASE), 0xbe5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_CS_BASE), 0xbc5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_SS_BASE), 0xb55},
+ {VMCS_FIELD(VMX_VMCS_GUEST_DS_BASE), 0xbd5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_FS_BASE), 0xbf5},
+ {VMCS_FIELD(VMX_VMCS_GUEST_GS_BASE), 0xb65},
+ {VMCS_FIELD(VMX_VMCS_GUEST_LDTR_BASE), 0xb1d72}
+};
+
+static uint64_t vmread_buffer[SHADOW_DATA_LENGTH];
+
+int guest_main(void)
+{
+ int i;
+
+ /* kvm_vmx_vmread() calls tst_brk(), don't use it in nested VM */
+ for (i = 0; i < SHADOW_DATA_LENGTH; i++) {
+ asm goto(
+ "vmread %1, (%0)\n"
+ "jna %l[read_error]\n"
+ "vmwrite %2, %3\n"
+ "jna %l[write_error]\n"
+ :
+ : "r" (&vmread_buffer[i]), "r" (host_data[i].field_id),
+ "r" (guest_data[i].value),
+ "r" (guest_data[i].field_id)
+ : "cc", "memory"
+ : read_error, write_error
+ );
+ }
+
+ return 0;
+
+read_error:
+ return GUEST_READ_ERROR;
+
+write_error:
+ return GUEST_WRITE_ERROR;
+}
+
+void main(void)
+{
+ struct kvm_vmx_vcpu *vcpu;
+ struct kvm_vmcs *shadow_vmcs;
+ char *vmcs_backup;
+ int i, errors;
+ uint64_t val;
+
+ kvm_set_vmx_state(1);
+
+ /* Check secondary VMCS execctl support */
+ if (kvm_rdmsr(MSR_IA32_VMX_BASIC) & IA32_VMXBASIC_USELESS_CTL_MASKS)
+ val = kvm_rdmsr(MSR_IA32_VMX_EXECCTL_MASK2);
+ else
+ val = kvm_rdmsr(MSR_IA32_VMX_EXECCTL_MASK);
+
+ if (!((val >> 32) & VMX_EXECCTL_ENABLE_CTL2))
+ tst_brk(TCONF, "CPU does not support shadow VMCS");
+
+ /* Create and configure guest VMCS */
+ shadow_vmcs = kvm_alloc_vmcs();
+ kvm_vmx_vmclear(shadow_vmcs);
+ shadow_vmcs->version |= VMX_SHADOW_VMCS;
+ vcpu = kvm_create_vmx_vcpu(guest_main, 1);
+ kvm_vmx_vmptrld(vcpu->vmcs);
+ val = kvm_vmx_vmread(VMX_VMCS_VMEXEC_CTL);
+ val |= VMX_EXECCTL_ENABLE_CTL2;
+ kvm_vmx_vmwrite(VMX_VMCS_VMEXEC_CTL, val);
+ val = kvm_rdmsr(MSR_IA32_VMX_EXECCTL2_MASK);
+
+ if (!((val >> 32) & VMX_EXECCTL2_SHADOW_VMCS))
+ tst_brk(TCONF, "CPU does not support shadow VMCS");
+
+ val = VMX_EXECCTL2_SHADOW_VMCS | (uint32_t)val;
+ kvm_vmx_vmwrite(VMX_VMCS_VMEXEC_CTL2, val);
+ kvm_vmx_vmwrite(VMX_VMCS_LINK_POINTER, (uintptr_t)shadow_vmcs);
+
+ /* Configure shadow VMCS */
+ vmcs_backup = tst_heap_alloc(sizeof(struct kvm_vmcs));
+ memcpy(vmcs_backup, shadow_vmcs, sizeof(struct kvm_vmcs));
+ kvm_vmx_vmptrld(shadow_vmcs);
+
+ for (i = 0; i < SHADOW_DATA_LENGTH; i++)
+ kvm_vmx_vmwrite(host_data[i].field_id, host_data[i].value);
+
+ /* Flush shadow VMCS just in case */
+ kvm_vmx_vmptrld(vcpu->vmcs);
+
+ if (!memcmp(vmcs_backup, shadow_vmcs, sizeof(struct kvm_vmcs)))
+ tst_res(TFAIL, "VMWRITE did not modify raw VMCS data");
+
+ /* Run nested VM */
+ memcpy(vmcs_backup, shadow_vmcs, sizeof(struct kvm_vmcs));
+ kvm_vmx_vmrun(vcpu);
+ val = kvm_vmx_vmread(VMX_VMCS_EXIT_REASON);
+
+ if (val != VMX_EXIT_HLT) {
+ tst_res(TFAIL, "Unexpected guest exit reason %llx", val);
+ return;
+ }
+
+ if (vcpu->regs.rax == GUEST_READ_ERROR) {
+ tst_res(TFAIL, "Guest failed to read shadow VMCS");
+ return;
+ }
+
+ if (vcpu->regs.rax == GUEST_WRITE_ERROR) {
+ tst_res(TFAIL, "Guest failed to write shadow VMCS");
+ return;
+ }
+
+ if (!memcmp(vmcs_backup, shadow_vmcs, sizeof(struct kvm_vmcs)))
+ tst_res(TFAIL, "Nested VMWRITE did not modify raw VMCS data");
+
+ /* Check values read by the nested VM from shadow VMCS */
+ for (i = 0, errors = 0; i < SHADOW_DATA_LENGTH; i++) {
+ if (vmread_buffer[i] == host_data[i].value)
+ continue;
+
+ errors++;
+ tst_res(TFAIL, "Shadow %s guest mismatch: %llx != %llx",
+ host_data[i].name, vmread_buffer[i],
+ host_data[i].value);
+ }
+
+ if (!errors)
+ tst_res(TPASS, "Guest read correct values from shadow VMCS");
+
+ /* Check values written by the nested VM to shadow VMCS */
+ kvm_vmx_vmptrld(shadow_vmcs);
+
+ for (i = 0, errors = 0; i < SHADOW_DATA_LENGTH; i++) {
+ val = kvm_vmx_vmread(guest_data[i].field_id);
+
+ if (val == guest_data[i].value)
+ continue;
+
+ errors++;
+ tst_res(TFAIL, "Shadow %s parent mismatch: %llx != %llx",
+ guest_data[i].name, val, guest_data[i].value);
+ }
+
+ if (!errors)
+ tst_res(TPASS, "Parent read correct values from shadow VMCS");
+}
+
+#else /* defined(__i386__) || defined(__x86_64__) */
+TST_TEST_TCONF("Test supported only on x86");
+#endif /* defined(__i386__) || defined(__x86_64__) */
+
+#else /* COMPILE_PAYLOAD */
+
+#include "tst_module.h"
+
+#define NESTED_INTEL_SYSFILE "/sys/module/kvm_intel/parameters/nested"
+
+static void setup(void)
+{
+ if (!tst_read_bool_sys_param(NESTED_INTEL_SYSFILE)) {
+ tst_module_reload("kvm_intel",
+ (char *const[]){"nested=1", NULL});
+ }
+
+ tst_kvm_setup();
+}
+
+static struct tst_test test = {
+ .test_all = tst_kvm_run,
+ .setup = setup,
+ .cleanup = tst_kvm_cleanup,
+ .needs_root = 1,
+ .supported_archs = (const char *const []) {
+ "x86_64",
+ "x86",
+ NULL
+ },
+};
+
+#endif /* COMPILE_PAYLOAD */
--
2.47.0
More information about the ltp
mailing list