@@ -430,4 +430,9 @@ static inline void write_pkru(u32 pkru)
: : "a" (eax), "c" (ecx), "d" (edx));
}
+static inline bool is_canonical(u64 addr)
+{
+ return (s64)(addr << 16) >> 16 == addr;
+}
+
#endif
@@ -494,6 +494,12 @@ extra_params = -cpu host,+vmx -m 2048 -append ept_access_test_force_2m_page
arch = x86_64
groups = vmx
+[vmx_invvpid]
+file = vmx.flat
+extra_params = -cpu host,+vmx -m 2048 -append invvpid_test_v2
+arch = x86_64
+groups = vmx
+
[debug]
file = debug.flat
arch = x86_64
@@ -987,9 +987,9 @@ bool ept_ad_bits_supported(void)
void vpid_sync(int type, u16 vpid)
{
switch(type) {
- case INVVPID_SINGLE:
- if (ept_vpid.val & VPID_CAP_INVVPID_SINGLE) {
- invvpid(INVVPID_SINGLE, vpid, 0);
+ case INVVPID_CONTEXT_GLOBAL:
+ if (ept_vpid.val & VPID_CAP_INVVPID_CXTGLB) {
+ invvpid(INVVPID_CONTEXT_GLOBAL, vpid, 0);
break;
}
case INVVPID_ALL:
@@ -13,6 +13,11 @@ struct vmcs {
char data[0];
};
+struct invvpid_operand {
+ u64 vpid;
+ u64 gla;
+};
+
struct regs {
u64 rax;
u64 rcx;
@@ -537,8 +542,10 @@ enum vm_instruction_error_number {
#define EPT_CAP_INVEPT_ALL (1ull << 26)
#define EPT_CAP_AD_FLAG (1ull << 21)
#define VPID_CAP_INVVPID (1ull << 32)
-#define VPID_CAP_INVVPID_SINGLE (1ull << 41)
-#define VPID_CAP_INVVPID_ALL (1ull << 42)
+#define VPID_CAP_INVVPID_ADDR (1ull << 40)
+#define VPID_CAP_INVVPID_CXTGLB (1ull << 41)
+#define VPID_CAP_INVVPID_ALL (1ull << 42)
+#define VPID_CAP_INVVPID_CXTLOC (1ull << 43)
#define PAGE_SIZE_2M (512 * PAGE_SIZE)
#define PAGE_SIZE_1G (512 * PAGE_SIZE_2M)
@@ -569,9 +576,10 @@ enum vm_instruction_error_number {
#define INVEPT_SINGLE 1
#define INVEPT_GLOBAL 2
-#define INVVPID_SINGLE_ADDRESS 0
-#define INVVPID_SINGLE 1
+#define INVVPID_ADDR 0
+#define INVVPID_CONTEXT_GLOBAL 1
#define INVVPID_ALL 2
+#define INVVPID_CONTEXT_LOCAL 3
#define ACTV_ACTIVE 0
#define ACTV_HLT 1
@@ -687,16 +695,12 @@ static inline bool invept(unsigned long type, u64 eptp)
return ret;
}
-static inline bool invvpid(unsigned long type, u16 vpid, u64 gva)
+static inline bool invvpid(unsigned long type, u64 vpid, u64 gla)
{
bool ret;
u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF;
- struct {
- u64 vpid : 16;
- u64 rsvd : 48;
- u64 gva;
- } operand = {vpid, 0, gva};
+ struct invvpid_operand operand = {vpid, gla};
asm volatile("push %1; popf; invvpid %2, %3; setbe %0"
: "=q" (ret) : "r" (rflags), "m"(operand),"r"(type) : "cc");
return ret;
@@ -13,6 +13,10 @@
#include "apic.h"
#include "types.h"
+#define NONCANONICAL 0xaaaaaaaaaaaaaaaaull
+
+#define VPID_CAP_INVVPID_TYPES_SHIFT 40
+
u64 ia32_pat;
u64 ia32_efer;
void *io_bitmap_a, *io_bitmap_b;
@@ -25,6 +29,15 @@ void *data_page1, *data_page2;
void *pml_log;
#define PML_INDEX 512
+static inline unsigned ffs(unsigned x)
+{
+ int pos = -1;
+
+ __asm__ __volatile__("bsf %1, %%eax; cmovnz %%eax, %0"
+ : "+r"(pos) : "rm"(x) : "eax");
+ return pos + 1;
+}
+
static inline void vmcall()
{
asm volatile("vmcall");
@@ -1375,7 +1388,8 @@ bool invvpid_test(int type, u16 vpid)
{
bool ret, supported;
- supported = ept_vpid.val & (VPID_CAP_INVVPID_SINGLE >> INVVPID_SINGLE << type);
+ supported = ept_vpid.val &
+ (VPID_CAP_INVVPID_ADDR >> INVVPID_ADDR << type);
ret = invvpid(type, vpid, 0);
if (ret == !supported)
@@ -1432,11 +1446,11 @@ static int vpid_exit_handler()
case VMX_VMCALL:
switch(vmx_get_test_stage()) {
case 0:
- if (!invvpid_test(INVVPID_SINGLE_ADDRESS, 1))
+ if (!invvpid_test(INVVPID_ADDR, 1))
vmx_inc_test_stage();
break;
case 2:
- if (!invvpid_test(INVVPID_SINGLE, 1))
+ if (!invvpid_test(INVVPID_CONTEXT_GLOBAL, 1))
vmx_inc_test_stage();
break;
case 4:
@@ -2916,6 +2930,178 @@ static void ept_access_test_force_2m_page(void)
ept_misconfig_at_level_mkhuge(true, 2, EPT_PRESENT, EPT_WA);
}
+static bool invvpid_valid(u64 type, u64 vpid, u64 gla)
+{
+ u64 msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP);
+
+ TEST_ASSERT(msr & VPID_CAP_INVVPID);
+
+ if (type < INVVPID_ADDR || type > INVVPID_CONTEXT_LOCAL)
+ return false;
+
+ if (!(msr & (1ull << (type + VPID_CAP_INVVPID_TYPES_SHIFT))))
+ return false;
+
+ if (vpid >> 16)
+ return false;
+
+ if (type != INVVPID_ALL && !vpid)
+ return false;
+
+ if (type == INVVPID_ADDR && !is_canonical(gla))
+ return false;
+
+ return true;
+}
+
+static void try_invvpid(u64 type, u64 vpid, u64 gla)
+{
+ int rc;
+ bool valid = invvpid_valid(type, vpid, gla);
+ u64 expected = valid ? VMXERR_UNSUPPORTED_VMCS_COMPONENT
+ : VMXERR_INVALID_OPERAND_TO_INVEPT_INVVPID;
+ /*
+ * Set VMX_INST_ERROR to VMXERR_UNVALID_VMCS_COMPONENT, so
+ * that we can tell if it is updated by INVVPID.
+ */
+ vmcs_read(~0);
+ rc = invvpid(type, vpid, gla);
+ report("INVVPID type %ld VPID %lx GLA %lx %s",
+ !rc == valid, type, vpid, gla,
+ valid ? "passes" : "fails");
+ report("After %s INVVPID, VMX_INST_ERR is %d (actual %d)",
+ vmcs_read(VMX_INST_ERROR) == expected,
+ rc ? "failed" : "successful",
+ expected, vmcs_read(VMX_INST_ERROR));
+}
+
+static void ds_invvpid(void *data)
+{
+ u64 msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP);
+ u64 type = ffs(msr >> VPID_CAP_INVVPID_TYPES_SHIFT) - 1;
+
+ TEST_ASSERT(type >= INVVPID_ADDR && type <= INVVPID_CONTEXT_LOCAL);
+ asm volatile("invvpid %0, %1"
+ :
+ : "m"(*(struct invvpid_operand *)data),
+ "r"(type));
+}
+
+/*
+ * The SS override is ignored in 64-bit mode, so we use an addressing
+ * mode with %rsp as the base register to generate an implicit SS
+ * reference.
+ */
+static void ss_invvpid(void *data)
+{
+ u64 msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP);
+ u64 type = ffs(msr >> VPID_CAP_INVVPID_TYPES_SHIFT) - 1;
+
+ TEST_ASSERT(type >= INVVPID_ADDR && type <= INVVPID_CONTEXT_LOCAL);
+ asm volatile("sub %%rsp,%0; invvpid (%%rsp,%0,1), %1"
+ : "+r"(data)
+ : "r"(type));
+}
+
+static void invvpid_test_gp(void)
+{
+ bool fault;
+
+ fault = test_for_exception(GP_VECTOR, &ds_invvpid,
+ (void *)NONCANONICAL);
+ report("INVVPID with non-canonical DS operand raises #GP", fault);
+}
+
+static void invvpid_test_ss(void)
+{
+ bool fault;
+
+ fault = test_for_exception(SS_VECTOR, &ss_invvpid,
+ (void *)NONCANONICAL);
+ report("INVVPID with non-canonical SS operand raises #SS", fault);
+}
+
+static void invvpid_test_pf(void)
+{
+ void *vpage = alloc_vpage();
+ bool fault;
+
+ fault = test_for_exception(PF_VECTOR, &ds_invvpid, vpage);
+ report("INVVPID with unmapped operand raises #PF", fault);
+}
+
+static void invvpid_test_not_in_vmx_operation(void)
+{
+ bool fault;
+
+ TEST_ASSERT(!vmx_off());
+ fault = test_for_exception(UD_VECTOR, &ds_invvpid, NULL);
+ report("INVVPID outside of VMX operation raises #UD", fault);
+ TEST_ASSERT(!vmx_on());
+}
+
+/*
+ * This does not test real-address mode, virtual-8086 mode, protected mode,
+ * compatibility mode, or CPL > 0.
+ */
+static void invvpid_test_v2(void)
+{
+ u64 msr;
+ int i;
+ unsigned types = 0;
+ unsigned type;
+
+ if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) ||
+ !(ctrl_cpu_rev[1].clr & CPU_VPID))
+ test_skip("VPID not supported");
+
+ msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP);
+
+ if (!(msr & VPID_CAP_INVVPID))
+ test_skip("INVVPID not supported.\n");
+
+ if (msr & VPID_CAP_INVVPID_ADDR)
+ types |= 1u << INVVPID_ADDR;
+ if (msr & VPID_CAP_INVVPID_CXTGLB)
+ types |= 1u << INVVPID_CONTEXT_GLOBAL;
+ if (msr & VPID_CAP_INVVPID_ALL)
+ types |= 1u << INVVPID_ALL;
+ if (msr & VPID_CAP_INVVPID_CXTLOC)
+ types |= 1u << INVVPID_CONTEXT_LOCAL;
+
+ if (!types)
+ test_skip("No INVVPID types supported.\n");
+
+ for (i = -127; i < 128; i++)
+ try_invvpid(i, 0xffff, 0);
+
+ /*
+ * VPID must not be more than 16 bits.
+ */
+ for (i = 0; i < 64; i++)
+ for (type = 0; type < 4; type++)
+ if (types & (1u << type))
+ try_invvpid(type, 1ul << i, 0);
+
+ /*
+ * VPID must not be zero, except for "all contexts."
+ */
+ for (type = 0; type < 4; type++)
+ if (types & (1u << type))
+ try_invvpid(type, 0, 0);
+
+ /*
+ * The gla operand is only validated for single-address INVVPID.
+ */
+ if (types & (1u << INVVPID_ADDR))
+ try_invvpid(INVVPID_ADDR, 0xffff, NONCANONICAL);
+
+ invvpid_test_gp();
+ invvpid_test_ss();
+ invvpid_test_pf();
+ invvpid_test_not_in_vmx_operation();
+}
+
#define TEST(name) { #name, .v2 = name }
/* name/init/guest_main/exit_handler/syscall_handler/guest_regs */
@@ -2977,5 +3163,7 @@ struct vmx_test vmx_tests[] = {
TEST(ept_access_test_paddr_read_execute_ad_enabled),
TEST(ept_access_test_paddr_not_present_page_fault),
TEST(ept_access_test_force_2m_page),
+ /* Opcode tests. */
+ TEST(invvpid_test_v2),
{ NULL, NULL, NULL, NULL, NULL, {0} },
};
Tests only for success/failure of invvpid. Does not test actual invvpid functionality. Signed-off-by: Jim Mattson <jmattson@google.com> --- lib/x86/processor.h | 5 ++ x86/unittests.cfg | 6 ++ x86/vmx.c | 6 +- x86/vmx.h | 24 ++++--- x86/vmx_tests.c | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 219 insertions(+), 16 deletions(-)