Kernel KVM virtualization development
 help / color / mirror / Atom feed
From: Jon Kohler <jon@nutanix.com>
To: Paolo Bonzini <pbonzini@redhat.com>
Cc: "kvm@vger.kernel.org" <kvm@vger.kernel.org>,
	Nikunj A Dadhania <nikunj@amd.com>,
	"amit.shah@amd.com" <amit.shah@amd.com>,
	Sean Christopherson <seanjc@google.com>
Subject: Re: [PATCH kvm-unit-tests 8/9] x86/vmx: run EPT tests with MBEC enabled when available
Date: Fri, 27 Mar 2026 15:57:23 +0000	[thread overview]
Message-ID: <79B6B36A-AF03-4AC4-9FD3-71BCA4F37B65@nutanix.com> (raw)
In-Reply-To: <20260326145035.119519-9-pbonzini@redhat.com>



> 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
> 
> 


  parent reply	other threads:[~2026-03-27 15:57 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=79B6B36A-AF03-4AC4-9FD3-71BCA4F37B65@nutanix.com \
    --to=jon@nutanix.com \
    --cc=amit.shah@amd.com \
    --cc=kvm@vger.kernel.org \
    --cc=nikunj@amd.com \
    --cc=pbonzini@redhat.com \
    --cc=seanjc@google.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox