Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests
@ 2026-03-26 14:50 Paolo Bonzini
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 1/9] move PFERR_* constants to lib Paolo Bonzini
                   ` (9 more replies)
  0 siblings, 10 replies; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

This adds new tests for both GMET and MBEC.

The code for MBEC is roughly based on the previous submission at
https://lore.kernel.org/kvm/20251223054850.1611618-1-jon@nutanix.com/,
though with pretty heavy reorganization and fixing work on top.

The existing EPT tests now test execution on both supervisor and
user-mode pages, with different expected outcomes depending on
whether MBEC is enabled or not; on top of this, the last patch
adds tests for XU=1 and XS=XU=1.

For simplicity, the tests always enable MBEC when available.
A new block in unittests.cfg ensures that both non-MBEC and
MBEC is covered.

I will shortly send a new version of the patches that passes
the tests.

Paolo

Jon Kohler (1):
  x86/vmx: update EPT installation to use EPT_PRESENT flag

Paolo Bonzini (8):
  move PFERR_* constants to lib
  add definitions for nested_ctl
  svm: add basic GMET tests
  x86/vmx: diagnose unexpected EPT violations
  x86/vmx: add mode-based execute control test for Skylake and above
  x86/vmx: add user execution operation to EPT access tests
  x86/vmx: run EPT tests with MBEC enabled when available
  x86/vmx: add EPT tests covering XU permission

 Makefile            |   2 +-
 lib/util.h          |  10 +-
 lib/x86/asm/page.h  |   7 +
 lib/x86/processor.h |   1 +
 x86/access.c        |   7 -
 x86/svm.c           |  19 +-
 x86/svm.h           |   4 +
 x86/svm_npt.c       |  83 ++++++++-
 x86/unittests.cfg   |  21 ++-
 x86/vmx.c           |   3 +-
 x86/vmx.h           |  32 ++--
 x86/vmx_tests.c     | 414 +++++++++++++++++++++++++++++++++++++-------
 12 files changed, 513 insertions(+), 90 deletions(-)

-- 
2.52.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH kvm-unit-tests 1/9] move PFERR_* constants to lib
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
@ 2026-03-26 14:50 ` Paolo Bonzini
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 2/9] add definitions for nested_ctl Paolo Bonzini
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

Allow using them from the SVM tests as well.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 lib/x86/asm/page.h | 7 +++++++
 x86/access.c       | 7 -------
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/lib/x86/asm/page.h b/lib/x86/asm/page.h
index bc0e78c7..84a315f3 100644
--- a/lib/x86/asm/page.h
+++ b/lib/x86/asm/page.h
@@ -19,6 +19,13 @@ typedef unsigned long pgd_t;
 
 #define PAGE_ALIGN(addr)        ALIGN(addr, PAGE_SIZE)
 
+#define PFERR_PRESENT_MASK (1ull << 0)
+#define PFERR_WRITE_MASK (1ull << 1)
+#define PFERR_USER_MASK (1ull << 2)
+#define PFERR_RESERVED_MASK (1ull << 3)
+#define PFERR_FETCH_MASK (1ull << 4)
+#define PFERR_PK_MASK (1ull << 5)
+
 #ifdef __x86_64__
 #define LARGE_PAGE_SIZE	(512 * PAGE_SIZE)
 #else
diff --git a/x86/access.c b/x86/access.c
index d94910bf..142c1d92 100644
--- a/x86/access.c
+++ b/x86/access.c
@@ -17,13 +17,6 @@ static int invalid_mask;
 #define PT_BASE_ADDR_MASK ((pt_element_t)((((pt_element_t)1 << 36) - 1) & PAGE_MASK))
 #define PT_PSE_BASE_ADDR_MASK (PT_BASE_ADDR_MASK & ~(1ull << 21))
 
