Re: [kvm-unit-tests PATCH v2] x86: Add test for nested VM entry prereqs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Jun 22, 2018 at 4:48 PM Krish Sadhukhan
<krish.sadhukhan@xxxxxxxxxx> wrote:
>
>
>
> On 06/20/2018 05:30 PM, Marc Orr wrote:
> > This patch adds a test for the prereq checks done as a part of a nested
> > VM launch related to event injection.
> >
> > Signed-off-by: Marc Orr <marcorr@xxxxxxxxxx>
> > ---
> > Changelog since v1:
> > - Add positive test case for interrupt type != INTR_TYPE_RESERVED
> > - Renamed nr to vector
> > - Add positive test case for interrupt type == INTR_TYPE_NMI_INTR
> > - Explicitly encode 0 vecotr (i.e., DE_VECTOR)
> > - Updated deliver errorcode checks to test postive cases
> > - Moved test for INTR_TYPE_OTHER_EVENT after test for INTR_TYPE_RESERVED
> >
> >   lib/x86/processor.h |   8 ++
> >   x86/types.h         |   1 +
> >   x86/vmx.h           |  27 ++++++
> >   x86/vmx_tests.c     | 231 ++++++++++++++++++++++++++++++++++++++++++++
> >   4 files changed, 267 insertions(+)
> >
> > diff --git a/lib/x86/processor.h b/lib/x86/processor.h
> > index 247386207bb0..886170cfa163 100644
> > --- a/lib/x86/processor.h
> > +++ b/lib/x86/processor.h
> > @@ -15,6 +15,14 @@
> >   #  define S "4"
> >   #endif
> >
> > +#define DF_VECTOR 8
> > +#define TS_VECTOR 10
> > +#define NP_VECTOR 11
> > +#define SS_VECTOR 12
> > +#define GP_VECTOR 13
> > +#define PF_VECTOR 14
> > +#define AC_VECTOR 17
> > +
> >   #define X86_CR0_PE     0x00000001
> >   #define X86_CR0_MP     0x00000002
> >   #define X86_CR0_TS     0x00000008
> > diff --git a/x86/types.h b/x86/types.h
> > index fd22743990f1..047556e854d6 100644
> > --- a/x86/types.h
> > +++ b/x86/types.h
> > @@ -3,6 +3,7 @@
> >
> >   #define DE_VECTOR 0
> >   #define DB_VECTOR 1
> > +#define NMI_VECTOR 2
> >   #define BP_VECTOR 3
> >   #define OF_VECTOR 4
> >   #define BR_VECTOR 5
> > diff --git a/x86/vmx.h b/x86/vmx.h
> > index bdcaac0edc01..116bbd515beb 100644
> > --- a/x86/vmx.h
> > +++ b/x86/vmx.h
> > @@ -420,6 +420,15 @@ enum Intr_type {
> >
> >   #define INTR_INFO_INTR_TYPE_SHIFT       8
> >
> > +#define INTR_TYPE_EXT_INTR              (0 << 8) /* external interrupt */
> > +#define INTR_TYPE_RESERVED              (1 << 8) /* reserved */
> > +#define INTR_TYPE_NMI_INTR           (2 << 8) /* NMI */
> > +#define INTR_TYPE_HARD_EXCEPTION     (3 << 8) /* processor exception */
> > +#define INTR_TYPE_SOFT_INTR             (4 << 8) /* software interrupt */
> > +#define INTR_TYPE_PRIV_SW_EXCEPTION  (5 << 8) /* priv. software exception */
> > +#define INTR_TYPE_SOFT_EXCEPTION     (6 << 8) /* software exception */
> > +#define INTR_TYPE_OTHER_EVENT           (7 << 8) /* other event */
> > +
> >   /*
> >    * VM-instruction error numbers
> >    */
> > @@ -712,6 +721,24 @@ static inline bool invvpid(unsigned long type, u64 vpid, u64 gla)
> >       return ret;
> >   }
> >
> > +static inline int enable_unrestricted_guest(void)
> > +{
> > +     if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY))
> > +             return -1;
> > +
> > +     if (!(ctrl_cpu_rev[1].clr & CPU_URG))
> > +             return -1;
> > +
> > +     vmcs_write(CPU_EXEC_CTRL0, vmcs_read(CPU_EXEC_CTRL0) | CPU_SECONDARY);
> > +     vmcs_write(CPU_EXEC_CTRL1, vmcs_read(CPU_EXEC_CTRL1) | CPU_URG);
> > +     return 0;
> > +}
> > +
> > +static inline void disable_unrestricted_guest(void)
> > +{
> > +     vmcs_write(CPU_EXEC_CTRL1, vmcs_read(CPU_EXEC_CTRL1) & ~CPU_URG);
> > +}
> > +
> >   const char *exit_reason_description(u64 reason);
> >   void print_vmexit_info();
> >   void print_vmentry_failure_info(struct vmentry_failure *failure);
> > diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
> > index 0c1a6952f9ea..98f95df1316b 100644
> > --- a/x86/vmx_tests.c
> > +++ b/x86/vmx_tests.c
> > @@ -3561,6 +3561,236 @@ static void try_tpr_threshold_and_vtpr(unsigned threshold, unsigned vtpr)
> >       report_prefix_pop();
> >   }
> >
> > +static void test_invalid_event_injection(void)
> > +{
> > +     u32 ent_intr_info_save = vmcs_read(ENT_INTR_INFO);
> > +     u32 ent_intr_error_save = vmcs_read(ENT_INTR_ERROR);
> > +     u32 ent_inst_len_save = vmcs_read(ENT_INST_LEN);
> > +     u32 primary_save = vmcs_read(CPU_EXEC_CTRL0);
> > +     u32 secondary_save = vmcs_read(CPU_EXEC_CTRL1);
> > +     u64 guest_cr0_save = vmcs_read(GUEST_CR0);
> > +     u32 ent_intr_info_base = INTR_INFO_VALID_MASK;
> > +     u32 ent_intr_info, ent_intr_err, ent_intr_len;
> > +     u32 cnt;
> > +
> > +     /* Setup */
> > +     report_prefix_push("invalid event injection");
> > +     vmcs_write(ENT_INTR_ERROR, 0x00000000);
> > +     vmcs_write(ENT_INST_LEN, 0x00000001);
> > +
> > +     /* The field’s interruption type is not set to a reserved value. */
> > +     ent_intr_info = ent_intr_info_base | INTR_TYPE_RESERVED | DE_VECTOR;
> This test is all about checking that the interruption type is not a
> reserved value. Is  DE_VECTOR relevant here ?

No, it's not related. The vector is arbitrarily encoded to be 0 (i.e.,
DE_VECTOR). I've made that encoding explicit in this version of the
patch. If you have a preference, let me know which you prefer---v1,
which implicitly encodes DE_VECTOR, or v2 which explicitly encodes
DE_VECTOR. I have no preference.

> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "RESERVED interruption type invalid",
> > +                         ent_intr_info);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +     test_vmx_controls(false, false);
> > +     report_prefix_pop();
> > +
> Don't we want to just remove INTR_TYPE_RESERVED and run the test again
> for positive test case ?

It's arbitrary. Removing INTR_TYPE_RESERVED encodes leaves the
interruption type as 0, which is INTR_TYPE_EXT_INTR. But I'll go ahead
and change it.

> > +     ent_intr_info = ent_intr_info_base | INTR_TYPE_HARD_EXCEPTION |
> > +                     DE_VECTOR;
> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "RESERVED interruption type invalid",
> > +                         ent_intr_info);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +     test_vmx_controls(true, false);
> > +     report_prefix_pop();
> > +
> > +     /* If the interruption type is other event, the vector is 0. */
> > +     ent_intr_info = ent_intr_info_base | INTR_TYPE_OTHER_EVENT | DB_VECTOR;
> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "(OTHER EVENT && vector != 0) invalid",
> > +                         ent_intr_info);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +     test_vmx_controls(false, false);
> > +     report_prefix_pop();
> It's more readable if you place this test right after the one for
> INTR_TYPE_RESERVED, because the SDM describes them together.

The previous test case is supposed to be for the positive test case,
where the interruption type is not INTR_TYPE_RESERVED. I think we
should leave the negative and positive test case adjacent to each
other. But let me know if you strongly disagree, and I'll move this
one above.

