The test iterates over every possible valid VMCS field and verifies that: - If VMWRITE/VMREAD bitmaps intercept both VMREAD & VMWRITE, then VMREAD & VMWRITE to this field is intercepted. - If VMWRITE/VMREAD bitmaps intercept only VMREAD, then VMREAD of this field is intercepted and VMWRITE is pass-throughed and written value written correctly to shadow VMCS. - If VMWRITE/VMREAD bitmaps intercept only VMWRITE, then VMWRITE of this field is intercepted and VMREAD is pass-throughed and value is read correctly from shadow VMCS. - If VMWRITE/VMREAD bitmaps neither intercept VMWRITE nor VMREAD of this field, then both VMREAD & VMWRITE of this field is pass-through and value is written and read correctly from shadow VMCS. - If field is pass-through for VMREAD/VMWRITE and field is not supported by physical CPU, then VMREAD/VMWRITE to it from guest should be intercepted with VMX_INST_ERROR equal to VMXERR_UNSUPPORTED_VMCS_COMPONENT. Above tests also verify that RFLAGS is updated correctly after VMREAD/VMWRITE simulation/pass-through. In addition, these tests are run again with shadow_vmcs==-1ull because this is a special valid case in which VMREAD/VMWRITE to shadowed-fields should fail with RFLAGS.CF being set. In addition, these tests are also run against non-valid VMCS fields (Fields having one bit set in bit-range 15-64. Each iteration with a single bit set on a different position) and verify that all VMREAD/VMWRITE to these fields are intercepted. Signed-off-by: Liran Alon <liran.alon@xxxxxxxxxx> Signed-off-by: Jim Mattson <jmattson@xxxxxxxxxx> --- x86/unittests.cfg | 7 ++ x86/vmx_tests.c | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 317 insertions(+) diff --git a/x86/unittests.cfg b/x86/unittests.cfg index a61df879990e..46f3a34e5e2e 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -571,6 +571,13 @@ extra_params = -cpu host,+vmx -m 2048 -append vmx_apic_passthrough_thread_test arch = x86_64 groups = vmx +[vmx_vmcs_shadow_test] +file = vmx.flat +smp = 2 +extra_params = -cpu host,+vmx -m 2048 -append vmx_vmcs_shadow_test +arch = x86_64 +groups = vmx + [debug] file = debug.flat arch = x86_64 diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 932759701314..53140dbf0b92 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -4206,6 +4206,314 @@ static void vmx_apic_passthrough_thread_test(void) vmx_apic_passthrough(true); } +enum vmcs_access { + ACCESS_VMREAD, + ACCESS_VMWRITE, + ACCESS_NONE, +}; + +struct vmcs_shadow_test_common { + enum vmcs_access op; + enum Reason reason; + u64 field; + u64 value; + u64 flags; + u64 time; +} l1_l2_common; + +static inline u64 vmread_flags(u64 field, u64 *val) +{ + u64 flags; + + asm volatile ("vmread %2, %1; pushf; pop %0" + : "=r" (flags), "=rm" (*val) : "r" (field) : "cc"); + return flags & X86_EFLAGS_ALU; +} + +static inline u64 vmwrite_flags(u64 field, u64 val) +{ + u64 flags; + + asm volatile ("vmwrite %1, %2; pushf; pop %0" + : "=r"(flags) : "rm" (val), "r" (field) : "cc"); + return flags & X86_EFLAGS_ALU; +} + +static void vmx_vmcs_shadow_test_guest(void) +{ + struct vmcs_shadow_test_common *c = &l1_l2_common; + u64 start; + + while (c->op != ACCESS_NONE) { + start = rdtsc(); + switch (c->op) { + default: + c->flags = -1ull; + break; + case ACCESS_VMREAD: + c->flags = vmread_flags(c->field, &c->value); + break; + case ACCESS_VMWRITE: + c->flags = vmwrite_flags(c->field, 0); + break; + } + c->time = rdtsc() - start; + vmcall(); + } +} + +static u64 vmread_from_shadow(u64 field) +{ + struct vmcs *primary; + struct vmcs *shadow; + u64 value; + + TEST_ASSERT(!vmcs_save(&primary)); + shadow = (struct vmcs *)vmcs_read(VMCS_LINK_PTR); + TEST_ASSERT(!make_vmcs_current(shadow)); + value = vmcs_read(field); + TEST_ASSERT(!make_vmcs_current(primary)); + return value; +} + +static u64 vmwrite_to_shadow(u64 field, u64 value) +{ + struct vmcs *primary; + struct vmcs *shadow; + + TEST_ASSERT(!vmcs_save(&primary)); + shadow = (struct vmcs *)vmcs_read(VMCS_LINK_PTR); + TEST_ASSERT(!make_vmcs_current(shadow)); + vmcs_write(field, value); + value = vmcs_read(field); + TEST_ASSERT(!make_vmcs_current(primary)); + return value; +} + +static void vmcs_shadow_test_access(u8 *bitmap[2], enum vmcs_access access) +{ + struct vmcs_shadow_test_common *c = &l1_l2_common; + + c->op = access; + vmcs_write(VMX_INST_ERROR, 0); + enter_guest(); + c->reason = vmcs_read(EXI_REASON) & 0xffff; + if (c->reason != VMX_VMCALL) { + skip_exit_insn(); + enter_guest(); + } + skip_exit_vmcall(); +} + +static void vmcs_shadow_test_field(u8 *bitmap[2], u64 field) +{ + struct vmcs_shadow_test_common *c = &l1_l2_common; + struct vmcs *shadow; + u64 value; + uintptr_t flags[2]; + bool good_shadow; + u32 vmx_inst_error; + + report_prefix_pushf("field %lx", field); + c->field = field; + + shadow = (struct vmcs *)vmcs_read(VMCS_LINK_PTR); + if (shadow != (struct vmcs *)-1ull) { + flags[ACCESS_VMREAD] = vmread_flags(field, &value); + flags[ACCESS_VMWRITE] = vmwrite_flags(field, value); + good_shadow = !flags[ACCESS_VMREAD] && !flags[ACCESS_VMWRITE]; + } else { + /* + * When VMCS link pointer is -1ull, VMWRITE/VMREAD on + * shadowed-fields should fail with setting RFLAGS.CF. + */ + flags[ACCESS_VMREAD] = X86_EFLAGS_CF; + flags[ACCESS_VMWRITE] = X86_EFLAGS_CF; + good_shadow = false; + } + + /* Intercept both VMREAD and VMWRITE. */ + report_prefix_push("no VMREAD/VMWRITE permission"); + /* VMWRITE/VMREAD done on reserved-bit should always intercept */ + if (!(field >> VMCS_FIELD_RESERVED_SHIFT)) { + set_bit(field, bitmap[ACCESS_VMREAD]); + set_bit(field, bitmap[ACCESS_VMWRITE]); + } + vmcs_shadow_test_access(bitmap, ACCESS_VMWRITE); + report("not shadowed for VMWRITE", c->reason == VMX_VMWRITE); + vmcs_shadow_test_access(bitmap, ACCESS_VMREAD); + report("not shadowed for VMREAD", c->reason == VMX_VMREAD); + report_prefix_pop(); + + if (field >> VMCS_FIELD_RESERVED_SHIFT) + goto out; + + /* Permit shadowed VMREAD. */ + report_prefix_push("VMREAD permission only"); + clear_bit(field, bitmap[ACCESS_VMREAD]); + set_bit(field, bitmap[ACCESS_VMWRITE]); + if (good_shadow) + value = vmwrite_to_shadow(field, MAGIC_VAL_1 + field); + vmcs_shadow_test_access(bitmap, ACCESS_VMWRITE); + report("not shadowed for VMWRITE", c->reason == VMX_VMWRITE); + vmcs_shadow_test_access(bitmap, ACCESS_VMREAD); + vmx_inst_error = vmcs_read(VMX_INST_ERROR); + report("shadowed for VMREAD (in %ld cycles)", c->reason == VMX_VMCALL, + c->time); + report("ALU flags after VMREAD (%lx) are as expected (%lx)", + c->flags == flags[ACCESS_VMREAD], + c->flags, flags[ACCESS_VMREAD]); + if (good_shadow) + report("value read from shadow (%lx) is as expected (%lx)", + c->value == value, c->value, value); + else if (shadow != (struct vmcs *)-1ull && flags[ACCESS_VMREAD]) + report("VMX_INST_ERROR (%d) is as expected (%d)", + vmx_inst_error == VMXERR_UNSUPPORTED_VMCS_COMPONENT, + vmx_inst_error, VMXERR_UNSUPPORTED_VMCS_COMPONENT); + report_prefix_pop(); + + /* Permit shadowed VMWRITE. */ + report_prefix_push("VMWRITE permission only"); + set_bit(field, bitmap[ACCESS_VMREAD]); + clear_bit(field, bitmap[ACCESS_VMWRITE]); + if (good_shadow) + vmwrite_to_shadow(field, MAGIC_VAL_1 + field); + vmcs_shadow_test_access(bitmap, ACCESS_VMWRITE); + vmx_inst_error = vmcs_read(VMX_INST_ERROR); + report("shadowed for VMWRITE (in %ld cycles)", c->reason == VMX_VMCALL, + c->time); + report("ALU flags after VMWRITE (%lx) are as expected (%lx)", + c->flags == flags[ACCESS_VMREAD], + c->flags, flags[ACCESS_VMREAD]); + if (good_shadow) { + value = vmread_from_shadow(field); + report("shadow VMCS value (%lx) is as expected (%lx)", + value == 0, value, 0ul); + } else if (shadow != (struct vmcs *)-1ull && flags[ACCESS_VMWRITE]) { + report("VMX_INST_ERROR (%d) is as expected (%d)", + vmx_inst_error == VMXERR_UNSUPPORTED_VMCS_COMPONENT, + vmx_inst_error, VMXERR_UNSUPPORTED_VMCS_COMPONENT); + } + vmcs_shadow_test_access(bitmap, ACCESS_VMREAD); + report("not shadowed for VMREAD", c->reason == VMX_VMREAD); + report_prefix_pop(); + + /* Permit shadowed VMREAD and VMWRITE. */ + report_prefix_push("VMREAD and VMWRITE permission"); + clear_bit(field, bitmap[ACCESS_VMREAD]); + clear_bit(field, bitmap[ACCESS_VMWRITE]); + if (good_shadow) + vmwrite_to_shadow(field, MAGIC_VAL_1 + field); + vmcs_shadow_test_access(bitmap, ACCESS_VMWRITE); + vmx_inst_error = vmcs_read(VMX_INST_ERROR); + report("shadowed for VMWRITE (in %ld cycles)", c->reason == VMX_VMCALL, + c->time); + report("ALU flags after VMWRITE (%lx) are as expected (%lx)", + c->flags == flags[ACCESS_VMREAD], + c->flags, flags[ACCESS_VMREAD]); + if (good_shadow) { + value = vmread_from_shadow(field); + report("shadow VMCS value (%lx) is as expected (%lx)", + value == 0, value, 0ul); + } else if (shadow != (struct vmcs *)-1ull && flags[ACCESS_VMWRITE]) { + report("VMX_INST_ERROR (%d) is as expected (%d)", + vmx_inst_error == VMXERR_UNSUPPORTED_VMCS_COMPONENT, + vmx_inst_error, VMXERR_UNSUPPORTED_VMCS_COMPONENT); + } + vmcs_shadow_test_access(bitmap, ACCESS_VMREAD); + vmx_inst_error = vmcs_read(VMX_INST_ERROR); + report("shadowed for VMREAD (in %ld cycles)", c->reason == VMX_VMCALL, + c->time); + report("ALU flags after VMREAD (%lx) are as expected (%lx)", + c->flags == flags[ACCESS_VMREAD], + c->flags, flags[ACCESS_VMREAD]); + if (good_shadow) + report("value read from shadow (%lx) is as expected (%lx)", + c->value == 0, c->value, 0ul); + else if (shadow != (struct vmcs *)-1ull && flags[ACCESS_VMREAD]) + report("VMX_INST_ERROR (%d) is as expected (%d)", + vmx_inst_error == VMXERR_UNSUPPORTED_VMCS_COMPONENT, + vmx_inst_error, VMXERR_UNSUPPORTED_VMCS_COMPONENT); + report_prefix_pop(); + +out: + report_prefix_pop(); +} + +static void vmx_vmcs_shadow_test_body(u8 *bitmap[2]) +{ + unsigned base; + unsigned index; + unsigned bit; + unsigned highest_index = rdmsr(MSR_IA32_VMX_VMCS_ENUM); + + /* Run test on all possible valid VMCS fields */ + for (base = 0; + base < (1 << VMCS_FIELD_RESERVED_SHIFT); + base += (1 << VMCS_FIELD_TYPE_SHIFT)) + for (index = 0; index <= highest_index; index++) + vmcs_shadow_test_field(bitmap, base + index); + + /* + * Run tests on some invalid VMCS fields + * (Have reserved bit set). + */ + for (bit = VMCS_FIELD_RESERVED_SHIFT; bit < VMCS_FIELD_BIT_SIZE; bit++) + vmcs_shadow_test_field(bitmap, (1ull << bit)); +} + +static void vmx_vmcs_shadow_test(void) +{ + u8 *bitmap[2]; + struct vmcs *shadow; + + if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY)) { + printf("\t'Activate secondary controls' not supported.\n"); + return; + } + + if (!(ctrl_cpu_rev[1].clr & CPU_SHADOW_VMCS)) { + printf("\t'VMCS shadowing' not supported.\n"); + return; + } + + if (!(rdmsr(MSR_IA32_VMX_MISC) & + MSR_IA32_VMX_MISC_VMWRITE_SHADOW_RO_FIELDS)) { + printf("\tVMWRITE can't modify VM-exit information fields.\n"); + return; + } + + test_set_guest(vmx_vmcs_shadow_test_guest); + + bitmap[ACCESS_VMREAD] = alloc_page(); + bitmap[ACCESS_VMWRITE] = alloc_page(); + + vmcs_write(VMREAD_BITMAP, virt_to_phys(bitmap[ACCESS_VMREAD])); + vmcs_write(VMWRITE_BITMAP, virt_to_phys(bitmap[ACCESS_VMWRITE])); + + shadow = alloc_page(); + shadow->hdr.revision_id = basic.revision; + shadow->hdr.shadow_vmcs = 1; + TEST_ASSERT(!vmcs_clear(shadow)); + + vmcs_clear_bits(CPU_EXEC_CTRL0, CPU_RDTSC); + vmcs_set_bits(CPU_EXEC_CTRL0, CPU_SECONDARY); + vmcs_set_bits(CPU_EXEC_CTRL1, CPU_SHADOW_VMCS); + + vmcs_write(VMCS_LINK_PTR, virt_to_phys(shadow)); + report_prefix_push("valid link pointer"); + vmx_vmcs_shadow_test_body(bitmap); + report_prefix_pop(); + + vmcs_write(VMCS_LINK_PTR, -1ull); + report_prefix_push("invalid link pointer"); + vmx_vmcs_shadow_test_body(bitmap); + report_prefix_pop(); + + l1_l2_common.op = ACCESS_NONE; + enter_guest(); +} + #define TEST(name) { #name, .v2 = name } /* name/init/guest_main/exit_handler/syscall_handler/guest_regs */ @@ -4256,6 +4564,8 @@ struct vmx_test vmx_tests[] = { /* APIC pass-through tests */ TEST(vmx_apic_passthrough_test), TEST(vmx_apic_passthrough_thread_test), + /* VMCS Shadowing tests */ + TEST(vmx_vmcs_shadow_test), /* Regression tests */ TEST(vmx_cr_load_test), /* EPT access tests. */ -- 1.9.1