-#define PFERR_PRESENT_MASK (1U << 0)
-#define PFERR_WRITE_MASK (1U << 1)
-#define PFERR_USER_MASK (1U << 2)
-#define PFERR_RESERVED_MASK (1U << 3)
-#define PFERR_FETCH_MASK (1U << 4)
-#define PFERR_PK_MASK (1U << 5)
-
 #define MSR_EFER 0xc0000080
 #define EFER_NX_MASK            (1ull << 11)
 
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH kvm-unit-tests 2/9] add definitions for nested_ctl
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 1/9] move PFERR_* constants to lib Paolo Bonzini
@ 2026-03-26 14:50 ` Paolo Bonzini
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 3/9] svm: add basic GMET tests Paolo Bonzini
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

Right now only bit 0 was used, and it was hard coded.  Change
that to a #define.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 x86/svm.c | 2 +-
 x86/svm.h | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/x86/svm.c b/x86/svm.c
index de9eb194..58cbf0a5 100644
--- a/x86/svm.c
+++ b/x86/svm.c
@@ -200,7 +200,7 @@ void vmcb_ident(struct vmcb *vmcb)
 	ctrl->msrpm_base_pa = virt_to_phys(msr_bitmap);
 
 	if (npt_supported()) {
-		ctrl->nested_ctl = 1;
+		ctrl->nested_ctl = SVM_NESTED_ENABLE;
 		ctrl->nested_cr3 = (u64)pml4e;
 		ctrl->tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID;
 	}
diff --git a/x86/svm.h b/x86/svm.h
index 264583a6..947206bb 100644
--- a/x86/svm.h
+++ b/x86/svm.h
@@ -151,6 +151,9 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
 #define SVM_IOIO_SIZE_MASK (7 << SVM_IOIO_SIZE_SHIFT)
 #define SVM_IOIO_ASIZE_MASK (7 << SVM_IOIO_ASIZE_SHIFT)
 
+#define SVM_NESTED_ENABLE	1
+#define SVM_NESTED_GMET		8
+
 #define SVM_VM_CR_VALID_MASK	0x001fULL
 #define SVM_VM_CR_SVM_LOCK_MASK 0x0008ULL
 #define SVM_VM_CR_SVM_DIS_MASK  0x0010ULL
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH kvm-unit-tests 3/9] svm: add basic GMET tests
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 1/9] move PFERR_* constants to lib Paolo Bonzini
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 2/9] add definitions for nested_ctl Paolo Bonzini
@ 2026-03-26 14:50 ` Paolo Bonzini
  2026-03-27 16:03   ` Jon Kohler
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 4/9] x86/vmx: update EPT installation to use EPT_PRESENT flag Paolo Bonzini
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

These cover three basic scenarios of running successfully,
failing due to NX=1 and failing due to U/S=1.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 Makefile            |  2 +-
 lib/x86/processor.h |  1 +
 x86/svm.c           | 17 ++++++++++
 x86/svm.h           |  1 +
 x86/svm_npt.c       | 83 +++++++++++++++++++++++++++++++++++++++++++--
 5 files changed, 101 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
index 0ce0813b..403fd495 100644
--- a/Makefile
+++ b/Makefile
@@ -93,7 +93,7 @@ COMMON_CFLAGS += $(wunused_but_set_parameter)
 CFLAGS += $(COMMON_CFLAGS)
 CFLAGS += $(wmissing_parameter_type)
 CFLAGS += $(wold_style_declaration)
-CFLAGS += -Woverride-init -Wmissing-prototypes -Wstrict-prototypes
+CFLAGS += -Wmissing-prototypes -Wstrict-prototypes
 
 # Evaluate and add late cflags last -- they may depend on previous flags
 LATE_CFLAGS := $(LATE_CFLAGS)
diff --git a/lib/x86/processor.h b/lib/x86/processor.h
index 42dd2d2a..32ce08e2 100644
--- a/lib/x86/processor.h
+++ b/lib/x86/processor.h
@@ -377,6 +377,7 @@ struct x86_cpu_feature {
 #define X86_FEATURE_PAUSEFILTER		X86_CPU_FEATURE(0x8000000A, 0, EDX, 10)
 #define X86_FEATURE_PFTHRESHOLD		X86_CPU_FEATURE(0x8000000A, 0, EDX, 12)
 #define X86_FEATURE_VGIF		X86_CPU_FEATURE(0x8000000A, 0, EDX, 16)
+#define X86_FEATURE_GMET		X86_CPU_FEATURE(0x8000000A, 0, EDX, 17)
 #define X86_FEATURE_VNMI		X86_CPU_FEATURE(0x8000000A, 0, EDX, 25)
 #define X86_FEATURE_SME			X86_CPU_FEATURE(0x8000001F, 0, EAX,  0)
 #define X86_FEATURE_SEV			X86_CPU_FEATURE(0x8000001F, 0, EAX,  1)
diff --git a/x86/svm.c b/x86/svm.c
index 58cbf0a5..a85da905 100644
--- a/x86/svm.c
+++ b/x86/svm.c
@@ -43,6 +43,23 @@ u64 *npt_get_pml4e(void)
 	return pml4e;
 }
 
+void npt_prepare_gmet_pte(bool user)
+{
+	extern u8 start;
+	u64 address = (u64)&start & ~(1 << 21);
+	u64 mask = user ? PT_USER_MASK : 0;
+	u64 *pte;
+	int i;
+
+
+	/* flip the U bit on the 2 MiB region where the code is loaded.
+	 * the U bit is only used for execution, therefore page table accesses ignore it
+	 */
+	pte = npt_get_pte(address);
+	for (i = 0; i < 512; i++)
+		pte[i] = (pte[i] & ~PT_USER_MASK) | mask;
+}
+
 bool smp_supported(void)
 {
 	return cpu_count() > 1;
diff --git a/x86/svm.h b/x86/svm.h
index 947206bb..c5695b37 100644
--- a/x86/svm.h
+++ b/x86/svm.h
@@ -418,6 +418,7 @@ u64 *npt_get_pte(u64 address);
 u64 *npt_get_pde(u64 address);
 u64 *npt_get_pdpe(u64 address);
 u64 *npt_get_pml4e(void);
+void npt_prepare_gmet_pte(bool user);
 bool smp_supported(void);
 bool default_supported(void);
 bool fep_supported(void);
diff --git a/x86/svm_npt.c b/x86/svm_npt.c
index bd5e8f35..75d9c2c9 100644
--- a/x86/svm_npt.c
+++ b/x86/svm_npt.c
@@ -87,6 +87,79 @@ static bool npt_us_check(struct svm_test *test)
 	    && (vmcb->control.exit_info_1 == 0x100000005ULL);
 }
 
+static bool npt_gmet_supported(void)
+{
+	return npt_supported() && this_cpu_has(X86_FEATURE_GMET);
+}
+
+static void npt_gmet_null_prepare(struct svm_test *test)
+{
+	/* set U=0 - no failure */
+	npt_prepare_gmet_pte(false);
+	vmcb->control.nested_ctl |= SVM_NESTED_GMET;
+}
+
+static bool npt_gmet_null_check(struct svm_test *test)
+{
+	/* reset U=1 */
+	npt_prepare_gmet_pte(true);
+	vmcb->control.nested_ctl &= ~SVM_NESTED_GMET;
+        return vmcb->control.exit_code == SVM_EXIT_VMMCALL;
+}
+
+static void npt_gmet_nx_prepare(struct svm_test *test)
+{
+	u64 *pte = npt_get_pte((u64) null_test);
+
+	/* set U=0 - failure will be from NX */
+	npt_prepare_gmet_pte(false);
+	*pte |= PT64_NX_MASK;
+	vmcb->control.nested_ctl |= SVM_NESTED_GMET;
+
+	test->scratch = rdmsr(MSR_EFER);
+	wrmsr(MSR_EFER, test->scratch | EFER_NX);
+}
+
+static bool npt_gmet_nx_check(struct svm_test *test)
+{
+	u64 *pte = npt_get_pte((u64) null_test);
+
+	/* reset U=1, NX=0 */
+	npt_prepare_gmet_pte(true);
+	*pte &= ~PT64_NX_MASK;
+	vmcb->control.nested_ctl &= ~SVM_NESTED_GMET;
+
+	wrmsr(MSR_EFER, test->scratch);
+
+	/* errata 1218 - the U bit in the page fault error code may be incorrect */
+	return (vmcb->control.exit_code == SVM_EXIT_NPF)
+	    && ((vmcb->control.exit_info_1 & ~PFERR_USER_MASK) == 0x100000011ULL);
+}
+
+static void npt_gmet_us_prepare(struct svm_test *test)
+{
+	u64 *pte = npt_get_pte((u64) null_test);
+
+	npt_prepare_gmet_pte(false);
+	*pte |= PT_USER_MASK;
+	vmcb->control.nested_ctl |= SVM_NESTED_GMET;
+
+	test->scratch = rdmsr(MSR_EFER);
+	wrmsr(MSR_EFER, test->scratch | EFER_NX);
+}
+
+static bool npt_gmet_us_check(struct svm_test *test)
+{
+	npt_prepare_gmet_pte(true);
+	vmcb->control.nested_ctl &= ~SVM_NESTED_GMET;
+
+	wrmsr(MSR_EFER, test->scratch);
+
+	/* errata 1218 - the U bit in the page fault error code may be incorrect */
+	return (vmcb->control.exit_code == SVM_EXIT_NPF)
+	    && ((vmcb->control.exit_info_1 & ~PFERR_USER_MASK) == 0x100000011ULL);
+}
+
 static void npt_rw_prepare(struct svm_test *test)
 {
 
@@ -380,9 +453,9 @@ skip_pte_test:
 	vmcb->save.cr4 = sg_cr4;
 }
 
-#define NPT_V1_TEST(name, prepare, guest_code, check)				\
+#define NPT_V1_TEST(name, prepare, guest_code, check, more...)				\
 	{ #name, npt_supported, prepare, default_prepare_gif_clear, guest_code,	\
-	  default_finished, check }
+	  default_finished, check, more }
 
 #define NPT_V2_TEST(name) { #name, .v2 = name }
 
@@ -390,6 +463,12 @@ static struct svm_test npt_tests[] = {
 	NPT_V1_TEST(npt_nx, npt_nx_prepare, null_test, npt_nx_check),
 	NPT_V1_TEST(npt_np, npt_np_prepare, npt_np_test, npt_np_check),
 	NPT_V1_TEST(npt_us, npt_us_prepare, npt_us_test, npt_us_check),
+	NPT_V1_TEST(npt_gmet_null, npt_gmet_null_prepare, null_test, npt_gmet_null_check,
+		.supported = npt_gmet_supported),
+	NPT_V1_TEST(npt_gmet_nx, npt_gmet_nx_prepare, null_test, npt_gmet_nx_check,
+		.supported = npt_gmet_supported),
+	NPT_V1_TEST(npt_gmet_us, npt_gmet_us_prepare, null_test, npt_gmet_us_check,
+		.supported = npt_gmet_supported),
 	NPT_V1_TEST(npt_rw, npt_rw_prepare, npt_rw_test, npt_rw_check),
 	NPT_V1_TEST(npt_rw_pfwalk, npt_rw_pfwalk_prepare, null_test, npt_rw_pfwalk_check),
 	NPT_V1_TEST(npt_l1mmio, npt_l1mmio_prepare, npt_l1mmio_test, npt_l1mmio_check),
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH kvm-unit-tests 4/9] x86/vmx: update EPT installation to use EPT_PRESENT flag
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
                   ` (2 preceding siblings ...)
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 3/9] svm: add basic GMET tests Paolo Bonzini
@ 2026-03-26 14:50 ` Paolo Bonzini
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 5/9] x86/vmx: diagnose unexpected EPT violations Paolo Bonzini
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

From: Jon Kohler <jon@nutanix.com>

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; this
will allow adding the XU bit to all upper-level page tables
easily when MBEC is active.

Note that EPT_PRESENT is not used testing the RWX combination
specifically.

No functional change intended.

Signed-off-by: Jon Kohler <jon@nutanix.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 x86/vmx.c       |  3 +--
 x86/vmx.h       | 13 +++++++------
 x86/vmx_tests.c | 24 ++++++++++++------------
 3 files changed, 20 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 33373bd1..d9f493d3 100644
--- a/x86/vmx.h
+++ b/x86/vmx.h
@@ -664,18 +664,19 @@ 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_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		(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 5ffb80a3..707e8ca4 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.52.0



^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH kvm-unit-tests 5/9] x86/vmx: diagnose unexpected EPT violations
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
                   ` (3 preceding siblings ...)
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 4/9] x86/vmx: update EPT installation to use EPT_PRESENT flag Paolo Bonzini
@ 2026-03-26 14:50 ` Paolo Bonzini
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 6/9] x86/vmx: add mode-based execute control test for Skylake and above Paolo Bonzini
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

Knowing the exit qualification when a test incorrectly raises an
EPT violation greatly simplifies debugging.  This requires a tweak to
__TEST_EQ, allowing any code block instead of just a function name for
the abort code.

For this particular case, include advanced vmexit info in the diagnosis.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 lib/util.h      | 10 +++-----
 x86/vmx.h       |  8 +++---
 x86/vmx_tests.c | 65 ++++++++++++++++++++++++++++---------------------
 3 files changed, 44 insertions(+), 39 deletions(-)

diff --git a/lib/util.h b/lib/util.h
index 00d0b47d..93f16410 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -41,17 +41,13 @@ do {											\
 			    (unsigned long) _b, _bin_b, (unsigned long) _b,		\
 			    fmt[0] == '\0' ? "" : "\n", ## args);			\
 		dump_stack();								\
-		if (assertion)								\
-			do_abort();							\
+		if (assertion) { do_abort; }						\
 	}										\
 	report_passed();								\
 } while (0)
 
-/* FIXME: Extend VMX's assert/abort framework to SVM and other environs. */
-static inline void dummy_abort(void) {}
-
-#define TEST_EXPECT_EQ(a, b) __TEST_EQ(a, b, #a, #b, 0, dummy_abort, "")
+#define TEST_EXPECT_EQ(a, b) __TEST_EQ(a, b, #a, #b, 0, , "")
 #define TEST_EXPECT_EQ_MSG(a, b, fmt, args...) \
-	__TEST_EQ(a, b, #a, #b, 0, dummy_abort fmt, ## args)
+	__TEST_EQ(a, b, #a, #b, 0, fmt, ## args)
 
 #endif
diff --git a/x86/vmx.h b/x86/vmx.h
index d9f493d3..0e29a57d 100644
--- a/x86/vmx.h
+++ b/x86/vmx.h
@@ -37,9 +37,9 @@ do {								\
 	report_passed();					\
 } while (0)
 
-#define TEST_ASSERT_EQ(a, b) __TEST_EQ(a, b, #a, #b, 1, __abort_test, "")
+#define TEST_ASSERT_EQ(a, b) __TEST_EQ(a, b, #a, #b, 1, __abort_test(), "")
 #define TEST_ASSERT_EQ_MSG(a, b, fmt, args...) \
-	__TEST_EQ(a, b, #a, #b, 1, __abort_test, fmt, ## args)
+	__TEST_EQ(a, b, #a, #b, 1, __abort_test(), fmt, ## args)
 
 struct vmcs_hdr {
 	u32 revision_id:31;
@@ -718,9 +718,9 @@ enum vm_entry_failure_code {
 #define EPT_VLT_PADDR		(1ull << 8)
 #define EPT_VLT_GUEST_USER	(1ull << 9)
 #define EPT_VLT_GUEST_RW	(1ull << 10)
-#define EPT_VLT_GUEST_EX	(1ull << 11)
+#define EPT_VLT_GUEST_NX	(1ull << 11)
 #define EPT_VLT_GUEST_MASK	(EPT_VLT_GUEST_USER | EPT_VLT_GUEST_RW | \
-				 EPT_VLT_GUEST_EX)
+				 EPT_VLT_GUEST_NX)
 
 #define MAGIC_VAL_1		0x12345678ul
 #define MAGIC_VAL_2		0x87654321ul
diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index 707e8ca4..0e3dca3c 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -2157,13 +2157,46 @@ static int exit_monitor_from_l2_handler(union exit_reason exit_reason)
 	return VMX_TEST_EXIT;
 }
 
+static void
+diagnose_ept_violation_qual(u64 expected, u64 actual)
+{
+
+#define DIAGNOSE(flag)							\
+do {									\
+	if ((expected & flag) != (actual & flag))			\
+		printf(#flag " %sexpected\n",				\
+		       (expected & flag) ? "" : "un");			\
+} while (0)
+
+	DIAGNOSE(EPT_VLT_RD);
+	DIAGNOSE(EPT_VLT_WR);
+	DIAGNOSE(EPT_VLT_FETCH);
+	DIAGNOSE(EPT_VLT_PERM_RD);
+	DIAGNOSE(EPT_VLT_PERM_WR);
+	DIAGNOSE(EPT_VLT_PERM_EX);
+	DIAGNOSE(EPT_VLT_LADDR_VLD);
+	DIAGNOSE(EPT_VLT_PADDR);
+	DIAGNOSE(EPT_VLT_GUEST_USER);
+	DIAGNOSE(EPT_VLT_GUEST_RW);
+	DIAGNOSE(EPT_VLT_GUEST_NX);
+
+#undef DIAGNOSE
+}
+
 static void assert_exit_reason(u64 expected)
 {
 	u64 actual = vmcs_read(EXI_REASON);
 
-	TEST_ASSERT_EQ_MSG(expected, actual, "Expected %s, got %s.",
-			   exit_reason_description(expected),
-			   exit_reason_description(actual));
+        __TEST_EQ(expected, actual, "expected", "actual", 1, {
+		printf("guest linear address %lx\n", vmcs_read(GUEST_LINEAR_ADDRESS));
+		if (actual == VMX_EPT_VIOLATION) {
+			u64 qual = vmcs_read(EXI_QUALIFICATION);
+			diagnose_ept_violation_qual(0, qual);
+		}
+		__abort_test();
+	}, "Expected %s, got %s.",
+		   exit_reason_description(expected),
+		   exit_reason_description(actual));
 }
 
 static void skip_exit_insn(void)
@@ -2276,29 +2309,6 @@ asm(
 	"ret42_end:\n"
 );
 
-static void
-diagnose_ept_violation_qual(u64 expected, u64 actual)
-{
-
-#define DIAGNOSE(flag)							\
-do {									\
-	if ((expected & flag) != (actual & flag))			\
-		printf(#flag " %sexpected\n",				\
-		       (expected & flag) ? "" : "un");			\
-} while (0)
-
-	DIAGNOSE(EPT_VLT_RD);
-	DIAGNOSE(EPT_VLT_WR);
-	DIAGNOSE(EPT_VLT_FETCH);
-	DIAGNOSE(EPT_VLT_PERM_RD);
-	DIAGNOSE(EPT_VLT_PERM_WR);
-	DIAGNOSE(EPT_VLT_PERM_EX);
-	DIAGNOSE(EPT_VLT_LADDR_VLD);
-	DIAGNOSE(EPT_VLT_PADDR);
-
-#undef DIAGNOSE
-}
-
 static void do_ept_access_op(enum ept_access_op op)
 {
 	ept_access_test_data.op = op;
@@ -2360,8 +2370,7 @@ 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);
+	qual &= ~(EPT_VLT_GUEST_MASK | EPT_VLT_PERM_USER_EX);
 
 	diagnose_ept_violation_qual(expected_qual, qual);
 	TEST_EXPECT_EQ(expected_qual, qual);
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH kvm-unit-tests 6/9] x86/vmx: add mode-based execute control test for Skylake and above
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
                   ` (4 preceding siblings ...)
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 5/9] x86/vmx: diagnose unexpected EPT violations Paolo Bonzini
@ 2026-03-26 14:50 ` Paolo Bonzini
  2026-03-27 15:57   ` Jon Kohler
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 7/9] x86/vmx: add user execution operation to EPT access tests Paolo Bonzini
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

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.

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