> > +
> > +     /* If the interruption type is NMI, the vector is 2 (negative case). */
> > +     ent_intr_info = ent_intr_info_base | INTR_TYPE_NMI_INTR | DE_VECTOR;
> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "(NMI && vector != 2) invalid", ent_intr_info);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +     test_vmx_controls(false, false);
> > +     report_prefix_pop();
> > +
> > +     /* If the interruption type is NMI, the vector is 2 (positive case). */
> > +     ent_intr_info = ent_intr_info_base | INTR_TYPE_NMI_INTR | NMI_VECTOR;
> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "(NMI && vector == 2) valid", ent_intr_info);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +     test_vmx_controls(true, false);
> > +     report_prefix_pop();
> > +
> > +     /*
> > +      * If the interruption type
> > +      * is HW exception, the vector is at most 31.
> > +      */
> > +     ent_intr_info = ent_intr_info_base | INTR_TYPE_HARD_EXCEPTION | 0x20;
> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "(HW exception && vector > 31) invalid",
> > +                         ent_intr_info);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +     test_vmx_controls(false, false);
> > +     report_prefix_pop();
> > +
> > +     /*
> > +      * deliver-error-code is 1 iff either
> > +      * (a) the "unrestricted guest" VM-execution control is 0
> > +      * (b) CR0.PE is set.
> > +      */
> > +     ent_intr_info = ent_intr_info_base | INTR_INFO_DELIVER_CODE_MASK |
> > +                     INTR_TYPE_HARD_EXCEPTION | GP_VECTOR;
> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "error code <-> (!URG || prot_mode)",
> > +                         ent_intr_info);
> > +     enable_unrestricted_guest();
> > +     vmcs_write(GUEST_CR0, guest_cr0_save & ~X86_CR0_PE & ~X86_CR0_PG);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +     test_vmx_controls(false, false);
> > +     report_prefix_pop();
> May be you can add another test with the same conditions except
> unrestricted guest being disabled ?

Will do.

> > +
> > +     ent_intr_info = ent_intr_info_base | INTR_TYPE_HARD_EXCEPTION |
> > +                     GP_VECTOR;
> You are missing INTR_INFO_DELIVER_CODE_MASK. Or are you purposely
> turning that bit off ? If you are purposely turning the bit off, the
> test should pass. The SDM says that if INTR_INFO_DELIVER_CODE_MASK is
> set, certain conditions need to be fulfilled. But if it's not set,
> there's nothing to test for failure.

"But if it's not set, there's nothing to test for failure." Your
interpretation is not correct (I had the same mis-interpretation when
I first reviewed the text in the SDM). When the SDM says "If and only
if" that specifies both the negative and positive case. In other
words: error code <-> (!URG || prot_mode). Thus, if !(!URG ||
prot_mode), then the deliver error code bit MUST be clear.

> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "error code <-> (!URG || prot_mode)",
> > +                         ent_intr_info);
> > +     vmcs_write(GUEST_CR0, guest_cr0_save | X86_CR0_PE);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +     test_vmx_controls(false, false);
> > +     report_prefix_pop();
> > +
> > +     /* deliver-error-code is 1 iff the interruption type is HW exception */
> > +     report_prefix_push("error code <-> HW exception");
> > +     for (cnt = 0; cnt < 8; cnt++) {
> > +             u32 exception_type_mask = cnt << 8;
> > +             u32 deliver_error_code_mask =
> > +                     exception_type_mask != INTR_TYPE_HARD_EXCEPTION ?
> > +                     INTR_INFO_DELIVER_CODE_MASK : 0;
> > +
> > +             ent_intr_info = ent_intr_info_base | deliver_error_code_mask |
> > +                             exception_type_mask | GP_VECTOR;
> > +             report_prefix_pushf("VM-entry intr info=0x%x", ent_intr_info);
> > +             vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +             test_vmx_controls(false, false);
> > +             report_prefix_pop();
> > +     }
> > +     report_prefix_pop();
> > +
> > +     /*
> > +      * deliver-error-code is 1 iff the the vector
> > +      * indicates an exception that would normally deliver an error code
> > +      */
> > +     report_prefix_push("error code <-> vector delivers error code");
> > +     for (cnt = 0; cnt < 32; cnt++) {
> > +             bool has_error_code = false;
> > +             u32 deliver_error_code_mask;
> > +
> > +             switch (cnt) {
> > +             case DF_VECTOR:
> > +             case TS_VECTOR:
> > +             case NP_VECTOR:
> > +             case SS_VECTOR:
> > +             case GP_VECTOR:
> > +             case PF_VECTOR:
> > +             case AC_VECTOR:
> > +                     has_error_code = true;
> > +             }
> > +
> > +             /* Negative case */
> > +             deliver_error_code_mask = has_error_code ?
> > +                                             0 :
> > +                                             INTR_INFO_DELIVER_CODE_MASK;
> > +             ent_intr_info = ent_intr_info_base | deliver_error_code_mask |
> > +                             INTR_TYPE_HARD_EXCEPTION | cnt;
> > +             report_prefix_pushf("VM-entry intr info=0x%x", ent_intr_info);
> > +             vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +             test_vmx_controls(false, false);
> > +             report_prefix_pop();
> > +
> > +             /* Positive case */
> > +             deliver_error_code_mask = has_error_code ?
> > +                                             INTR_INFO_DELIVER_CODE_MASK :
> > +                                             0;
> > +             ent_intr_info = ent_intr_info_base | deliver_error_code_mask |
> > +                             INTR_TYPE_HARD_EXCEPTION | cnt;
> > +             report_prefix_pushf("VM-entry intr info=0x%x", ent_intr_info);
> > +             vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +             test_vmx_controls(true, false);
> > +             report_prefix_pop();
> > +     }
> > +     report_prefix_pop();
> Do we need the extra call to report_prefix_pop() at the end of each loop ?

Yes, it's paired with a report_prefix_push to distinguish the exact
value of the VM-entry interruption information field. This makes the
log much easier to parse. For example:
PASS: invalid event injection: error code <-> vector delivers error
code: VM-entry intr info=0x80000b00: vmlaunch fails
PASS: invalid event injection: error code <-> vector delivers error
code: VM-entry intr info=0x80000b00: VMX inst error is 7 (actual 7)
PASS: invalid event injection: error code <-> vector delivers error
code: VM-entry intr info=0x80000300: vmlaunch succeeds
PASS: invalid event injection: error code <-> vector delivers error
code: VM-entry intr info=0x80000b01: vmlaunch fails
PASS: invalid event injection: error code <-> vector delivers error
code: VM-entry intr info=0x80000b01: VMX inst error is 7 (actual 7)
PASS: invalid event injection: error code <-> vector delivers error
code: VM-entry intr info=0x80000301: vmlaunch succeeds

