Test various NMI-window exiting scenarios. In the active activity state, test without any blocking, with blocking by MOV-SS, no blocking with event injection, and with blocking by NMI. In the halted activity state, test without any blocking, with and without event injection. Signed-off-by: Jim Mattson <jmattson@xxxxxxxxxx> Reviewed-by: Peter Shier <pshier@xxxxxxxxxx> --- lib/x86/desc.h | 9 +++ x86/unittests.cfg | 6 ++ x86/vmx_tests.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) diff --git a/lib/x86/desc.h b/lib/x86/desc.h index 977a233..7a7358a 100644 --- a/lib/x86/desc.h +++ b/lib/x86/desc.h @@ -224,4 +224,13 @@ void __set_exception_jmpbuf(jmp_buf *addr); #define set_exception_jmpbuf(jmpbuf) \ (setjmp(jmpbuf) ? : (__set_exception_jmpbuf(&(jmpbuf)), 0)) +static inline void *get_idt_addr(idt_entry_t *entry) +{ + uintptr_t addr = entry->offset0 | ((u32)entry->offset1 << 16); +#ifdef __x86_64__ + addr |= (u64)entry->offset2 << 32; +#endif + return (void *)addr; +} + #endif diff --git a/x86/unittests.cfg b/x86/unittests.cfg index 3b21a85..04faa6e 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -578,6 +578,12 @@ extra_params = -cpu host,+vmx -m 2560 -append vmx_db_test arch = x86_64 groups = vmx +[vmx_nmi_window_test] +file = vmx.flat +extra_params = -cpu host,+vmx -m 2560 -append vmx_nmi_window_test +arch = x86_64 +groups = vmx + [vmx_eoi_bitmap_ioapic_scan] file = vmx.flat smp = 2 diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index b105b23..ec4f051 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -5031,6 +5031,142 @@ static void vmx_pending_event_hlt_test(void) vmx_pending_event_test_core(true); } +static int vmx_window_test_ud_count; + +static void vmx_window_test_ud_handler(struct ex_regs *regs) +{ + vmx_window_test_ud_count++; +} + +static void vmx_nmi_window_test_guest(void) +{ + handle_exception(UD_VECTOR, vmx_window_test_ud_handler); + + asm volatile("vmcall\n\t" + "nop\n\t"); + + handle_exception(UD_VECTOR, NULL); +} + +static void verify_nmi_window_exit(u64 rip) +{ + u32 exit_reason = vmcs_read(EXI_REASON); + + report("Exit reason (%d) is 'NMI window'", + exit_reason == VMX_NMI_WINDOW, exit_reason); + report("RIP (%#lx) is %#lx", vmcs_read(GUEST_RIP) == rip, + vmcs_read(GUEST_RIP), rip); + report("Activity state (%ld) is 'ACTIVE'", + vmcs_read(GUEST_ACTV_STATE) == ACTV_ACTIVE, + vmcs_read(GUEST_ACTV_STATE)); +} + +static void vmx_nmi_window_test(void) +{ + u64 nop_addr; + void *ud_fault_addr = get_idt_addr(&boot_idt[UD_VECTOR]); + + if (!(ctrl_pin_rev.clr & PIN_VIRT_NMI)) { + report_skip("CPU does not support the \"Virtual NMIs\" VM-execution control."); + return; + } + + if (!(ctrl_cpu_rev[0].clr & CPU_NMI_WINDOW)) { + report_skip("CPU does not support the \"NMI-window exiting\" VM-execution control."); + return; + } + + vmx_window_test_ud_count = 0; + + report_prefix_push("NMI-window"); + test_set_guest(vmx_nmi_window_test_guest); + vmcs_set_bits(PIN_CONTROLS, PIN_VIRT_NMI); + enter_guest(); + skip_exit_vmcall(); + nop_addr = vmcs_read(GUEST_RIP); + + /* + * Ask for "NMI-window exiting," and expect an immediate VM-exit. + * RIP will not advance. + */ + report_prefix_push("active, no blocking"); + vmcs_set_bits(CPU_EXEC_CTRL0, CPU_NMI_WINDOW); + enter_guest(); + verify_nmi_window_exit(nop_addr); + report_prefix_pop(); + + /* + * Ask for "NMI-window exiting" in a MOV-SS shadow, and expect + * a VM-exit on the next instruction after the nop. (The nop + * is one byte.) + */ + report_prefix_push("active, blocking by MOV-SS"); + vmcs_write(GUEST_INTR_STATE, GUEST_INTR_STATE_MOVSS); + enter_guest(); + verify_nmi_window_exit(nop_addr + 1); + report_prefix_pop(); + + /* + * Ask for "NMI-window exiting" (with event injection), and + * expect a VM-exit after the event is injected. (RIP should + * be at the address specified in the IDT entry for #UD.) + */ + report_prefix_push("active, no blocking, injecting #UD"); + vmcs_write(ENT_INTR_INFO, + INTR_INFO_VALID_MASK | INTR_TYPE_HARD_EXCEPTION | UD_VECTOR); + enter_guest(); + verify_nmi_window_exit((u64)ud_fault_addr); + report_prefix_pop(); + + /* + * Ask for "NMI-window exiting" with NMI blocking, and expect + * a VM-exit after the next IRET (i.e. after the #UD handler + * returns). So, RIP should be back at one byte past the nop. + */ + report_prefix_push("active, blocking by NMI"); + vmcs_write(GUEST_INTR_STATE, GUEST_INTR_STATE_NMI); + enter_guest(); + verify_nmi_window_exit(nop_addr + 1); + report("#UD handler executed once (actual %d times)", + vmx_window_test_ud_count == 1, + vmx_window_test_ud_count); + report_prefix_pop(); + + if (!(rdmsr(MSR_IA32_VMX_MISC) & (1 << 6))) { + report_skip("CPU does not support activity state HLT."); + } else { + /* + * Ask for "NMI-window exiting" when entering activity + * state HLT, and expect an immediate VM-exit. RIP is + * still one byte past the nop. + */ + report_prefix_push("halted, no blocking"); + vmcs_write(GUEST_ACTV_STATE, ACTV_HLT); + enter_guest(); + verify_nmi_window_exit(nop_addr + 1); + report_prefix_pop(); + + /* + * Ask for "NMI-window exiting" when entering activity + * state HLT (with event injection), and expect a + * VM-exit after the event is injected. (RIP should be + * at the address specified in the IDT entry for #UD.) + */ + report_prefix_push("halted, no blocking, injecting #UD"); + vmcs_write(GUEST_ACTV_STATE, ACTV_HLT); + vmcs_write(ENT_INTR_INFO, + INTR_INFO_VALID_MASK | INTR_TYPE_HARD_EXCEPTION | + UD_VECTOR); + enter_guest(); + verify_nmi_window_exit((u64)ud_fault_addr); + report_prefix_pop(); + } + + vmcs_clear_bits(CPU_EXEC_CTRL0, CPU_NMI_WINDOW); + enter_guest(); + report_prefix_pop(); +} + static void vmx_db_test_guest(void) { /* @@ -5833,6 +5969,7 @@ struct vmx_test vmx_tests[] = { TEST(vmx_cr_load_test), TEST(vmx_nm_test), TEST(vmx_db_test), + TEST(vmx_nmi_window_test), TEST(vmx_pending_event_test), TEST(vmx_pending_event_hlt_test), /* EPT access tests. */ -- 2.19.1.1215.g8438c0b245-goog