Co-authored-by: Jon Kohler <jon@nutanix.com>
Signed-off-by: Jon Kohler <jon@nutanix.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 x86/unittests.cfg |  9 +++++++
 x86/vmx.h         |  8 ++++++
 x86/vmx_tests.c   | 64 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 81 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 0e29a57d..b492ec74 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,
 };
 
@@ -843,6 +844,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 0e3dca3c..dbc456cb 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -4876,6 +4876,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
@@ -5336,6 +5399,7 @@ static void test_vm_execution_ctls(void)
 	test_pml();
 	test_vpid();
 	test_ept_eptp();
+	test_mode_based_execute_control();
 	test_vmx_preemption_timer();
 }
 
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH kvm-unit-tests 7/9] x86/vmx: add user execution operation to EPT access tests
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
                   ` (5 preceding siblings ...)
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 6/9] x86/vmx: add mode-based execute control test for Skylake and above Paolo Bonzini
@ 2026-03-26 14:50 ` Paolo Bonzini
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available Paolo Bonzini
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

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.

Since the tests do not support MBEC yet, the expected behavior is the
same as for OP_EXEC.

Co-authored-by: Jon Kohler <jon@nutanix.com>
Signed-off-by: Jon Kohler <jon@nutanix.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 x86/vmx_tests.c | 51 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 47 insertions(+), 4 deletions(-)

diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index dbc456cb..023512e6 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -2285,6 +2285,7 @@ enum ept_access_op {
 	OP_READ,
 	OP_WRITE,
 	OP_EXEC,
+	OP_EXEC_USER,
 	OP_FLUSH_TLB,
 	OP_EXIT,
 };
@@ -2399,8 +2400,8 @@ ept_violation_at_level_mkhuge(bool mkhuge, int level, unsigned long clear,
 	orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set);
 
 	do_ept_violation(level == 1 || mkhuge, op, expected_qual,
-			 op == OP_EXEC ? data->gpa + sizeof(unsigned long) :
-					 data->gpa);
+			 (op == OP_EXEC || op == OP_EXEC_USER
+			  ? data->gpa + sizeof(unsigned long) : data->gpa));
 
 	/* Fix the violation and resume the op loop. */
 	ept_untwiddle(data->gpa, level, orig_pte);
@@ -2589,11 +2590,13 @@ static void ept_ignored_bit(int bit)
 	ept_allowed(0, 1ul << bit, OP_READ);
 	ept_allowed(0, 1ul << bit, OP_WRITE);
 	ept_allowed(0, 1ul << bit, OP_EXEC);
+	ept_allowed(0, 1ul << bit, OP_EXEC_USER);
 
 	/* Clear the bit. */
 	ept_allowed(1ul << bit, 0, OP_READ);
 	ept_allowed(1ul << bit, 0, OP_WRITE);
 	ept_allowed(1ul << bit, 0, OP_EXEC);
+	ept_allowed(1ul << bit, 0, OP_EXEC_USER);
 }
 
 static void ept_access_allowed(unsigned long access, enum ept_access_op op)
@@ -2726,10 +2729,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) {
@@ -2744,6 +2757,11 @@ static void ept_access_test_guest(void)
 		case OP_EXEC:
 			TEST_ASSERT_EQ(42, code());
 			break;
+		case OP_EXEC_USER:
+			ret_val = run_in_user(exec_user_on_gva, DE_VECTOR, // no exceptions
+					      0, 0, 0, 0, &unused);
+			TEST_ASSERT_EQ(42, ret_val);
+			break;
 		case OP_FLUSH_TLB:
 			write_cr3(read_cr3());
 			break;
@@ -2803,6 +2821,7 @@ 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);
+	ept_access_violation(0, OP_EXEC_USER, EPT_VLT_FETCH);
 }
 
 static void ept_access_test_read_only(void)
@@ -2813,6 +2832,7 @@ 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);
+	ept_access_violation(EPT_RA, OP_EXEC_USER, EPT_VLT_FETCH | EPT_VLT_PERM_RD);
 }
 
 static void ept_access_test_write_only(void)
@@ -2829,7 +2849,11 @@ 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);
+	ept_access_violation(EPT_RA | EPT_WA, OP_EXEC_USER,
+			     EPT_VLT_FETCH | EPT_VLT_PERM_RD |
+			     EPT_VLT_PERM_WR);
 }
 
 
@@ -2843,6 +2867,7 @@ 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);
+		ept_access_allowed(EPT_EA, OP_EXEC_USER);
 	} else {
 		ept_access_misconfig(EPT_EA);
 	}
@@ -2854,8 +2879,9 @@ 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);
+	ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC_USER);
 }
 
 static void ept_access_test_write_execute(void)
@@ -2872,6 +2898,7 @@ 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);
+	ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_EXEC_USER);
 }
 
 static void ept_access_test_reserved_bits(void)
@@ -2952,6 +2979,7 @@ 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);
+	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)
@@ -2964,6 +2992,7 @@ 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);
+	ept_access_violation_paddr(0, PT_AD_MASK, OP_EXEC_USER, qual);
 }
 
 static void ept_access_test_paddr_read_only_ad_disabled(void)
@@ -2983,14 +3012,17 @@ static void ept_access_test_paddr_read_only_ad_disabled(void)
 	ept_access_violation_paddr(EPT_RA, 0, OP_READ, qual);
 	ept_access_violation_paddr(EPT_RA, 0, OP_WRITE, qual);
 	ept_access_violation_paddr(EPT_RA, 0, OP_EXEC, qual);
+	ept_access_violation_paddr(EPT_RA, 0, OP_EXEC_USER, qual);
 	/* AD bits disabled, so only writes try to update the D bit. */
 	ept_access_allowed_paddr(EPT_RA, PT_ACCESSED_MASK, OP_READ);
 	ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_WRITE, qual);
 	ept_access_allowed_paddr(EPT_RA, PT_ACCESSED_MASK, OP_EXEC);
+	ept_access_allowed_paddr(EPT_RA, PT_ACCESSED_MASK, OP_EXEC_USER);
 	/* Both A and D already set, so read-only is OK. */
 	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);
+	ept_access_allowed_paddr(EPT_RA, PT_AD_MASK, OP_EXEC_USER);
 }
 
 static void ept_access_test_paddr_read_only_ad_enabled(void)
@@ -3008,12 +3040,15 @@ static void ept_access_test_paddr_read_only_ad_enabled(void)
 	ept_access_violation_paddr(EPT_RA, 0, OP_READ, qual);
 	ept_access_violation_paddr(EPT_RA, 0, OP_WRITE, qual);
 	ept_access_violation_paddr(EPT_RA, 0, OP_EXEC, qual);
+	ept_access_violation_paddr(EPT_RA, 0, OP_EXEC_USER, qual);
 	ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_READ, qual);
 	ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_WRITE, qual);
 	ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_EXEC, qual);
+	ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_EXEC_USER, qual);
 	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);
+	ept_access_violation_paddr(EPT_RA, PT_AD_MASK, OP_EXEC_USER, qual);
 }
 
 static void ept_access_test_paddr_read_write(void)
@@ -3023,6 +3058,7 @@ 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);
+	ept_access_allowed_paddr(EPT_RA | EPT_WA, 0, OP_EXEC_USER);
 }
 
 static void ept_access_test_paddr_read_write_execute(void)
@@ -3032,6 +3068,7 @@ 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);
+	ept_access_allowed_paddr(EPT_RA | EPT_WA | EPT_EA, 0, OP_EXEC_USER);
 }
 
 static void ept_access_test_paddr_read_execute_ad_disabled(void)
@@ -3051,14 +3088,17 @@ static void ept_access_test_paddr_read_execute_ad_disabled(void)
 	ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_READ, qual);
 	ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_WRITE, qual);
 	ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_EXEC, qual);
+	ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_EXEC_USER, qual);
 	/* AD bits disabled, so only writes try to update the D bit. */
 	ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_READ);
 	ept_access_violation_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_WRITE, qual);
 	ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_EXEC);
+	ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_EXEC_USER);
 	/* Both A and D already set, so read-only is OK. */
 	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);
+	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)
@@ -3076,12 +3116,15 @@ static void ept_access_test_paddr_read_execute_ad_enabled(void)
 	ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_READ, qual);
 	ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_WRITE, qual);
 	ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_EXEC, qual);
+	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_READ, qual);
 	ept_access_violation_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_WRITE, qual);
 	ept_access_violation_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_EXEC, 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_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);
+	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.52.0