> > +
> > +     /* Reserved bits in the field (30:12) are 0. */
> > +     report_prefix_push("reserved bits clear");
> > +     for (cnt = 12; cnt <= 30; cnt++) {
> > +             ent_intr_info = ent_intr_info_base |
> > +                             INTR_INFO_DELIVER_CODE_MASK |
> > +                             INTR_TYPE_HARD_EXCEPTION | GP_VECTOR |
> > +                             (1U << cnt);
> Do we need to set INTR_INFO_DELIVER_CODE_MASK, INTR_TYPE_HARD_EXCEPTION
> and GP_VECTOR here ? All we are testing here are bits [30:12].

We need an VM-entry information interruption field that delivers an
error code. I chose this one because I knew that it meets the
conditions to deliver an error code.

> > +             report_prefix_pushf("VM-entry intr info=0x%x", ent_intr_info);
> > +             vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +             test_vmx_controls(false, false);
> > +             report_prefix_pop();
> > +     }
> > +     report_prefix_pop();
> > +
> > +     /*
> > +      * If deliver-error-code is 1
> > +      * bits 31:15 of the VM-entry exception error-code field are 0.
> > +      */
> > +     ent_intr_info = ent_intr_info_base | INTR_INFO_DELIVER_CODE_MASK |
> > +                     INTR_TYPE_HARD_EXCEPTION | GP_VECTOR;
> Again, do we need INTR_TYPE_HARD_EXCEPTION and GP_VECTOR here ?

Same response as above.

> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "VM-entry exception error code[31:15] clear",
> > +                         ent_intr_info);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +     for (cnt = 15; cnt <= 31; cnt++) {
> > +             ent_intr_err = 1U << cnt;
> > +             report_prefix_pushf("VM-entry intr error=0x%x", ent_intr_err);
> > +             vmcs_write(ENT_INTR_ERROR, ent_intr_err);
> > +             test_vmx_controls(false, false);
> > +             report_prefix_pop();
> > +     }
> > +     vmcs_write(ENT_INTR_ERROR, 0x00000000);
> > +     report_prefix_pop();
> > +
> > +     /*
> > +      * If the interruption type is software interrupt, software exception,
> > +      * or privileged software exception, the VM-entry instruction-length
> > +      * field is in the range 0–15.
> > +      */
> > +
> > +     ent_intr_info = ent_intr_info_base | INTR_TYPE_SOFT_EXCEPTION;
> > +     report_prefix_pushf("%s, VM-entry intr info=0x%x",
> > +                         "VM-entry instruction-length check", ent_intr_info);
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info);
> > +
> > +     /* Instruction length set to -1 (0xFFFFFFFF) should fail */
> > +     ent_intr_len = -1;
> > +     report_prefix_pushf("VM-entry intr length = 0x%x", ent_intr_len);
> > +     vmcs_write(ENT_INST_LEN, ent_intr_len);
> > +     test_vmx_controls(false, false);
> > +     report_prefix_pop();
> > +
> > +     /* Instruction length set to 16 should fail */
> > +     ent_intr_len = 0x00000010;
> > +     report_prefix_pushf("VM-entry intr length = 0x%x", ent_intr_len);
> > +     vmcs_write(ENT_INST_LEN, 0x00000010);
> > +     test_vmx_controls(false, false);
> > +     report_prefix_pop();
> > +
> > +     report_prefix_pop();
> You should also test INTR_TYPE_PRIV_SW_EXCEPTION and INTR_TYPE_SOFT_INTR
> in addition to INTR_TYPE_SOFT_EXCEPTION.

Good call, I'll add the tests.

> > +
> > +     /* Cleanup */
> > +     vmcs_write(ENT_INTR_INFO, ent_intr_info_save);
> > +     vmcs_write(ENT_INTR_ERROR, ent_intr_error_save);
> > +     vmcs_write(ENT_INST_LEN, ent_inst_len_save);
> > +     vmcs_write(CPU_EXEC_CTRL0, primary_save);
> > +     vmcs_write(CPU_EXEC_CTRL1, secondary_save);
> > +     vmcs_write(GUEST_CR0, guest_cr0_save);
> > +     report_prefix_pop();
> > +}
> > +
> >   /*
> >    * Test interesting vTPR values for a given TPR threshold.
> >    */
> > @@ -3807,6 +4037,7 @@ static void vmx_controls_test(void)
> >       test_apic_virt_addr();
> >       test_tpr_threshold();
> >       test_nmi_ctrls();
> > +     test_invalid_event_injection();
> >   }
> >
> >   static bool valid_vmcs_for_vmentry(void)
>




[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux