Introduce a new test for mode-based execute control (MBEC) in the VMX controls, validating the dependency between MBEC and EPT VM-execution controls. The test ensures that VM entry fails when MBEC is enabled without EPT, and succeeds in valid combinations. Update the unit test configuration to include a specific test case for MBEC on Skylake-Server CPU model, as that was the first CPU series to have MBEC. Tested on Intel SPR Gold 5416S. Passing test result Test suite: vmx_controls_test_mbec PASS: MBEC disabled, EPT disabled (valid combination): vmlaunch succeeds PASS: MBEC enabled, EPT disabled (invalid combination): vmlaunch fails PASS: MBEC enabled, EPT disabled (invalid combination): VMX inst error is 7 (actual 7) PASS: MBEC enabled, EPT enabled (valid combination): vmlaunch succeeds PASS: MBEC disabled, EPT enabled (valid combination): vmlaunch succeeds Test ran with "-vmx-mbec": Test suite: vmx_controls_test_mbec SKIP: test_mode_based_execute_control : "Secondary execution" or "enable EPT" or "enable mode-based execute control" control not supported Note, all other tests pass (including EPT with MBEC enabled and disabled) with MBEC v1 series. Signed-off-by: Jon Kohler --- x86/unittests.cfg | 9 ++++++ x86/vmx.h | 8 +++++ x86/vmx_tests.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/x86/unittests.cfg b/x86/unittests.cfg index 522318d3..b82bbc4e 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -324,6 +324,15 @@ qemu_params = -cpu max,+vmx arch = x86_64 groups = vmx +# VMX controls is a generic test; however, mode-based execute control +# aka MBEC is only available on Skylake and above, be specific about +# the CPU model and test it directly. +[vmx_controls_test_mbec] +file = vmx.flat +extra_params = -cpu Skylake-Server,+vmx,+vmx-mbec -append "vmx_controls_test_mbec" +arch = x86_64 +groups = vmx + [ept] file = vmx.flat test_args = "ept_access*" diff --git a/x86/vmx.h b/x86/vmx.h index 33373bd1..75667ccc 100644 --- a/x86/vmx.h +++ b/x86/vmx.h @@ -510,6 +510,7 @@ enum Ctrl1 { CPU_SHADOW_VMCS = 1ul << 14, CPU_RDSEED = 1ul << 16, CPU_PML = 1ul << 17, + CPU_MODE_BASED_EPT_EXEC = 1ul << 22, CPU_USE_TSC_SCALING = 1ul << 25, }; @@ -842,6 +843,13 @@ static inline bool is_invvpid_type_supported(unsigned long type) return ept_vpid.val & (VPID_CAP_INVVPID_ADDR << (type - INVVPID_ADDR)); } +static inline bool is_mbec_supported(void) +{ + return (ctrl_cpu_rev[0].clr & CPU_SECONDARY) && + (ctrl_cpu_rev[1].clr & CPU_EPT) && + (ctrl_cpu_rev[1].clr & CPU_MODE_BASED_EPT_EXEC); +} + extern u64 *bsp_vmxon_region; extern bool launched; diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 5ffb80a3..ad7cfe83 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -4867,6 +4867,69 @@ skip_unrestricted_guest: vmcs_write(EPTP, eptp_saved); } +/* + * Test the dependency between mode-based execute control for EPT (MBEC) and + * enable EPT VM-execution controls. + * + * When MBEC (bit 22 of secondary processor-based VM-execution controls) is enabled, + * it allows separate execute permissions for supervisor-mode and user-mode linear + * addresses in EPT paging structures. However, per Intel SDM requirement: + * + * "If the 'mode-based execute control for EPT' VM-execution control is 1, + * the 'enable EPT' VM-execution control must also be 1." + * + * This test validates that VM entry fails when MBEC is enabled without EPT, + * and succeeds in all other valid combinations. + * + * [Intel SDM Vol. 3C, Section 26.6.2, Table 26-7] + */ +static void test_mode_based_execute_control(void) +{ + u32 primary_saved = vmcs_read(CPU_EXEC_CTRL0); + u32 secondary_saved = vmcs_read(CPU_EXEC_CTRL1); + u32 primary = primary_saved; + u32 secondary = secondary_saved; + + /* Skip test if required VM-execution controls are not supported */ + if (!is_mbec_supported()) { + report_skip("MBEC not supported"); + return; + } + + /* Test case 1: MBEC disabled, EPT disabled - should be valid */ + primary |= CPU_SECONDARY; + vmcs_write(CPU_EXEC_CTRL0, primary); + secondary &= ~(CPU_MODE_BASED_EPT_EXEC | CPU_EPT); + vmcs_write(CPU_EXEC_CTRL1, secondary); + report_prefix_pushf("MBEC disabled, EPT disabled (valid combination)"); + test_vmx_valid_controls(); + report_prefix_pop(); + + /* Test case 2: MBEC enabled, EPT disabled - should be invalid per SDM */ + secondary |= CPU_MODE_BASED_EPT_EXEC; + vmcs_write(CPU_EXEC_CTRL1, secondary); + report_prefix_pushf("MBEC enabled, EPT disabled (invalid combination)"); + test_vmx_invalid_controls(); + report_prefix_pop(); + + /* Test case 3: MBEC enabled, EPT enabled - should be valid */ + secondary |= CPU_EPT; + setup_dummy_ept(); + report_prefix_pushf("MBEC enabled, EPT enabled (valid combination)"); + test_vmx_valid_controls(); + report_prefix_pop(); + + /* Test case 4: MBEC disabled, EPT enabled - should be valid */ + secondary &= ~CPU_MODE_BASED_EPT_EXEC; + vmcs_write(CPU_EXEC_CTRL1, secondary); + report_prefix_pushf("MBEC disabled, EPT enabled (valid combination)"); + test_vmx_valid_controls(); + report_prefix_pop(); + + vmcs_write(CPU_EXEC_CTRL0, primary_saved); + vmcs_write(CPU_EXEC_CTRL1, secondary_saved); +} + /* * If the 'enable PML' VM-execution control is 1, the 'enable EPT' * VM-execution control must also be 1. In addition, the PML address @@ -5327,6 +5390,7 @@ static void test_vm_execution_ctls(void) test_pml(); test_vpid(); test_ept_eptp(); + test_mode_based_execute_control(); test_vmx_preemption_timer(); } @@ -5551,6 +5615,17 @@ static void vmx_controls_test(void) test_vm_entry_ctls(); } +/* + * Check that Intel MBEC controls function properly, which is a + * Skylake and above feature, and is not supported on older CPUs. + */ +static void vmx_controls_test_mbec(void) +{ + vmcs_write(GUEST_RFLAGS, 0); + + test_mode_based_execute_control(); +} + struct apic_reg_virt_config { bool apic_register_virtualization; bool use_tpr_shadow; @@ -11506,6 +11581,7 @@ struct vmx_test vmx_tests[] = { TEST(invvpid_test), /* VM-entry tests */ TEST(vmx_controls_test), + TEST(vmx_controls_test_mbec), TEST(vmx_host_state_area_test), TEST(vmx_guest_state_area_test), TEST(vmentry_movss_shadow_test), -- 2.43.0 Prepare for MBEC EPT access test cases by refactoring the EPT installation logic in vmx_tests.c and vmx.c to replace the use of EPT_RA | EPT_WA | EPT_EA flags with the EPT_PRESENT flag. Update the EPT_PRESENT definition in vmx.h to conditionally include user access rights based on MBEC support. No functional change intended, all tests pass with both +vmx-mbec and -vmx-mbec. Signed-off-by: Jon Kohler --- x86/vmx.c | 3 +-- x86/vmx.h | 16 ++++++++++------ x86/vmx_tests.c | 24 ++++++++++++------------ 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/x86/vmx.c b/x86/vmx.c index c803eaa6..eb2965d8 100644 --- a/x86/vmx.c +++ b/x86/vmx.c @@ -875,8 +875,7 @@ void install_ept_entry(unsigned long *pml4, else pt_page = 0; memset(new_pt, 0, PAGE_SIZE); - pt[offset] = virt_to_phys(new_pt) - | EPT_RA | EPT_WA | EPT_EA; + pt[offset] = virt_to_phys(new_pt) | EPT_PRESENT; } else if (pt[offset] & EPT_LARGE_PAGE) split_large_ept_entry(&pt[offset], level); pt = phys_to_virt(pt[offset] & EPT_ADDR_MASK); diff --git a/x86/vmx.h b/x86/vmx.h index 75667ccc..f88188af 100644 --- a/x86/vmx.h +++ b/x86/vmx.h @@ -665,18 +665,22 @@ enum vm_entry_failure_code { #define EPT_MEM_TYPE_WP 5ul #define EPT_MEM_TYPE_WB 6ul -#define EPT_RA 1ul -#define EPT_WA 2ul -#define EPT_EA 4ul -#define EPT_PRESENT (EPT_RA | EPT_WA | EPT_EA) +#define EPT_RA (1ul << 0) +#define EPT_WA (1ul << 1) +#define EPT_EA (1ul << 2) +#define EPT_IGNORE_PAT (1ul << 6) +#define EPT_LARGE_PAGE (1ul << 7) #define EPT_ACCESS_FLAG (1ul << 8) #define EPT_DIRTY_FLAG (1ul << 9) -#define EPT_LARGE_PAGE (1ul << 7) +#define EPT_EA_USER (1ul << 10) #define EPT_MEM_TYPE_SHIFT 3ul #define EPT_MEM_TYPE_MASK 0x7ul -#define EPT_IGNORE_PAT (1ul << 6) #define EPT_SUPPRESS_VE (1ull << 63) +#define EPT_PRESENT (is_mbec_supported() ? \ + (EPT_RA | EPT_WA | EPT_EA | EPT_EA_USER) : \ + (EPT_RA | EPT_WA | EPT_EA)) + #define EPT_CAP_EXEC_ONLY (1ull << 0) #define EPT_CAP_PWL4 (1ull << 6) #define EPT_CAP_PWL5 (1ull << 7) diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index ad7cfe83..9d91ce6b 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -1100,7 +1100,7 @@ static int setup_ept(bool enable_ad) */ setup_ept_range(pml4, 0, end_of_memory, 0, !enable_ad && ept_2m_supported(), - EPT_WA | EPT_RA | EPT_EA); + EPT_PRESENT); return 0; } @@ -1179,7 +1179,7 @@ static int ept_init_common(bool have_ad) *((u32 *)data_page1) = MAGIC_VAL_1; *((u32 *)data_page2) = MAGIC_VAL_2; install_ept(pml4, (unsigned long)data_page1, (unsigned long)data_page2, - EPT_RA | EPT_WA | EPT_EA); + EPT_PRESENT); apic_version = apic_read(APIC_LVR); @@ -1359,8 +1359,8 @@ static int ept_exit_handler_common(union exit_reason exit_reason, bool have_ad) *((u32 *)data_page2) == MAGIC_VAL_2) { vmx_inc_test_stage(); install_ept(pml4, (unsigned long)data_page2, - (unsigned long)data_page2, - EPT_RA | EPT_WA | EPT_EA); + (unsigned long)data_page2, + EPT_PRESENT); } else report_fail("EPT basic framework - write"); break; @@ -1371,9 +1371,9 @@ static int ept_exit_handler_common(union exit_reason exit_reason, bool have_ad) break; case 2: install_ept(pml4, (unsigned long)data_page1, - (unsigned long)data_page1, - EPT_RA | EPT_WA | EPT_EA | - (2 << EPT_MEM_TYPE_SHIFT)); + (unsigned long)data_page1, + EPT_PRESENT | + (2 << EPT_MEM_TYPE_SHIFT)); invept(INVEPT_SINGLE, eptp); break; case 3: @@ -1417,8 +1417,8 @@ static int ept_exit_handler_common(union exit_reason exit_reason, bool have_ad) case 2: vmx_inc_test_stage(); install_ept(pml4, (unsigned long)data_page1, - (unsigned long)data_page1, - EPT_RA | EPT_WA | EPT_EA); + (unsigned long)data_page1, + EPT_PRESENT); invept(INVEPT_SINGLE, eptp); break; // Should not reach here @@ -3020,9 +3020,9 @@ static void ept_access_test_paddr_read_write_execute(void) { ept_access_test_setup(); /* RWX access to paging structure. */ - ept_access_allowed_paddr(EPT_PRESENT, 0, OP_READ); - ept_access_allowed_paddr(EPT_PRESENT, 0, OP_WRITE); - ept_access_allowed_paddr(EPT_PRESENT, 0, OP_EXEC); + ept_access_allowed_paddr(EPT_RA | EPT_WA | EPT_EA, 0, OP_READ); + ept_access_allowed_paddr(EPT_RA | EPT_WA | EPT_EA, 0, OP_WRITE); + ept_access_allowed_paddr(EPT_RA | EPT_WA | EPT_EA, 0, OP_EXEC); } static void ept_access_test_paddr_read_execute_ad_disabled(void) -- 2.43.0 Introduce a new ept_access_op, OP_EXEC_USER, to the EPT access tests to prepare for MBEC, which allows execution of user-level code. No functional change intended. Signed-off-by: Jon Kohler --- x86/vmx_tests.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 9d91ce6b..3ea6f9e2 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -2252,6 +2252,7 @@ enum ept_access_op { OP_READ, OP_WRITE, OP_EXEC, + OP_EXEC_USER, OP_FLUSH_TLB, OP_EXIT, }; @@ -2717,10 +2718,20 @@ static void ept_access_test_teardown(void *unused) do_ept_access_op(OP_EXIT); } +static u64 exec_user_on_gva(void) +{ + struct ept_access_test_data *data = &ept_access_test_data; + int (*code)(void) = (int (*)(void)) &data->gva[1]; + + return code(); +} + static void ept_access_test_guest(void) { struct ept_access_test_data *data = &ept_access_test_data; int (*code)(void) = (int (*)(void)) &data->gva[1]; + bool unused; + u64 ret_val; while (true) { switch (data->op) { @@ -2735,6 +2746,12 @@ static void ept_access_test_guest(void) case OP_EXEC: TEST_ASSERT_EQ(42, code()); break; + case OP_EXEC_USER: + TEST_ASSERT_EQ(is_mbec_supported(), true); + ret_val = run_in_user(exec_user_on_gva, GP_VECTOR, + 0, 0, 0, 0, &unused); + TEST_ASSERT_EQ(42, ret_val); + break; case OP_FLUSH_TLB: write_cr3(read_cr3()); break; -- 2.43.0 Update the ept_access_test_ignored_bits test only ignore bit 10 if MBEC is not supported, as the bit is not ignored when MBEC is enabled. When MBEC is enabled, test ept_allowed for OP_EXEC_USER in ept_ignored_bit. Update unittest.cfg to break off MBEC into a separate test suite, as using -cpu max will automagically pick up MBEC, and it should be testable with MBEC enabled and disabled. Signed-off-by: Jon Kohler --- x86/unittests.cfg | 12 +++++++++++- x86/vmx_tests.c | 8 +++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/x86/unittests.cfg b/x86/unittests.cfg index b82bbc4e..022ea52c 100644 --- a/x86/unittests.cfg +++ b/x86/unittests.cfg @@ -336,7 +336,17 @@ groups = vmx [ept] file = vmx.flat test_args = "ept_access*" -qemu_params = -cpu max,host-phys-bits,+vmx -m 2560 +qemu_params = -cpu max,host-phys-bits,+vmx,-vmx-mbec -m 2560 +arch = x86_64 +groups = vmx + +# EPT is a generic test; however, mode-based execute control aka MBEC +# is only available on Skylake and above, be specific about the CPU +# model and test it directly. +[ept-mbec] +file = vmx.flat +test_args = "ept_access*" +qemu_params = -cpu Skylake-Server,host-phys-bits,+vmx,+vmx-mbec -m 2560 arch = x86_64 groups = vmx diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 3ea6f9e2..9a636eef 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -2586,6 +2586,11 @@ static void ept_ignored_bit(int bit) ept_allowed(1ul << bit, 0, OP_READ); ept_allowed(1ul << bit, 0, OP_WRITE); ept_allowed(1ul << bit, 0, OP_EXEC); + + if (is_mbec_supported()) { + ept_allowed(0, 1ul << bit, OP_EXEC_USER); + ept_allowed(1ul << bit, 0, OP_EXEC_USER); + } } static void ept_access_allowed(unsigned long access, enum ept_access_op op) @@ -2936,7 +2941,8 @@ static void ept_access_test_ignored_bits(void) */ ept_ignored_bit(8); ept_ignored_bit(9); - ept_ignored_bit(10); + if (!is_mbec_supported()) + ept_ignored_bit(10); ept_ignored_bit(11); ept_ignored_bit(52); ept_ignored_bit(53); -- 2.43.0 Extend ept_access_test_read_write_execute to cover MBEC EPT rwx case, which uses OP_EXEC_USER to execute user mode code when MBEC is enabled. Tests pass with both -vmx-mbec and +vmx-mbec. Signed-off-by: Jon Kohler --- x86/vmx_tests.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 9a636eef..ce871141 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -2885,6 +2885,9 @@ static void ept_access_test_read_write_execute(void) ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_READ); ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_WRITE); ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_EXEC); + + if (is_mbec_supported()) + ept_access_allowed(EPT_PRESENT, OP_EXEC_USER); } static void ept_access_test_reserved_bits(void) -- 2.43.0 Extend all ept_access_test_paddr_* tests to plumb in OP_EXEC_USER for MBEC support. Tests pass with both -vmx-mbec and +vmx-mbec. Signed-off-by: Jon Kohler --- x86/vmx_tests.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index ce871141..465bcf72 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -2969,6 +2969,9 @@ static void ept_access_test_paddr_not_present_ad_disabled(void) ept_access_violation_paddr(0, PT_AD_MASK, OP_READ, EPT_VLT_RD); ept_access_violation_paddr(0, PT_AD_MASK, OP_WRITE, EPT_VLT_RD); ept_access_violation_paddr(0, PT_AD_MASK, OP_EXEC, EPT_VLT_RD); + + if (is_mbec_supported()) + ept_access_violation_paddr(0, PT_AD_MASK, OP_EXEC_USER, EPT_VLT_RD); } static void ept_access_test_paddr_not_present_ad_enabled(void) @@ -2981,6 +2984,9 @@ static void ept_access_test_paddr_not_present_ad_enabled(void) ept_access_violation_paddr(0, PT_AD_MASK, OP_READ, qual); ept_access_violation_paddr(0, PT_AD_MASK, OP_WRITE, qual); ept_access_violation_paddr(0, PT_AD_MASK, OP_EXEC, qual); + + if (is_mbec_supported()) + ept_access_violation_paddr(0, PT_AD_MASK, OP_EXEC_USER, qual); } static void ept_access_test_paddr_read_only_ad_disabled(void) @@ -3008,6 +3014,12 @@ static void ept_access_test_paddr_read_only_ad_disabled(void) ept_access_allowed_paddr(EPT_RA, PT_AD_MASK, OP_READ); ept_access_allowed_paddr(EPT_RA, PT_AD_MASK, OP_WRITE); ept_access_allowed_paddr(EPT_RA, PT_AD_MASK, OP_EXEC); + + if (is_mbec_supported()) { + ept_access_violation_paddr(EPT_RA, 0, OP_EXEC_USER, qual); + ept_access_allowed_paddr(EPT_RA, PT_ACCESSED_MASK, OP_EXEC_USER); + ept_access_allowed_paddr(EPT_RA, PT_AD_MASK, OP_EXEC_USER); + } } static void ept_access_test_paddr_read_only_ad_enabled(void) @@ -3031,6 +3043,12 @@ static void ept_access_test_paddr_read_only_ad_enabled(void) ept_access_violation_paddr(EPT_RA, PT_AD_MASK, OP_READ, qual); ept_access_violation_paddr(EPT_RA, PT_AD_MASK, OP_WRITE, qual); ept_access_violation_paddr(EPT_RA, PT_AD_MASK, OP_EXEC, qual); + + if (is_mbec_supported()) { + ept_access_violation_paddr(EPT_RA, 0, OP_EXEC_USER, qual); + ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_EXEC_USER, qual); + ept_access_violation_paddr(EPT_RA, PT_AD_MASK, OP_EXEC_USER, qual); + } } static void ept_access_test_paddr_read_write(void) @@ -3040,6 +3058,9 @@ static void ept_access_test_paddr_read_write(void) ept_access_allowed_paddr(EPT_RA | EPT_WA, 0, OP_READ); ept_access_allowed_paddr(EPT_RA | EPT_WA, 0, OP_WRITE); ept_access_allowed_paddr(EPT_RA | EPT_WA, 0, OP_EXEC); + + if (is_mbec_supported()) + ept_access_allowed_paddr(EPT_RA | EPT_WA, 0, OP_EXEC_USER); } static void ept_access_test_paddr_read_write_execute(void) @@ -3049,6 +3070,9 @@ static void ept_access_test_paddr_read_write_execute(void) ept_access_allowed_paddr(EPT_RA | EPT_WA | EPT_EA, 0, OP_READ); ept_access_allowed_paddr(EPT_RA | EPT_WA | EPT_EA, 0, OP_WRITE); ept_access_allowed_paddr(EPT_RA | EPT_WA | EPT_EA, 0, OP_EXEC); + + if (is_mbec_supported()) + ept_access_allowed_paddr(EPT_PRESENT, 0, OP_EXEC_USER); } static void ept_access_test_paddr_read_execute_ad_disabled(void) @@ -3076,6 +3100,15 @@ static void ept_access_test_paddr_read_execute_ad_disabled(void) ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_READ); ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_WRITE); ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_EXEC); + + if (is_mbec_supported()) { + ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_EXEC_USER, + qual); + ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, + OP_EXEC_USER); + ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_AD_MASK, + OP_EXEC_USER); + } } static void ept_access_test_paddr_read_execute_ad_enabled(void) @@ -3099,6 +3132,15 @@ static void ept_access_test_paddr_read_execute_ad_enabled(void) ept_access_violation_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_READ, qual); ept_access_violation_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_WRITE, qual); ept_access_violation_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_EXEC, qual); + + if (is_mbec_supported()) { + ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_EXEC_USER, + qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, + OP_EXEC_USER, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, PT_AD_MASK, + OP_EXEC_USER, qual); + } } static void ept_access_test_paddr_not_present_page_fault(void) -- 2.43.0 Extend ept_access_test_read_execute to cover MBEC EPT r-x case, with the caveat that two of the cases do not currently work and are now commented out. Need a hand with sanity checking this, as both of the commented out test cases produce a tight EPT violation loop on the kernel side, and I'm unsure as of yet if its a test side issue (setup?) or what. Tests pass with both -vmx-mbec and +vmx-mbec (for the case that isn't commented out) Signed-off-by: Jon Kohler --- x86/vmx_tests.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 465bcf72..e869d702 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -2867,8 +2867,17 @@ static void ept_access_test_read_execute(void) /* r-x */ ept_access_allowed(EPT_RA | EPT_EA, OP_READ); ept_access_violation(EPT_RA | EPT_EA, OP_WRITE, - EPT_VLT_WR | EPT_VLT_PERM_RD | EPT_VLT_PERM_EX); + EPT_VLT_WR | EPT_VLT_PERM_RD | EPT_VLT_PERM_EX); ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC); + if (is_mbec_supported()) { + ept_access_allowed(EPT_RA | EPT_EA_USER, OP_READ); + // FIXME: this one produces EPT_VIOLATION LOOP (doesn't work, should it?) + // ept_access_violation(EPT_RA | EPT_EA_USER, OP_WRITE, + // EPT_VLT_WR | EPT_VLT_PERM_RD | + // EPT_VLT_PERM_EX); + // FIXME: this one produces EPT_VIOLATION LOOP (doesn't work, should it?) + //ept_access_allowed(EPT_RA | EPT_EA_USER, OP_EXEC_USER); + } } static void ept_access_test_write_execute(void) -- 2.43.0 Extend ept_access_test_execute_only to cover MBEC EPT --x case, with the caveat that it doesn't actually work as expected. Need a hand with sanity checking this, as both of the commented out test cases produce a tight EPT violation loop on the kernel side, and I'm unsure as of yet if its a test side issue (setup?) or what. Tests pass with both -vmx-mbec and +vmx-mbec (for the case that isn't commented out) Signed-off-by: Jon Kohler --- x86/vmx_tests.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index e869d702..3705e2ca 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -2856,6 +2856,14 @@ static void ept_access_test_execute_only(void) ept_access_violation(EPT_EA, OP_WRITE, EPT_VLT_WR | EPT_VLT_PERM_EX); ept_access_allowed(EPT_EA, OP_EXEC); + if (is_mbec_supported()) { + // FIXME: this does not produce the expected + // EPT violation, instead we get assert: + // Expected VMX_EPT_VIOLATION, got VMX_VMCALL + // ept_access_violation(EPT_EA, OP_EXEC_USER, + // EPT_VLT_FETCH | + // EPT_VLT_PERM_EX); + } } else { ept_access_misconfig(EPT_EA); } -- 2.43.0 Add ept_access_test_execute_user_only to validate MBEC execute user only access. The general structure of the test is similar to the traditional exec only test; however, there seem to be problems getting user mode addresses, even when executing the test from user mode, as we're not getting the right EPT violation qualifications back. I'm guessing this is something to do with how KUT allocates memory, I'd appreciate a hand in brainstorming how to resolve this, as well as any other suggestions on test structure. Signed-off-by: Jon Kohler --- x86/vmx_tests.c | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 3705e2ca..926e4c84 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -2294,6 +2294,7 @@ do { \ DIAGNOSE(EPT_VLT_PERM_RD); DIAGNOSE(EPT_VLT_PERM_WR); DIAGNOSE(EPT_VLT_PERM_EX); + DIAGNOSE(EPT_VLT_PERM_USER_EX); DIAGNOSE(EPT_VLT_LADDR_VLD); DIAGNOSE(EPT_VLT_PADDR); @@ -2360,9 +2361,12 @@ static void do_ept_violation(bool leaf, enum ept_access_op op, qual = vmcs_read(EXI_QUALIFICATION); - /* Mask undefined bits (which may later be defined in certain cases). */ - qual &= ~(EPT_VLT_GUEST_USER | EPT_VLT_GUEST_RW | EPT_VLT_GUEST_EX | - EPT_VLT_PERM_USER_EX); + /* + * Exit-qualifications are masked not to account for advanced + * VM-exit information. Once KVM supports this feature, this + * masking should be removed. + */ + qual &= ~EPT_VLT_GUEST_MASK; diagnose_ept_violation_qual(expected_qual, qual); TEST_EXPECT_EQ(expected_qual, qual); @@ -2390,6 +2394,8 @@ ept_violation_at_level_mkhuge(bool mkhuge, int level, unsigned long clear, orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set); + // FIXME: does this need to be modified for OP_USER_EXEC to feed + // in differently allocated memory perhaps? do_ept_violation(level == 1 || mkhuge, op, expected_qual, op == OP_EXEC ? data->gpa + sizeof(unsigned long) : data->gpa); @@ -2869,6 +2875,41 @@ static void ept_access_test_execute_only(void) } } +static void ept_access_test_execute_user_only(void) +{ + if (!is_mbec_supported()) { + report_skip("MBEC not supported"); + return; + } + + ept_access_test_setup(); + /* --X (exec user only) */ + if (ept_execute_only_supported()) { + // FIXME: should we be expecting EPT_VLT_PERM_USER_EX? + // as we're not getting it. Perhaps this has to do with + // how KUT is allocating memory here? + ept_access_violation(EPT_EA_USER, OP_READ, + EPT_VLT_RD | + EPT_VLT_PERM_USER_EX); + ept_access_violation(EPT_EA_USER, OP_WRITE, + EPT_VLT_WR | + EPT_VLT_PERM_USER_EX); + ept_access_violation(EPT_EA_USER, OP_EXEC, + EPT_VLT_FETCH | + EPT_VLT_PERM_USER_EX); + // FIXME: this one gets EPT VIOLATION + // Expected VMX_VMCALL, got VMX_EPT_VIOLATION with + // flags EPT_VLT_FETCH | EPT_VLT_LADDR_VLD | EPT_VLT_PADDR + // I'm guessing this is getting EPT VIOLATION for the same + // reason the above are not getting USER_EX, because the data + // address is supervisor mode linear address, not user mode + // linear? + ept_access_allowed(EPT_EA_USER, OP_EXEC_USER); + } else { + ept_access_misconfig(EPT_EA_USER); + } +} + static void ept_access_test_read_execute(void) { ept_access_test_setup(); @@ -11707,6 +11748,7 @@ struct vmx_test vmx_tests[] = { TEST(ept_access_test_write_only), TEST(ept_access_test_read_write), TEST(ept_access_test_execute_only), + TEST(ept_access_test_execute_user_only), TEST(ept_access_test_read_execute), TEST(ept_access_test_write_execute), TEST(ept_access_test_read_write_execute), -- 2.43.0 Plumb in OP_EXEC_USER to the remaining EPT access tests: ept_access_test_not_present ept_access_test_read_only ept_access_test_read_write Note: we do see one oddball failure in do_ept_violation when doing ept_access_test_read_write, where it appears the memory addresses are just a wee bit off. This goes the previous commentary in the series that I think there is something a bit off about memory allocation here in KUT and perhaps this is all related. Signed-off-by: Jon Kohler --- x86/vmx_tests.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c index 926e4c84..54adf9bd 100644 --- a/x86/vmx_tests.c +++ b/x86/vmx_tests.c @@ -2381,7 +2381,22 @@ static void do_ept_violation(bool leaf, enum ept_access_op op, * TODO: tests that probe expected_paddr in pages other than the one at * the beginning of the 1g region. */ - TEST_EXPECT_EQ(vmcs_read(INFO_PHYS_ADDR), expected_paddr); + // FIXME: ept_access_test_read_write fails without this, otherwise + // test outputs: + // Test suite: ept_access_test_read_write + // FAIL: x86/vmx_tests.c:2384: Expectation failed: (vmcs_read(INFO_PHYS_ADDR)) == (expected_paddr) + // LHS: 0x0000008000000008 - 0000'0000'0000'0000'0000'0000'1000'0000'0000'0000'0000'0000'0000'0000'0000'1000 - 549755813896 + // RHS: 0x0000008000000000 - 0000'0000'0000'0000'0000'0000'1000'0000'0000'0000'0000'0000'0000'0000'0000'0000 - 549755813888 + // STACK: 4175d0 417632 417699 417720 417863 402273 4040e5 4001bd + if (is_mbec_supported() && op == OP_EXEC_USER) { + u64 vmcs_paddr = vmcs_read(INFO_PHYS_ADDR); + u64 mask = ~(PAGE_SIZE - 1); + + if (vmcs_paddr) + TEST_EXPECT_EQ((vmcs_paddr & mask), (expected_paddr & mask)); + } else { + TEST_EXPECT_EQ(vmcs_read(INFO_PHYS_ADDR), expected_paddr); + } } static void @@ -2822,6 +2837,8 @@ static void ept_access_test_not_present(void) ept_access_violation(0, OP_READ, EPT_VLT_RD); ept_access_violation(0, OP_WRITE, EPT_VLT_WR); ept_access_violation(0, OP_EXEC, EPT_VLT_FETCH); + if (is_mbec_supported()) + ept_access_violation(0, OP_EXEC_USER, EPT_VLT_FETCH); } static void ept_access_test_read_only(void) @@ -2832,6 +2849,9 @@ static void ept_access_test_read_only(void) ept_access_allowed(EPT_RA, OP_READ); ept_access_violation(EPT_RA, OP_WRITE, EPT_VLT_WR | EPT_VLT_PERM_RD); ept_access_violation(EPT_RA, OP_EXEC, EPT_VLT_FETCH | EPT_VLT_PERM_RD); + if (is_mbec_supported()) + ept_access_violation(EPT_RA, OP_EXEC_USER, + EPT_VLT_FETCH | EPT_VLT_PERM_RD); } static void ept_access_test_write_only(void) @@ -2848,7 +2868,12 @@ static void ept_access_test_read_write(void) ept_access_allowed(EPT_RA | EPT_WA, OP_READ); ept_access_allowed(EPT_RA | EPT_WA, OP_WRITE); ept_access_violation(EPT_RA | EPT_WA, OP_EXEC, - EPT_VLT_FETCH | EPT_VLT_PERM_RD | EPT_VLT_PERM_WR); + EPT_VLT_FETCH | EPT_VLT_PERM_RD | + EPT_VLT_PERM_WR); + if (is_mbec_supported()) + ept_access_violation(EPT_RA | EPT_WA, OP_EXEC_USER, + EPT_VLT_FETCH | EPT_VLT_PERM_RD | + EPT_VLT_PERM_WR); } -- 2.43.0