^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
                   ` (6 preceding siblings ...)
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 7/9] x86/vmx: add user execution operation to EPT access tests Paolo Bonzini
@ 2026-03-26 14:50 ` Paolo Bonzini
  2026-03-26 16:13   ` Paolo Bonzini
  2026-03-27 15:57   ` Jon Kohler
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 9/9] x86/vmx: add EPT tests covering XU permission Paolo Bonzini
  2026-05-12 11:06 ` [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
  9 siblings, 2 replies; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

Check that the XS bit does not allow execution of user-mode pages
when MBEC is available (and enabled); this requires tweaking
the guest page tables to set U=0 for OP_EXEC.  Update the unit test
configuration to include a specific test case for MBEC.

Co-authored-by: Jon Kohler <jon@nutanix.com>
Signed-off-by: Jon Kohler <jon@nutanix.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 x86/unittests.cfg | 12 +++++-
 x86/vmx.h         |  5 ++-
 x86/vmx_tests.c   | 98 ++++++++++++++++++++++++++++++++++++++---------
 3 files changed, 94 insertions(+), 21 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.h b/x86/vmx.h
index b492ec74..7ad7672a 100644
--- a/x86/vmx.h
+++ b/x86/vmx.h
@@ -672,11 +672,14 @@ enum vm_entry_failure_code {
 #define EPT_LARGE_PAGE		(1ul << 7)
 #define EPT_ACCESS_FLAG		(1ul << 8)
 #define EPT_DIRTY_FLAG		(1ul << 9)
+#define EPT_EA_USER		(1ul << 10)
 #define EPT_MEM_TYPE_SHIFT	3ul
 #define EPT_MEM_TYPE_MASK	0x7ul
 #define EPT_SUPPRESS_VE		(1ull << 63)
 
-#define EPT_PRESENT		(EPT_RA | EPT_WA | EPT_EA)
+#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)
diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index 023512e6..bf03451a 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -1044,6 +1044,8 @@ static int insn_intercept_exit_handler(union exit_reason exit_reason)
  */
 static int __setup_ept(u64 hpa, bool enable_ad)
 {
+	u64 secondary;
+
 	if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) ||
 	    !(ctrl_cpu_rev[1].clr & CPU_EPT)) {
 		printf("\tEPT is not supported\n");
@@ -1067,9 +1069,13 @@ static int __setup_ept(u64 hpa, bool enable_ad)
 	if (enable_ad)
 		eptp |= EPTP_AD_FLAG;
 
+	secondary = vmcs_read(CPU_EXEC_CTRL1) | CPU_EPT;
+	if (is_mbec_supported())
+		secondary |= CPU_MODE_BASED_EPT_EXEC;
+
 	vmcs_write(EPTP, eptp);
 	vmcs_write(CPU_EXEC_CTRL0, vmcs_read(CPU_EXEC_CTRL0)| CPU_SECONDARY);
-	vmcs_write(CPU_EXEC_CTRL1, vmcs_read(CPU_EXEC_CTRL1)| CPU_EPT);
+	vmcs_write(CPU_EXEC_CTRL1, secondary);
 
 	return 0;
 }
@@ -2174,6 +2180,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);
 	DIAGNOSE(EPT_VLT_GUEST_USER);
@@ -2326,13 +2333,36 @@ static void ept_access_test_guest_flush_tlb(void)
 	skip_exit_vmcall();
 }
 
+/*
+ * Modifies the leaf guest page table entry that maps @gva, clearing the bits
+ * in @clear then setting the bits in @set.  This is needed when testing
+ * MBEC so that the processor knows whether to observe XS or XU.
+ */
+static void guest_page_table_twiddle(unsigned long *gva, unsigned long clear, unsigned long set)
+{
+	pgd_t *cr3 = current_page_table();
+	int i;
+
+	for (i = 1; i <= PAGE_LEVEL; i++) {
+		u64 *pte = get_pte_level(cr3, gva, i);
+		if (!pte)
+			continue;
+
+		TEST_ASSERT(*pte & PT_PRESENT_MASK);
+		*pte = (*pte & ~clear) | set;
+		break;
+	}
+	invlpg((void *)gva);
+}
+
 /*
  * Modifies the EPT entry at @level in the mapping of @gpa. First clears the
  * bits in @clear then sets the bits in @set. @mkhuge transforms the entry into
  * a huge page.
  */
 static unsigned long ept_twiddle(unsigned long gpa, bool mkhuge, int level,
-				 unsigned long clear, unsigned long set)
+				 unsigned long clear, unsigned long set,
+				 enum ept_access_op op)
 {
 	struct ept_access_test_data *data = &ept_access_test_data;
 	unsigned long orig_pte;
@@ -2347,15 +2377,27 @@ static unsigned long ept_twiddle(unsigned long gpa, bool mkhuge, int level,
 		pte = orig_pte;
 	pte = (pte & ~clear) | set;
 	set_ept_pte(pml4, gpa, level, pte);
-	invept(INVEPT_SINGLE, eptp);
 
+	if (is_mbec_supported() && op == OP_EXEC)
+		guest_page_table_twiddle(data->gva, PT_USER_MASK, 0);
+
+	invept(INVEPT_SINGLE, eptp);
 	return orig_pte;
 }
 
-static void ept_untwiddle(unsigned long gpa, int level, unsigned long orig_pte)
+static void ept_untwiddle(unsigned long gpa, int level, unsigned long orig_pte,
+			  enum ept_access_op op)
 {
+	unsigned long pte;
+
+	pte = get_ept_pte(pml4, gpa, level, &pte);
 	set_ept_pte(pml4, gpa, level, orig_pte);
 	invept(INVEPT_SINGLE, eptp);
+
+	if (is_mbec_supported() && op == OP_EXEC) {
+		struct ept_access_test_data *data = &ept_access_test_data;
+		guest_page_table_twiddle(data->gva, 0, PT_USER_MASK);
+	}
 }
 
 static void do_ept_violation(bool leaf, enum ept_access_op op,
@@ -2370,8 +2412,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_MASK | EPT_VLT_PERM_USER_EX);
+	/*
+	 * Exit-qualifications are masked not to account for advanced
+	 * VM-exit information. KVM supports this feature, so the tests
+	 * could be enhanced to cover it.
+	 */
+	qual &= ~EPT_VLT_GUEST_MASK;
 
 	diagnose_ept_violation_qual(expected_qual, qual);
 	TEST_EXPECT_EQ(expected_qual, qual);
@@ -2397,14 +2443,14 @@ ept_violation_at_level_mkhuge(bool mkhuge, int level, unsigned long clear,
 	struct ept_access_test_data *data = &ept_access_test_data;
 	unsigned long orig_pte;
 
-	orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set);
+	orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set, op);
 
 	do_ept_violation(level == 1 || mkhuge, op, expected_qual,
 			 (op == OP_EXEC || op == OP_EXEC_USER
 			  ? data->gpa + sizeof(unsigned long) : data->gpa));
 
 	/* Fix the violation and resume the op loop. */
-	ept_untwiddle(data->gpa, level, orig_pte);
+	ept_untwiddle(data->gpa, level, orig_pte, op);
 	enter_guest();
 	skip_exit_vmcall();
 }
@@ -2502,12 +2548,12 @@ static void ept_access_paddr(unsigned long ept_access, unsigned long pte_ad,
 	 */
 	install_ept(pml4, gpa, gpa, EPT_PRESENT);
 	orig_epte = ept_twiddle(gpa, /*mkhuge=*/0, /*level=*/1,
-				/*clear=*/EPT_PRESENT, /*set=*/ept_access);
+				/*clear=*/EPT_PRESENT, /*set=*/ept_access, op);
 
 	if (expect_violation) {
 		do_ept_violation(/*leaf=*/true, op,
 				 expected_qual | EPT_VLT_LADDR_VLD, gpa);
-		ept_untwiddle(gpa, /*level=*/1, orig_epte);
+		ept_untwiddle(gpa, /*level=*/1, orig_epte, op);
 		do_ept_access_op(op);
 	} else {
 		do_ept_access_op(op);
@@ -2522,7 +2568,7 @@ static void ept_access_paddr(unsigned long ept_access, unsigned long pte_ad,
 			}
 		}
 
-		ept_untwiddle(gpa, /*level=*/1, orig_epte);
+		ept_untwiddle(gpa, /*level=*/1, orig_epte, op);
 	}
 
 	TEST_ASSERT(*ptep & PT_ACCESSED_MASK);
@@ -2558,13 +2604,13 @@ static void ept_allowed_at_level_mkhuge(bool mkhuge, int level,
 	struct ept_access_test_data *data = &ept_access_test_data;
 	unsigned long orig_pte;
 
-	orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set);
+	orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set, op);
 
 	/* No violation. Should proceed to vmcall. */
 	do_ept_access_op(op);
 	skip_exit_vmcall();
 
-	ept_untwiddle(data->gpa, level, orig_pte);
+	ept_untwiddle(data->gpa, level, orig_pte, op);
 }
 
 static void ept_allowed_at_level(int level, unsigned long clear,
@@ -2613,7 +2659,7 @@ static void ept_misconfig_at_level_mkhuge_op(bool mkhuge, int level,
 	struct ept_access_test_data *data = &ept_access_test_data;
 	unsigned long orig_pte;
 
-	orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set);
+	orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set, op);
 
 	do_ept_access_op(op);
 	assert_exit_reason(VMX_EPT_MISCONFIG);
@@ -2637,7 +2683,7 @@ static void ept_misconfig_at_level_mkhuge_op(bool mkhuge, int level,
 	#endif
 
 	/* Fix the violation and resume the op loop. */
-	ept_untwiddle(data->gpa, level, orig_pte);
+	ept_untwiddle(data->gpa, level, orig_pte, op);
 	enter_guest();
 	skip_exit_vmcall();
 }
@@ -2867,7 +2913,12 @@ 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);
-		ept_access_allowed(EPT_EA, OP_EXEC_USER);
+		if (is_mbec_supported())
+			ept_access_violation(EPT_EA, OP_EXEC_USER,
+					     EPT_VLT_FETCH |
+					     EPT_VLT_PERM_EX);
+		else
+			ept_access_allowed(EPT_EA, OP_EXEC_USER);
 	} else {
 		ept_access_misconfig(EPT_EA);
 	}
@@ -2881,7 +2932,11 @@ static void ept_access_test_read_execute(void)
 	ept_access_violation(EPT_RA | EPT_EA, OP_WRITE,
 			     EPT_VLT_WR | EPT_VLT_PERM_RD | EPT_VLT_PERM_EX);
 	ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC);
-	ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC_USER);
+	if (is_mbec_supported())
+		ept_access_violation(EPT_RA | EPT_EA, OP_EXEC_USER,
+				     EPT_VLT_FETCH | EPT_VLT_PERM_RD | EPT_VLT_PERM_EX);
+	else
+		ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC_USER);
 }
 
 static void ept_access_test_write_execute(void)
@@ -2898,7 +2953,11 @@ 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);
-	ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_EXEC_USER);
+	if (is_mbec_supported())
+		ept_access_violation(EPT_RA | EPT_WA | EPT_EA, OP_EXEC_USER,
+				     EPT_VLT_FETCH | EPT_VLT_PERM_RD | EPT_VLT_PERM_WR | EPT_VLT_PERM_EX);
+	else
+		ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_EXEC_USER);
 }
 
 static void ept_access_test_reserved_bits(void)
@@ -2955,7 +3014,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.52.0



^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH kvm-unit-tests 9/9] x86/vmx: add EPT tests covering XU permission
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
                   ` (7 preceding siblings ...)
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available Paolo Bonzini
@ 2026-03-26 14:50 ` Paolo Bonzini
  2026-03-27 15:56   ` Jon Kohler
  2026-05-12 11:06 ` [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
  9 siblings, 1 reply; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 14:50 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

Add tests to validate MBEC execute access when XU=1, with and without XS=1.

Co-authored-by: Jon Kohler <jon@nutanix.com>
Signed-off-by: Jon Kohler <jon@nutanix.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 x86/vmx_tests.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 120 insertions(+)

diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index bf03451a..a47e8470 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -2924,6 +2924,52 @@ 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()) {
+		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);
+		ept_access_allowed(EPT_EA_USER, OP_EXEC_USER);
+	} else {
+		ept_access_misconfig(EPT_EA_USER);
+	}
+}
+
+static void ept_access_test_execute_both(void)
+{
+	if (!is_mbec_supported()) {
+		report_skip("MBEC not supported");
+		return;
+	}
+
+	ept_access_test_setup();
+	/* --x (both XS and XU) */
+	if (ept_execute_only_supported()) {
+		ept_access_violation(EPT_EA | EPT_EA_USER, OP_READ,
+				     EPT_VLT_RD | EPT_VLT_PERM_EX | EPT_VLT_PERM_USER_EX);
+		ept_access_violation(EPT_EA | EPT_EA_USER, OP_WRITE,
+				     EPT_VLT_WR | EPT_VLT_PERM_EX | EPT_VLT_PERM_USER_EX);
+		ept_access_allowed(EPT_EA | EPT_EA_USER, OP_EXEC);
+		ept_access_allowed(EPT_EA | EPT_EA_USER, OP_EXEC_USER);
+	} else {
+		ept_access_misconfig(EPT_EA | EPT_EA_USER);
+	}
+}
+
 static void ept_access_test_read_execute(void)
 {
 	ept_access_test_setup();
@@ -2939,6 +2985,43 @@ static void ept_access_test_read_execute(void)
 		ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC_USER);
 }
 
+static void ept_access_test_read_execute_user_only(void)
+{
+	if (!is_mbec_supported()) {
+		report_skip("MBEC not supported");
+		return;
+	}
+
+	ept_access_test_setup();
+	/* r-x (exec user only) */
+	ept_access_allowed(EPT_RA | EPT_EA_USER, OP_READ);
+	ept_access_violation(EPT_RA | EPT_EA_USER, OP_WRITE,
+			     EPT_VLT_WR | EPT_VLT_PERM_RD |
+			     EPT_VLT_PERM_USER_EX);
+	ept_access_violation(EPT_RA | EPT_EA_USER, OP_EXEC,
+			     EPT_VLT_FETCH | EPT_VLT_PERM_RD |
+			     EPT_VLT_PERM_USER_EX);
+	ept_access_allowed(EPT_RA | EPT_EA_USER, OP_EXEC_USER);
+}
+
+static void ept_access_test_read_execute_both(void)
+{
+	if (!is_mbec_supported()) {
+		report_skip("MBEC not supported");
+		return;
+	}
+
+	ept_access_test_setup();
+	/* r-x (both XS and XU) */
+	ept_access_allowed(EPT_RA | EPT_EA | EPT_EA_USER, OP_READ);
+	ept_access_violation(EPT_RA | EPT_EA | EPT_EA_USER, OP_WRITE,
+			     EPT_VLT_WR | EPT_VLT_PERM_RD |
+			     EPT_VLT_PERM_EX | EPT_VLT_PERM_USER_EX);
+	ept_access_allowed(EPT_RA | EPT_EA | EPT_EA_USER, OP_EXEC);
+	ept_access_allowed(EPT_RA | EPT_EA | EPT_EA_USER, OP_EXEC_USER);
+}
+
+
 static void ept_access_test_write_execute(void)
 {
 	ept_access_test_setup();
@@ -2960,6 +3043,37 @@ static void ept_access_test_read_write_execute(void)
 		ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_EXEC_USER);
 }
 
+static void ept_access_test_read_write_execute_user_only(void)
+{
+	if (!is_mbec_supported()) {
+		report_skip("MBEC not supported");
+		return;
+	}
+
+	ept_access_test_setup();
+	/* rwx (exec user only) */
+	ept_access_allowed(EPT_RA | EPT_WA | EPT_EA_USER, OP_READ);
+	ept_access_allowed(EPT_RA | EPT_WA | EPT_EA_USER, OP_WRITE);
+	ept_access_violation(EPT_RA | EPT_WA | EPT_EA_USER, OP_EXEC,
+			     EPT_VLT_FETCH | EPT_VLT_PERM_RD | EPT_VLT_PERM_WR | EPT_VLT_PERM_USER_EX);
+	ept_access_allowed(EPT_RA | EPT_WA | EPT_EA_USER, OP_EXEC_USER);
+}
+
+static void ept_access_test_read_write_execute_both(void)
+{
+	if (!is_mbec_supported()) {
+		report_skip("MBEC not supported");
+		return;
+	}
+
+	ept_access_test_setup();
+	/* rwx (both XS and XU) */
+	ept_access_allowed(EPT_RA | EPT_WA | EPT_EA | EPT_EA_USER, OP_READ);
+	ept_access_allowed(EPT_RA | EPT_WA | EPT_EA | EPT_EA_USER, OP_WRITE);
+	ept_access_allowed(EPT_RA | EPT_WA | EPT_EA | EPT_EA_USER, OP_EXEC);
+	ept_access_allowed(EPT_RA | EPT_WA | EPT_EA | EPT_EA_USER, OP_EXEC_USER);
+}
+
 static void ept_access_test_reserved_bits(void)
 {
 	int i;
@@ -11722,9 +11836,15 @@ 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_execute_both),
 	TEST(ept_access_test_read_execute),
+	TEST(ept_access_test_read_execute_user_only),
+	TEST(ept_access_test_read_execute_both),
 	TEST(ept_access_test_write_execute),
 	TEST(ept_access_test_read_write_execute),
+	TEST(ept_access_test_read_write_execute_user_only),
+	TEST(ept_access_test_read_write_execute_both),
 	TEST(ept_access_test_reserved_bits),
 	TEST(ept_access_test_ignored_bits),
 	TEST(ept_access_test_paddr_not_present_ad_disabled),
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available Paolo Bonzini
@ 2026-03-26 16:13   ` Paolo Bonzini
  2026-03-27 15:57     ` Jon Kohler
  2026-03-27 15:57   ` Jon Kohler
  1 sibling, 1 reply; 17+ messages in thread
From: Paolo Bonzini @ 2026-03-26 16:13 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

On 3/26/26 15:50, Paolo Bonzini wrote:
> diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
> index 023512e6..bf03451a 100644
> --- a/x86/vmx_tests.c
> +++ b/x86/vmx_tests.c
> @@ -1044,6 +1044,8 @@ static int insn_intercept_exit_handler(union exit_reason exit_reason)
>    */
>   static int __setup_ept(u64 hpa, bool enable_ad)
>   {
> +	u64 secondary;
> +
>   	if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) ||
>   	    !(ctrl_cpu_rev[1].clr & CPU_EPT)) {
>   		printf("\tEPT is not supported\n");
> @@ -1067,9 +1069,13 @@ static int __setup_ept(u64 hpa, bool enable_ad)
>   	if (enable_ad)
>   		eptp |= EPTP_AD_FLAG;
>   
> +	secondary = vmcs_read(CPU_EXEC_CTRL1) | CPU_EPT;
> +	if (is_mbec_supported())
> +		secondary |= CPU_MODE_BASED_EPT_EXEC;
> +
>   	vmcs_write(EPTP, eptp);
>   	vmcs_write(CPU_EXEC_CTRL0, vmcs_read(CPU_EXEC_CTRL0)| CPU_SECONDARY);
> -	vmcs_write(CPU_EXEC_CTRL1, vmcs_read(CPU_EXEC_CTRL1)| CPU_EPT);
> +	vmcs_write(CPU_EXEC_CTRL1, secondary);
>   
>   	return 0;
>   }

This is missing a hunk:

diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index 023512e6..b5994719 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -4886,7 +4946,7 @@ static void test_ept_eptp(void)
                 report_prefix_pop();
         }
  
-       secondary &= ~(CPU_EPT | CPU_URG);
+       secondary &= ~(CPU_URG | CPU_EPT | CPU_MODE_BASED_EPT_EXEC);
         vmcs_write(CPU_EXEC_CTRL1, secondary);
         report_prefix_pushf("Enable-EPT disabled, unrestricted-guest disabled");
         test_vmx_valid_controls();

and possibly this as well for consistency:

diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
index b5994719..cf1022ba 100644
--- a/x86/vmx_tests.c
+++ b/x86/vmx_tests.c
@@ -5068,7 +5068,7 @@ static void test_pml(void)
  
  	primary |= CPU_SECONDARY;
  	vmcs_write(CPU_EXEC_CTRL0, primary);
-	secondary &= ~(CPU_PML | CPU_EPT);
+	secondary &= ~(CPU_PML | CPU_EPT | CPU_MODE_BASED_EPT_EXEC);
  	vmcs_write(CPU_EXEC_CTRL1, secondary);
  	report_prefix_pushf("enable-PML disabled, enable-EPT disabled");
  	test_vmx_valid_controls();

Paolo


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH kvm-unit-tests 9/9] x86/vmx: add EPT tests covering XU permission
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 9/9] x86/vmx: add EPT tests covering XU permission Paolo Bonzini
@ 2026-03-27 15:56   ` Jon Kohler
  0 siblings, 0 replies; 17+ messages in thread
From: Jon Kohler @ 2026-03-27 15:56 UTC (permalink / raw)
  To: Paolo Bonzini
  Cc: kvm@vger.kernel.org, Nikunj A Dadhania, amit.shah@amd.com,
	Sean Christopherson



> On Mar 26, 2026, at 10:50 AM, Paolo Bonzini <pbonzini@redhat.com> wrote:
> 
> Add tests to validate MBEC execute access when XU=1, with and without XS=1.
> 
> Co-authored-by: Jon Kohler <jon@nutanix.com>
> Signed-off-by: Jon Kohler <jon@nutanix.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> x86/vmx_tests.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 120 insertions(+)
> 
> diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
> index bf03451a..a47e8470 100644
> --- a/x86/vmx_tests.c
> +++ b/x86/vmx_tests.c
> @@ -2924,6 +2924,52 @@ 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()) {
> + 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);
> + ept_access_allowed(EPT_EA_USER, OP_EXEC_USER);
> + } else {
> + ept_access_misconfig(EPT_EA_USER);
> + }
> +}
> +
> +static void ept_access_test_execute_both(void)
> +{
> + if (!is_mbec_supported()) {
> + report_skip("MBEC not supported");
> + return;
> + }
> +
> + ept_access_test_setup();
> + /* --x (both XS and XU) */
> + if (ept_execute_only_supported()) {
> + ept_access_violation(EPT_EA | EPT_EA_USER, OP_READ,
> +     EPT_VLT_RD | EPT_VLT_PERM_EX | EPT_VLT_PERM_USER_EX);
> + ept_access_violation(EPT_EA | EPT_EA_USER, OP_WRITE,
> +     EPT_VLT_WR | EPT_VLT_PERM_EX | EPT_VLT_PERM_USER_EX);

Wrap EPT_VLT_PERM_USER_EX ? ^^

> + ept_access_allowed(EPT_EA | EPT_EA_USER, OP_EXEC);
> + ept_access_allowed(EPT_EA | EPT_EA_USER, OP_EXEC_USER);
> + } else {
> + ept_access_misconfig(EPT_EA | EPT_EA_USER);
> + }
> +}
> +
> static void ept_access_test_read_execute(void)
> {
> ept_access_test_setup();
> @@ -2939,6 +2985,43 @@ static void ept_access_test_read_execute(void)
> ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC_USER);
> }
> 
> +static void ept_access_test_read_execute_user_only(void)
> +{
> + if (!is_mbec_supported()) {
> + report_skip("MBEC not supported");
> + return;
> + }
> +
> + ept_access_test_setup();
> + /* r-x (exec user only) */
> + ept_access_allowed(EPT_RA | EPT_EA_USER, OP_READ);
> + ept_access_violation(EPT_RA | EPT_EA_USER, OP_WRITE,
> +     EPT_VLT_WR | EPT_VLT_PERM_RD |
> +     EPT_VLT_PERM_USER_EX);
> + ept_access_violation(EPT_RA | EPT_EA_USER, OP_EXEC,
> +     EPT_VLT_FETCH | EPT_VLT_PERM_RD |
> +     EPT_VLT_PERM_USER_EX);
> + ept_access_allowed(EPT_RA | EPT_EA_USER, OP_EXEC_USER);
> +}
> +
> +static void ept_access_test_read_execute_both(void)
> +{
> + if (!is_mbec_supported()) {
> + report_skip("MBEC not supported");
> + return;
> + }
> +
> + ept_access_test_setup();
> + /* r-x (both XS and XU) */
> + ept_access_allowed(EPT_RA | EPT_EA | EPT_EA_USER, OP_READ);
> + ept_access_violation(EPT_RA | EPT_EA | EPT_EA_USER, OP_WRITE,
> +     EPT_VLT_WR | EPT_VLT_PERM_RD |
> +     EPT_VLT_PERM_EX | EPT_VLT_PERM_USER_EX);
> + ept_access_allowed(EPT_RA | EPT_EA | EPT_EA_USER, OP_EXEC);
> + ept_access_allowed(EPT_RA | EPT_EA | EPT_EA_USER, OP_EXEC_USER);
> +}
> +
> +
> static void ept_access_test_write_execute(void)
> {
> ept_access_test_setup();
> @@ -2960,6 +3043,37 @@ static void ept_access_test_read_write_execute(void)
> ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_EXEC_USER);
> }
> 
> +static void ept_access_test_read_write_execute_user_only(void)
> +{
> + if (!is_mbec_supported()) {
> + report_skip("MBEC not supported");
> + return;
> + }
> +
> + ept_access_test_setup();
> + /* rwx (exec user only) */
> + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA_USER, OP_READ);
> + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA_USER, OP_WRITE);
> + ept_access_violation(EPT_RA | EPT_WA | EPT_EA_USER, OP_EXEC,
> +     EPT_VLT_FETCH | EPT_VLT_PERM_RD | EPT_VLT_PERM_WR | EPT_VLT_PERM_USER_EX);

Wrap line ^^

> + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA_USER, OP_EXEC_USER);
> +}
> +
> +static void ept_access_test_read_write_execute_both(void)
> +{
> + if (!is_mbec_supported()) {
> + report_skip("MBEC not supported");
> + return;
> + }
> +
> + ept_access_test_setup();
> + /* rwx (both XS and XU) */
> + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA | EPT_EA_USER, OP_READ);
> + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA | EPT_EA_USER, OP_WRITE);
> + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA | EPT_EA_USER, OP_EXEC);
> + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA | EPT_EA_USER, OP_EXEC_USER);
> +}
> +
> static void ept_access_test_reserved_bits(void)
> {
> int i;
> @@ -11722,9 +11836,15 @@ 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_execute_both),
> TEST(ept_access_test_read_execute),
> + TEST(ept_access_test_read_execute_user_only),
> + TEST(ept_access_test_read_execute_both),
> TEST(ept_access_test_write_execute),
> TEST(ept_access_test_read_write_execute),
> + TEST(ept_access_test_read_write_execute_user_only),
> + TEST(ept_access_test_read_write_execute_both),
> TEST(ept_access_test_reserved_bits),
> TEST(ept_access_test_ignored_bits),
> TEST(ept_access_test_paddr_not_present_ad_disabled),
> -- 
> 2.52.0
> 


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available
  2026-03-26 16:13   ` Paolo Bonzini
@ 2026-03-27 15:57     ` Jon Kohler
  0 siblings, 0 replies; 17+ messages in thread
From: Jon Kohler @ 2026-03-27 15:57 UTC (permalink / raw)
  To: Paolo Bonzini
  Cc: kvm@vger.kernel.org, Nikunj A Dadhania, amit.shah@amd.com,
	Sean Christopherson



> On Mar 26, 2026, at 12:13 PM, Paolo Bonzini <pbonzini@redhat.com> wrote:
> 
> On 3/26/26 15:50, Paolo Bonzini wrote:
>> diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
>> index 023512e6..bf03451a 100644
>> --- a/x86/vmx_tests.c
>> +++ b/x86/vmx_tests.c
>> @@ -1044,6 +1044,8 @@ static int insn_intercept_exit_handler(union exit_reason exit_reason)
>>   */
>>  static int __setup_ept(u64 hpa, bool enable_ad)
>>  {
>> + u64 secondary;
>> +
>>   if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) ||
>>      !(ctrl_cpu_rev[1].clr & CPU_EPT)) {
>>   printf("\tEPT is not supported\n");
>> @@ -1067,9 +1069,13 @@ static int __setup_ept(u64 hpa, bool enable_ad)
>>   if (enable_ad)
>>   eptp |= EPTP_AD_FLAG;
>>  + secondary = vmcs_read(CPU_EXEC_CTRL1) | CPU_EPT;
>> + if (is_mbec_supported())
>> + secondary |= CPU_MODE_BASED_EPT_EXEC;
>> +
>>   vmcs_write(EPTP, eptp);
>>   vmcs_write(CPU_EXEC_CTRL0, vmcs_read(CPU_EXEC_CTRL0)| CPU_SECONDARY);
>> - vmcs_write(CPU_EXEC_CTRL1, vmcs_read(CPU_EXEC_CTRL1)| CPU_EPT);
>> + vmcs_write(CPU_EXEC_CTRL1, secondary);
>>     return 0;
>>  }
> 
> This is missing a hunk:
> 
> diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
> index 023512e6..b5994719 100644
> --- a/x86/vmx_tests.c
> +++ b/x86/vmx_tests.c
> @@ -4886,7 +4946,7 @@ static void test_ept_eptp(void)
>                report_prefix_pop();
>        }
> -       secondary &= ~(CPU_EPT | CPU_URG);
> +       secondary &= ~(CPU_URG | CPU_EPT | CPU_MODE_BASED_EPT_EXEC);
>        vmcs_write(CPU_EXEC_CTRL1, secondary);
>        report_prefix_pushf("Enable-EPT disabled, unrestricted-guest disabled");
>        test_vmx_valid_controls();

Thanks, agreed. This works for me (which fixes the sole failure I had
on EPT side)

> 
> and possibly this as well for consistency:
> 
> diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
> index b5994719..cf1022ba 100644
> --- a/x86/vmx_tests.c
> +++ b/x86/vmx_tests.c
> @@ -5068,7 +5068,7 @@ static void test_pml(void)
>   primary |= CPU_SECONDARY;
> vmcs_write(CPU_EXEC_CTRL0, primary);
> - secondary &= ~(CPU_PML | CPU_EPT);
> + secondary &= ~(CPU_PML | CPU_EPT | CPU_MODE_BASED_EPT_EXEC);
> vmcs_write(CPU_EXEC_CTRL1, secondary);
> report_prefix_pushf("enable-PML disabled, enable-EPT disabled");
> test_vmx_valid_controls();
> 
> Paolo


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available Paolo Bonzini
  2026-03-26 16:13   ` Paolo Bonzini
@ 2026-03-27 15:57   ` Jon Kohler
  1 sibling, 0 replies; 17+ messages in thread
From: Jon Kohler @ 2026-03-27 15:57 UTC (permalink / raw)
  To: Paolo Bonzini
  Cc: kvm@vger.kernel.org, Nikunj A Dadhania, amit.shah@amd.com,
	Sean Christopherson



> On Mar 26, 2026, at 10:50 AM, Paolo Bonzini <pbonzini@redhat.com> wrote:
> 
> Check that the XS bit does not allow execution of user-mode pages
> when MBEC is available (and enabled); this requires tweaking
> the guest page tables to set U=0 for OP_EXEC.  Update the unit test
> configuration to include a specific test case for MBEC.
> 
> Co-authored-by: Jon Kohler <jon@nutanix.com>
> Signed-off-by: Jon Kohler <jon@nutanix.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> x86/unittests.cfg | 12 +++++-
> x86/vmx.h         |  5 ++-
> x86/vmx_tests.c   | 98 ++++++++++++++++++++++++++++++++++++++---------
> 3 files changed, 94 insertions(+), 21 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.h b/x86/vmx.h
> index b492ec74..7ad7672a 100644
> --- a/x86/vmx.h
> +++ b/x86/vmx.h
> @@ -672,11 +672,14 @@ enum vm_entry_failure_code {
> #define EPT_LARGE_PAGE (1ul << 7)
> #define EPT_ACCESS_FLAG (1ul << 8)
> #define EPT_DIRTY_FLAG (1ul << 9)
> +#define EPT_EA_USER (1ul << 10)
> #define EPT_MEM_TYPE_SHIFT 3ul
> #define EPT_MEM_TYPE_MASK 0x7ul
> #define EPT_SUPPRESS_VE (1ull << 63)
> 
> -#define EPT_PRESENT (EPT_RA | EPT_WA | EPT_EA)
> +#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)
> diff --git a/x86/vmx_tests.c b/x86/vmx_tests.c
> index 023512e6..bf03451a 100644
> --- a/x86/vmx_tests.c
> +++ b/x86/vmx_tests.c
> @@ -1044,6 +1044,8 @@ static int insn_intercept_exit_handler(union exit_reason exit_reason)
>  */
> static int __setup_ept(u64 hpa, bool enable_ad)
> {
> + u64 secondary;
> +
> if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) ||
>    !(ctrl_cpu_rev[1].clr & CPU_EPT)) {
> printf("\tEPT is not supported\n");
> @@ -1067,9 +1069,13 @@ static int __setup_ept(u64 hpa, bool enable_ad)
> if (enable_ad)
> eptp |= EPTP_AD_FLAG;
> 
> + secondary = vmcs_read(CPU_EXEC_CTRL1) | CPU_EPT;
> + if (is_mbec_supported())
> + secondary |= CPU_MODE_BASED_EPT_EXEC;
> +
> vmcs_write(EPTP, eptp);
> vmcs_write(CPU_EXEC_CTRL0, vmcs_read(CPU_EXEC_CTRL0)| CPU_SECONDARY);
> - vmcs_write(CPU_EXEC_CTRL1, vmcs_read(CPU_EXEC_CTRL1)| CPU_EPT);
> + vmcs_write(CPU_EXEC_CTRL1, secondary);
> 
> return 0;
> }
> @@ -2174,6 +2180,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);
> DIAGNOSE(EPT_VLT_GUEST_USER);
> @@ -2326,13 +2333,36 @@ static void ept_access_test_guest_flush_tlb(void)
> skip_exit_vmcall();
> }
> 
> +/*
> + * Modifies the leaf guest page table entry that maps @gva, clearing the bits
> + * in @clear then setting the bits in @set.  This is needed when testing
> + * MBEC so that the processor knows whether to observe XS or XU.
> + */
> +static void guest_page_table_twiddle(unsigned long *gva, unsigned long clear, unsigned long set)
> +{
> + pgd_t *cr3 = current_page_table();
> + int i;
> +
> + for (i = 1; i <= PAGE_LEVEL; i++) {
> + u64 *pte = get_pte_level(cr3, gva, i);
> + if (!pte)
> + continue;
> +
> + TEST_ASSERT(*pte & PT_PRESENT_MASK);
> + *pte = (*pte & ~clear) | set;
> + break;
> + }
> + invlpg((void *)gva);
> +}
> +
> /*
>  * Modifies the EPT entry at @level in the mapping of @gpa. First clears the
>  * bits in @clear then sets the bits in @set. @mkhuge transforms the entry into
>  * a huge page.
>  */
> static unsigned long ept_twiddle(unsigned long gpa, bool mkhuge, int level,
> - unsigned long clear, unsigned long set)
> + unsigned long clear, unsigned long set,
> + enum ept_access_op op)
> {
> struct ept_access_test_data *data = &ept_access_test_data;
> unsigned long orig_pte;
> @@ -2347,15 +2377,27 @@ static unsigned long ept_twiddle(unsigned long gpa, bool mkhuge, int level,
> pte = orig_pte;
> pte = (pte & ~clear) | set;
> set_ept_pte(pml4, gpa, level, pte);
> - invept(INVEPT_SINGLE, eptp);
> 
> + if (is_mbec_supported() && op == OP_EXEC)
> + guest_page_table_twiddle(data->gva, PT_USER_MASK, 0);
> +
> + invept(INVEPT_SINGLE, eptp);
> return orig_pte;
> }
> 
> -static void ept_untwiddle(unsigned long gpa, int level, unsigned long orig_pte)
> +static void ept_untwiddle(unsigned long gpa, int level, unsigned long orig_pte,
> +  enum ept_access_op op)
> {
> + unsigned long pte;
> +
> + pte = get_ept_pte(pml4, gpa, level, &pte);
> set_ept_pte(pml4, gpa, level, orig_pte);
> invept(INVEPT_SINGLE, eptp);
> +
> + if (is_mbec_supported() && op == OP_EXEC) {
> + struct ept_access_test_data *data = &ept_access_test_data;
> + guest_page_table_twiddle(data->gva, 0, PT_USER_MASK);
> + }
> }
> 
> static void do_ept_violation(bool leaf, enum ept_access_op op,
> @@ -2370,8 +2412,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_MASK | EPT_VLT_PERM_USER_EX);
> + /*
> + * Exit-qualifications are masked not to account for advanced
> + * VM-exit information. KVM supports this feature, so the tests
> + * could be enhanced to cover it.
> + */
> + qual &= ~EPT_VLT_GUEST_MASK;
> 
> diagnose_ept_violation_qual(expected_qual, qual);
> TEST_EXPECT_EQ(expected_qual, qual);
> @@ -2397,14 +2443,14 @@ ept_violation_at_level_mkhuge(bool mkhuge, int level, unsigned long clear,
> struct ept_access_test_data *data = &ept_access_test_data;
> unsigned long orig_pte;
> 
> - orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set);
> + orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set, op);
> 
> do_ept_violation(level == 1 || mkhuge, op, expected_qual,
> (op == OP_EXEC || op == OP_EXEC_USER
>  ? data->gpa + sizeof(unsigned long) : data->gpa));
> 
> /* Fix the violation and resume the op loop. */
> - ept_untwiddle(data->gpa, level, orig_pte);
> + ept_untwiddle(data->gpa, level, orig_pte, op);
> enter_guest();
> skip_exit_vmcall();
> }
> @@ -2502,12 +2548,12 @@ static void ept_access_paddr(unsigned long ept_access, unsigned long pte_ad,
> */
> install_ept(pml4, gpa, gpa, EPT_PRESENT);
> orig_epte = ept_twiddle(gpa, /*mkhuge=*/0, /*level=*/1,
> - /*clear=*/EPT_PRESENT, /*set=*/ept_access);
> + /*clear=*/EPT_PRESENT, /*set=*/ept_access, op);
> 
> if (expect_violation) {
> do_ept_violation(/*leaf=*/true, op,
> expected_qual | EPT_VLT_LADDR_VLD, gpa);
> - ept_untwiddle(gpa, /*level=*/1, orig_epte);
> + ept_untwiddle(gpa, /*level=*/1, orig_epte, op);
> do_ept_access_op(op);
> } else {
> do_ept_access_op(op);
> @@ -2522,7 +2568,7 @@ static void ept_access_paddr(unsigned long ept_access, unsigned long pte_ad,
> }
> }
> 
> - ept_untwiddle(gpa, /*level=*/1, orig_epte);
> + ept_untwiddle(gpa, /*level=*/1, orig_epte, op);
> }
> 
> TEST_ASSERT(*ptep & PT_ACCESSED_MASK);
> @@ -2558,13 +2604,13 @@ static void ept_allowed_at_level_mkhuge(bool mkhuge, int level,
> struct ept_access_test_data *data = &ept_access_test_data;
> unsigned long orig_pte;
> 
> - orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set);
> + orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set, op);
> 
> /* No violation. Should proceed to vmcall. */
> do_ept_access_op(op);
> skip_exit_vmcall();
> 
> - ept_untwiddle(data->gpa, level, orig_pte);
> + ept_untwiddle(data->gpa, level, orig_pte, op);
> }
> 
> static void ept_allowed_at_level(int level, unsigned long clear,
> @@ -2613,7 +2659,7 @@ static void ept_misconfig_at_level_mkhuge_op(bool mkhuge, int level,
> struct ept_access_test_data *data = &ept_access_test_data;
> unsigned long orig_pte;
> 
> - orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set);
> + orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set, op);
> 
> do_ept_access_op(op);
> assert_exit_reason(VMX_EPT_MISCONFIG);
> @@ -2637,7 +2683,7 @@ static void ept_misconfig_at_level_mkhuge_op(bool mkhuge, int level,
> #endif
> 
> /* Fix the violation and resume the op loop. */
> - ept_untwiddle(data->gpa, level, orig_pte);
> + ept_untwiddle(data->gpa, level, orig_pte, op);
> enter_guest();
> skip_exit_vmcall();
> }
> @@ -2867,7 +2913,12 @@ 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);
> - ept_access_allowed(EPT_EA, OP_EXEC_USER);
> + if (is_mbec_supported())
> + ept_access_violation(EPT_EA, OP_EXEC_USER,
> +     EPT_VLT_FETCH |
> +     EPT_VLT_PERM_EX);
> + else
> + ept_access_allowed(EPT_EA, OP_EXEC_USER);
> } else {
> ept_access_misconfig(EPT_EA);
> }
> @@ -2881,7 +2932,11 @@ static void ept_access_test_read_execute(void)
> ept_access_violation(EPT_RA | EPT_EA, OP_WRITE,
>     EPT_VLT_WR | EPT_VLT_PERM_RD | EPT_VLT_PERM_EX);
> ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC);
> - ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC_USER);
> + if (is_mbec_supported())
> + ept_access_violation(EPT_RA | EPT_EA, OP_EXEC_USER,
> +     EPT_VLT_FETCH | EPT_VLT_PERM_RD | EPT_VLT_PERM_EX);
> + else
> + ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC_USER);
> }
> 
> static void ept_access_test_write_execute(void)
> @@ -2898,7 +2953,11 @@ 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);
> - ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_EXEC_USER);
> + if (is_mbec_supported())
> + ept_access_violation(EPT_RA | EPT_WA | EPT_EA, OP_EXEC_USER,
> +     EPT_VLT_FETCH | EPT_VLT_PERM_RD | EPT_VLT_PERM_WR | EPT_VLT_PERM_EX);

Wrap line a bit ^

> + else
> + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_EXEC_USER);
> }
> 
> static void ept_access_test_reserved_bits(void)
> @@ -2955,7 +3014,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.52.0
> 
> 


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH kvm-unit-tests 6/9] x86/vmx: add mode-based execute control test for Skylake and above
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 6/9] x86/vmx: add mode-based execute control test for Skylake and above Paolo Bonzini
@ 2026-03-27 15:57   ` Jon Kohler
  0 siblings, 0 replies; 17+ messages in thread
