(an interrupt, SMI, NMI), but lets L2 control either EFLAG.IF or GIF Signed-off-by: Maxim Levitsky <mlevitsk@xxxxxxxxxx> --- x86/svm.h | 11 +++ x86/svm_tests.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) diff --git a/x86/svm.h b/x86/svm.h index 58b9410..df1b1ac 100644 --- a/x86/svm.h +++ b/x86/svm.h @@ -426,6 +426,17 @@ void test_set_guest(test_guest_func func); extern struct vmcb *vmcb; extern struct svm_test svm_tests[]; +static inline void stgi(void) +{ + asm volatile ("stgi"); +} + +static inline void clgi(void) +{ + asm volatile ("clgi"); +} + + #define SAVE_GPR_C \ "xchg %%rbx, regs+0x8\n\t" \ diff --git a/x86/svm_tests.c b/x86/svm_tests.c index b2ba283..ef8b5ee 100644 --- a/x86/svm_tests.c +++ b/x86/svm_tests.c @@ -3312,6 +3312,195 @@ static void svm_lbrv_nested_test2(void) check_lbr(&host_branch4_from, &host_branch4_to); } + +// test that a nested guest which does enable INTR interception +// but doesn't enable virtual interrupt masking works + +static volatile int dummy_isr_recevied; +static void dummy_isr(isr_regs_t *regs) +{ + dummy_isr_recevied++; + eoi(); +} + + +static volatile int nmi_recevied; +static void dummy_nmi_handler(struct ex_regs *regs) +{ + nmi_recevied++; +} + + +static void svm_intr_intercept_mix_run_guest(volatile int *counter, int expected_vmexit) +{ + if (counter) + *counter = 0; + + sti(); // host IF value should not matter + clgi(); // vmrun will set back GI to 1 + + svm_vmrun(); + + if (counter) + report(!*counter, "No interrupt expected"); + + stgi(); + + if (counter) + report(*counter == 1, "Interrupt is expected"); + + report (vmcb->control.exit_code == expected_vmexit, "Test expected VM exit"); + report(vmcb->save.rflags & X86_EFLAGS_IF, "Guest should have EFLAGS.IF set now"); + cli(); +} + + +// subtest: test that enabling EFLAGS.IF is enought to trigger an interrupt +static void svm_intr_intercept_mix_if_guest(struct svm_test *test) +{ + asm volatile("nop;nop;nop;nop"); + report(!dummy_isr_recevied, "No interrupt expected"); + sti(); + asm volatile("nop"); + report(0, "must not reach here"); +} + +static void svm_intr_intercept_mix_if(void) +{ + // make a physical interrupt to be pending + handle_irq(0x55, dummy_isr); + + vmcb->control.intercept |= (1 << INTERCEPT_INTR); + vmcb->control.int_ctl &= ~V_INTR_MASKING_MASK; + vmcb->save.rflags &= ~X86_EFLAGS_IF; + + test_set_guest(svm_intr_intercept_mix_if_guest); + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | 0x55, 0); + svm_intr_intercept_mix_run_guest(&dummy_isr_recevied, SVM_EXIT_INTR); +} + + +// subtest: test that a clever guest can trigger an interrupt by setting GIF +// if GIF is not intercepted +static void svm_intr_intercept_mix_gif_guest(struct svm_test *test) +{ + + asm volatile("nop;nop;nop;nop"); + report(!dummy_isr_recevied, "No interrupt expected"); + + // clear GIF and enable IF + // that should still not cause VM exit + clgi(); + sti(); + asm volatile("nop"); + report(!dummy_isr_recevied, "No interrupt expected"); + + stgi(); + asm volatile("nop"); + report(0, "must not reach here"); +} + +static void svm_intr_intercept_mix_gif(void) +{ + handle_irq(0x55, dummy_isr); + + vmcb->control.intercept |= (1 << INTERCEPT_INTR); + vmcb->control.int_ctl &= ~V_INTR_MASKING_MASK; + vmcb->save.rflags &= ~X86_EFLAGS_IF; + + test_set_guest(svm_intr_intercept_mix_gif_guest); + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | 0x55, 0); + svm_intr_intercept_mix_run_guest(&dummy_isr_recevied, SVM_EXIT_INTR); +} + + + +// subtest: test that a clever guest can trigger an interrupt by setting GIF +// if GIF is not intercepted and interrupt comes after guest +// started running +static void svm_intr_intercept_mix_gif_guest2(struct svm_test *test) +{ + asm volatile("nop;nop;nop;nop"); + report(!dummy_isr_recevied, "No interrupt expected"); + + clgi(); + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | 0x55, 0); + report(!dummy_isr_recevied, "No interrupt expected"); + + stgi(); + asm volatile("nop"); + report(0, "must not reach here"); +} + +static void svm_intr_intercept_mix_gif2(void) +{ + handle_irq(0x55, dummy_isr); + + vmcb->control.intercept |= (1 << INTERCEPT_INTR); + vmcb->control.int_ctl &= ~V_INTR_MASKING_MASK; + vmcb->save.rflags |= X86_EFLAGS_IF; + + test_set_guest(svm_intr_intercept_mix_gif_guest2); + svm_intr_intercept_mix_run_guest(&dummy_isr_recevied, SVM_EXIT_INTR); +} + + +// subtest: test that pending NMI will be handled when guest enables GIF +static void svm_intr_intercept_mix_nmi_guest(struct svm_test *test) +{ + asm volatile("nop;nop;nop;nop"); + report(!nmi_recevied, "No NMI expected"); + cli(); // should have no effect + + clgi(); + asm volatile("nop"); + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_NMI, 0); + sti(); // should have no effect + asm volatile("nop"); + report(!nmi_recevied, "No NMI expected"); + + stgi(); + asm volatile("nop"); + report(0, "must not reach here"); +} + +static void svm_intr_intercept_mix_nmi(void) +{ + handle_exception(2, dummy_nmi_handler); + + vmcb->control.intercept |= (1 << INTERCEPT_NMI); + vmcb->control.int_ctl &= ~V_INTR_MASKING_MASK; + vmcb->save.rflags |= X86_EFLAGS_IF; + + test_set_guest(svm_intr_intercept_mix_nmi_guest); + svm_intr_intercept_mix_run_guest(&nmi_recevied, SVM_EXIT_NMI); +} + +// test that pending SMI will be handled when guest enables GIF +// TODO: can't really count #SMIs so just test that guest doesn't hang +// and VMexits on SMI +static void svm_intr_intercept_mix_smi_guest(struct svm_test *test) +{ + asm volatile("nop;nop;nop;nop"); + + clgi(); + asm volatile("nop"); + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_SMI, 0); + sti(); // should have no effect + asm volatile("nop"); + stgi(); + asm volatile("nop"); + report(0, "must not reach here"); +} + +static void svm_intr_intercept_mix_smi(void) +{ + vmcb->control.intercept |= (1 << INTERCEPT_SMI); + vmcb->control.int_ctl &= ~V_INTR_MASKING_MASK; + test_set_guest(svm_intr_intercept_mix_smi_guest); + svm_intr_intercept_mix_run_guest(NULL, SVM_EXIT_SMI); +} + struct svm_test svm_tests[] = { { "null", default_supported, default_prepare, default_prepare_gif_clear, null_test, @@ -3439,5 +3628,10 @@ struct svm_test svm_tests[] = { TEST(svm_lbrv_test2), TEST(svm_lbrv_nested_test1), TEST(svm_lbrv_nested_test2), + TEST(svm_intr_intercept_mix_if), + TEST(svm_intr_intercept_mix_gif), + TEST(svm_intr_intercept_mix_gif2), + TEST(svm_intr_intercept_mix_nmi), + TEST(svm_intr_intercept_mix_smi), { NULL, NULL, NULL, NULL, NULL, NULL, NULL } }; -- 2.26.3