From: Jon Kohler @ 2026-03-27 15:57 UTC (permalink / raw)
  To: Paolo Bonzini
  Cc: kvm@vger.kernel.org, Nikunj A Dadhania, amit.shah@amd.com,
	Sean Christopherson



> On Mar 26, 2026, at 10:50 AM, Paolo Bonzini <pbonzini@redhat.com> wrote:
> 
> 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.
> 
> Passing test result
> Test suite: vmx_controls_test_mbec

The test suite name was a left over from my original patch series, and
does not exist in this patch. As such, this test fails like so:

TESTNAME=vmx_controls_test_mbec TIMEOUT=90s MACHINE= ACCEL= ./x86/run x86/vmx.flat -smp 1 -cpu Skylake-Server,+vmx,+vmx-mbec -append "vmx_controls_test_mbec"
FAIL vmx_controls_test_mbec (1 tests, 1 unexpected failures)

And when run directly, we get:
TESTNAME=vmx_controls_test_mbec TIMEOUT=90s MACHINE= ACCEL= ./x86/run x86/vmx.flat -smp 1 -cpu Skylake-Server,+vmx,+vmx-mbec -append "vmx_controls_test_mbec"
timeout -k 1s --foreground 90s /usr/libexec/qemu-kvm --no-reboot -nodefaults -global kvm-pit.lost_tick_policy=discard -device pc-testdev -device isa-debug-exit,iobase=0xf4,iosize=0x4 -display none -serial stdio -device pci-testdev -machine accel=kvm -kernel x86/vmx.flat -smp 1 -cpu Skylake-Server,+vmx,+vmx-mbec -append vmx_controls_test_mbec # -initrd /tmp/tmp.E4mbYzCHB3
qemu-kvm: warning: Machine type 'pc-i440fx-rhel7.6.0' is deprecated: machines from the previous RHEL major release are subject to deletion in the next RHEL major release
qemu-kvm: warning: host doesn't support requested feature: CPUID.07H:EBX.mpx [bit 14]
enabling apic
smp: waiting for 0 APs
paging enabled
cr0 = 80010011
cr3 = 1007000
cr4 = 20
...
FAIL: command line didn't match any tests! <<<< 
SUMMARY: 1 tests, 1 unexpected failures


That said, the current patch is actually working as it runs as part of the
larger controls test and does indeed pass with the same log entry as I had
originally punched in below.

> 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
> 
> Co-authored-by: Jon Kohler <jon@nutanix.com>
> Signed-off-by: Jon Kohler <jon@nutanix.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> x86/unittests.cfg |  9 +++++++
> x86/vmx.h         |  8 ++++++
> x86/vmx_tests.c   | 64 +++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 81 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 0e29a57d..b492ec74 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,
> };
> 
> @@ -843,6 +844,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 0e3dca3c..dbc456cb 100644
> --- a/x86/vmx_tests.c
> +++ b/x86/vmx_tests.c
> @@ -4876,6 +4876,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
> @@ -5336,6 +5399,7 @@ static void test_vm_execution_ctls(void)
> test_pml();
> test_vpid();
> test_ept_eptp();
> + test_mode_based_execute_control();
> test_vmx_preemption_timer();
> }

If we were interested in keeping this as a separate test, what I had before was:
/*
 * 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 vmx_test vmx_tests[] = {
...
	TEST(vmx_controls_test_mbec),
...
}



^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH kvm-unit-tests 3/9] svm: add basic GMET tests
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 3/9] svm: add basic GMET tests Paolo Bonzini
@ 2026-03-27 16:03   ` Jon Kohler
  0 siblings, 0 replies; 17+ messages in thread
From: Jon Kohler @ 2026-03-27 16:03 UTC (permalink / raw)
  To: Paolo Bonzini
  Cc: kvm@vger.kernel.org, Nikunj A Dadhania, amit.shah@amd.com,
	Sean Christopherson



> On Mar 26, 2026, at 10:50 AM, Paolo Bonzini <pbonzini@redhat.com> wrote:
> 
> These cover three basic scenarios of running successfully,
> failing due to NX=1 and failing due to U/S=1.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> Makefile            |  2 +-
> lib/x86/processor.h |  1 +
> x86/svm.c           | 17 ++++++++++
> x86/svm.h           |  1 +
> x86/svm_npt.c       | 83 +++++++++++++++++++++++++++++++++++++++++++--
> 5 files changed, 101 insertions(+), 3 deletions(-)
> 
> diff --git a/Makefile b/Makefile
> index 0ce0813b..403fd495 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -93,7 +93,7 @@ COMMON_CFLAGS += $(wunused_but_set_parameter)
> CFLAGS += $(COMMON_CFLAGS)
> CFLAGS += $(wmissing_parameter_type)
> CFLAGS += $(wold_style_declaration)
> -CFLAGS += -Woverride-init -Wmissing-prototypes -Wstrict-prototypes
> +CFLAGS += -Wmissing-prototypes -Wstrict-prototypes
> 
> # Evaluate and add late cflags last -- they may depend on previous flags
> LATE_CFLAGS := $(LATE_CFLAGS)
> diff --git a/lib/x86/processor.h b/lib/x86/processor.h
> index 42dd2d2a..32ce08e2 100644
> --- a/lib/x86/processor.h
> +++ b/lib/x86/processor.h
> @@ -377,6 +377,7 @@ struct x86_cpu_feature {
> #define X86_FEATURE_PAUSEFILTER X86_CPU_FEATURE(0x8000000A, 0, EDX, 10)
> #define X86_FEATURE_PFTHRESHOLD X86_CPU_FEATURE(0x8000000A, 0, EDX, 12)
> #define X86_FEATURE_VGIF X86_CPU_FEATURE(0x8000000A, 0, EDX, 16)
> +#define X86_FEATURE_GMET X86_CPU_FEATURE(0x8000000A, 0, EDX, 17)
> #define X86_FEATURE_VNMI X86_CPU_FEATURE(0x8000000A, 0, EDX, 25)
> #define X86_FEATURE_SME X86_CPU_FEATURE(0x8000001F, 0, EAX,  0)
> #define X86_FEATURE_SEV X86_CPU_FEATURE(0x8000001F, 0, EAX,  1)
> diff --git a/x86/svm.c b/x86/svm.c
> index 58cbf0a5..a85da905 100644
> --- a/x86/svm.c
> +++ b/x86/svm.c
> @@ -43,6 +43,23 @@ u64 *npt_get_pml4e(void)
> return pml4e;
> }
> 
> +void npt_prepare_gmet_pte(bool user)
> +{
> + extern u8 start;
> + u64 address = (u64)&start & ~(1 << 21);
> + u64 mask = user ? PT_USER_MASK : 0;
> + u64 *pte;
> + int i;
> +
> +
> + /* flip the U bit on the 2 MiB region where the code is loaded.
> + * the U bit is only used for execution, therefore page table accesses ignore it
> + */
> + pte = npt_get_pte(address);
> + for (i = 0; i < 512; i++)
> + pte[i] = (pte[i] & ~PT_USER_MASK) | mask;
> +}
> +
> bool smp_supported(void)
> {
> return cpu_count() > 1;
> diff --git a/x86/svm.h b/x86/svm.h
> index 947206bb..c5695b37 100644
> --- a/x86/svm.h
> +++ b/x86/svm.h
> @@ -418,6 +418,7 @@ u64 *npt_get_pte(u64 address);
> u64 *npt_get_pde(u64 address);
> u64 *npt_get_pdpe(u64 address);
> u64 *npt_get_pml4e(void);
> +void npt_prepare_gmet_pte(bool user);
> bool smp_supported(void);
> bool default_supported(void);
> bool fep_supported(void);
> diff --git a/x86/svm_npt.c b/x86/svm_npt.c
> index bd5e8f35..75d9c2c9 100644
> --- a/x86/svm_npt.c
> +++ b/x86/svm_npt.c
> @@ -87,6 +87,79 @@ static bool npt_us_check(struct svm_test *test)
>    && (vmcb->control.exit_info_1 == 0x100000005ULL);
> }
> 
> +static bool npt_gmet_supported(void)
> +{
> + return npt_supported() && this_cpu_has(X86_FEATURE_GMET);
> +}
> +
> +static void npt_gmet_null_prepare(struct svm_test *test)
> +{
> + /* set U=0 - no failure */
> + npt_prepare_gmet_pte(false);
> + vmcb->control.nested_ctl |= SVM_NESTED_GMET;
> +}
> +
> +static bool npt_gmet_null_check(struct svm_test *test)
> +{
> + /* reset U=1 */
> + npt_prepare_gmet_pte(true);
> + vmcb->control.nested_ctl &= ~SVM_NESTED_GMET;
> +        return vmcb->control.exit_code == SVM_EXIT_VMMCALL;
> +}

Nit: space vs tabs above ^^

> +
> +static void npt_gmet_nx_prepare(struct svm_test *test)
> +{
> + u64 *pte = npt_get_pte((u64) null_test);
> +
> + /* set U=0 - failure will be from NX */
> + npt_prepare_gmet_pte(false);
> + *pte |= PT64_NX_MASK;
> + vmcb->control.nested_ctl |= SVM_NESTED_GMET;
> +
> + test->scratch = rdmsr(MSR_EFER);
> + wrmsr(MSR_EFER, test->scratch | EFER_NX);
> +}
> +
> +static bool npt_gmet_nx_check(struct svm_test *test)
> +{
> + u64 *pte = npt_get_pte((u64) null_test);
> +
> + /* reset U=1, NX=0 */
> + npt_prepare_gmet_pte(true);
> + *pte &= ~PT64_NX_MASK;
> + vmcb->control.nested_ctl &= ~SVM_NESTED_GMET;
> +
> + wrmsr(MSR_EFER, test->scratch);
> +
> + /* errata 1218 - the U bit in the page fault error code may be incorrect */
> + return (vmcb->control.exit_code == SVM_EXIT_NPF)
> +    && ((vmcb->control.exit_info_1 & ~PFERR_USER_MASK) == 0x100000011ULL);
> +}
> +
> +static void npt_gmet_us_prepare(struct svm_test *test)
> +{
> + u64 *pte = npt_get_pte((u64) null_test);
> +
> + npt_prepare_gmet_pte(false);
> + *pte |= PT_USER_MASK;
> + vmcb->control.nested_ctl |= SVM_NESTED_GMET;
> +
> + test->scratch = rdmsr(MSR_EFER);
> + wrmsr(MSR_EFER, test->scratch | EFER_NX);
> +}
> +
> +static bool npt_gmet_us_check(struct svm_test *test)
> +{
> + npt_prepare_gmet_pte(true);
> + vmcb->control.nested_ctl &= ~SVM_NESTED_GMET;
> +
> + wrmsr(MSR_EFER, test->scratch);
> +
> + /* errata 1218 - the U bit in the page fault error code may be incorrect */
> + return (vmcb->control.exit_code == SVM_EXIT_NPF)
> +    && ((vmcb->control.exit_info_1 & ~PFERR_USER_MASK) == 0x100000011ULL);
> +}
> +
> static void npt_rw_prepare(struct svm_test *test)
> {
> 
> @@ -380,9 +453,9 @@ skip_pte_test:
> vmcb->save.cr4 = sg_cr4;
> }
> 
> -#define NPT_V1_TEST(name, prepare, guest_code, check) \
> +#define NPT_V1_TEST(name, prepare, guest_code, check, more...) \
> { #name, npt_supported, prepare, default_prepare_gif_clear, guest_code, \
> -  default_finished, check }
> +  default_finished, check, more }
> 
> #define NPT_V2_TEST(name) { #name, .v2 = name }
> 
> @@ -390,6 +463,12 @@ static struct svm_test npt_tests[] = {
> NPT_V1_TEST(npt_nx, npt_nx_prepare, null_test, npt_nx_check),
> NPT_V1_TEST(npt_np, npt_np_prepare, npt_np_test, npt_np_check),
> NPT_V1_TEST(npt_us, npt_us_prepare, npt_us_test, npt_us_check),
> + NPT_V1_TEST(npt_gmet_null, npt_gmet_null_prepare, null_test, npt_gmet_null_check,
> + .supported = npt_gmet_supported),
> + NPT_V1_TEST(npt_gmet_nx, npt_gmet_nx_prepare, null_test, npt_gmet_nx_check,
> + .supported = npt_gmet_supported),
> + NPT_V1_TEST(npt_gmet_us, npt_gmet_us_prepare, null_test, npt_gmet_us_check,
> + .supported = npt_gmet_supported),
> NPT_V1_TEST(npt_rw, npt_rw_prepare, npt_rw_test, npt_rw_check),
> NPT_V1_TEST(npt_rw_pfwalk, npt_rw_pfwalk_prepare, null_test, npt_rw_pfwalk_check),
> NPT_V1_TEST(npt_l1mmio, npt_l1mmio_prepare, npt_l1mmio_test, npt_l1mmio_check),
> -- 
> 2.52.0
> 
> 


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests
  2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
                   ` (8 preceding siblings ...)
  2026-03-26 14:50 ` [PATCH kvm-unit-tests 9/9] x86/vmx: add EPT tests covering XU permission Paolo Bonzini
@ 2026-05-12 11:06 ` Paolo Bonzini
  9 siblings, 0 replies; 17+ messages in thread
From: Paolo Bonzini @ 2026-05-12 11:06 UTC (permalink / raw)
  To: kvm; +Cc: Jon Kohler, Nikunj A Dadhania, Amit Shah, Sean Christopherson

On 3/26/26 15:50, Paolo Bonzini wrote:
> This adds new tests for both GMET and MBEC.
> 
> The code for MBEC is roughly based on the previous submission at
> https://lore.kernel.org/kvm/20251223054850.1611618-1-jon@nutanix.com/,
> though with pretty heavy reorganization and fixing work on top.
> 
> The existing EPT tests now test execution on both supervisor and
> user-mode pages, with different expected outcomes depending on
> whether MBEC is enabled or not; on top of this, the last patch
> adds tests for XU=1 and XS=XU=1.
> 
> For simplicity, the tests always enable MBEC when available.
> A new block in unittests.cfg ensures that both non-MBEC and
> MBEC is covered.

Pushed to kvm-unit-tests.git.

Paolo

> 
> Jon Kohler (1):
>    x86/vmx: update EPT installation to use EPT_PRESENT flag
> 
> Paolo Bonzini (8):
>    move PFERR_* constants to lib
>    add definitions for nested_ctl
>    svm: add basic GMET tests
>    x86/vmx: diagnose unexpected EPT violations
>    x86/vmx: add mode-based execute control test for Skylake and above
>    x86/vmx: add user execution operation to EPT access tests
>    x86/vmx: run EPT tests with MBEC enabled when available
>    x86/vmx: add EPT tests covering XU permission
> 
>   Makefile            |   2 +-
>   lib/util.h          |  10 +-
>   lib/x86/asm/page.h  |   7 +
>   lib/x86/processor.h |   1 +
>   x86/access.c        |   7 -
>   x86/svm.c           |  19 +-
>   x86/svm.h           |   4 +
>   x86/svm_npt.c       |  83 ++++++++-
>   x86/unittests.cfg   |  21 ++-
>   x86/vmx.c           |   3 +-
>   x86/vmx.h           |  32 ++--
>   x86/vmx_tests.c     | 414 +++++++++++++++++++++++++++++++++++++-------
>   12 files changed, 513 insertions(+), 90 deletions(-)
> 


^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-05-12 11:06 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-26 14:50 [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini
2026-03-26 14:50 ` [PATCH kvm-unit-tests 1/9] move PFERR_* constants to lib Paolo Bonzini
2026-03-26 14:50 ` [PATCH kvm-unit-tests 2/9] add definitions for nested_ctl Paolo Bonzini
2026-03-26 14:50 ` [PATCH kvm-unit-tests 3/9] svm: add basic GMET tests Paolo Bonzini
2026-03-27 16:03   ` Jon Kohler
2026-03-26 14:50 ` [PATCH kvm-unit-tests 4/9] x86/vmx: update EPT installation to use EPT_PRESENT flag Paolo Bonzini
2026-03-26 14:50 ` [PATCH kvm-unit-tests 5/9] x86/vmx: diagnose unexpected EPT violations Paolo Bonzini
2026-03-26 14:50 ` [PATCH kvm-unit-tests 6/9] x86/vmx: add mode-based execute control test for Skylake and above Paolo Bonzini
2026-03-27 15:57   ` Jon Kohler
2026-03-26 14:50 ` [PATCH kvm-unit-tests 7/9] x86/vmx: add user execution operation to EPT access tests Paolo Bonzini
2026-03-26 14:50 ` [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available Paolo Bonzini
2026-03-26 16:13   ` Paolo Bonzini
2026-03-27 15:57     ` Jon Kohler
2026-03-27 15:57   ` Jon Kohler
2026-03-26 14:50 ` [PATCH kvm-unit-tests 9/9] x86/vmx: add EPT tests covering XU permission Paolo Bonzini
2026-03-27 15:56   ` Jon Kohler
2026-05-12 11:06 ` [PATCH kvm-unit-tests 0/9] Combined GMET and MBEC tests Paolo Bonzini

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox