Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [RFC 0/4] KVM: nVMX: Fix guest (CET) state handling on VM-entry failure
@ 2026-09-04  2:31 Zhao Liu
  2026-09-04  2:31 ` [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it Zhao Liu
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Zhao Liu @ 2026-09-04  2:31 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H . Peter Anvin, Shuah Khan
  Cc: Chao Gao, Xin Li, Sohil Mehta, kvm, linux-kernel, linux-kselftest,
	Zhao Liu

Hi,

This series tries to point out the missing guest state retain behavior
in the current KVM handling for the nested vm-entry failure case, by a
CET nested selftest (patch 4), and to raise a discussion on how KVM
retains guest state at VM-entry failure.

Currently, I think there are two possible fix approaches: one is patch 1,
which explicitly enumerates the stages, and the other is option B
mentioned in section "Option B", which uses (or reuse pre_vmenter_* ?)
intermediate variables to cache the guest state between vmcs01 and
vmcs02.

Back to problem (using CET as the example), at present, on a nested
VM-exit with VM_EXIT_LOAD_CET_STATE cleared, KVM unconditionally copies
vmcs12's guest CET fields into vmcs01. When the VM-exit is a VM-entry
failure that occurred before guest state was loaded, those fields hold
what L1 wrote for L2, not what the CPU would have kept, so L1 resumes
with the S_CET/SSP/INTERRUPT_SSP_TABLE_ADDR it had programmed for L2.

This does not conform to the SDM's description of vm-entry failure.

Background
==========

What L1 should observe after a nested VM-exit depends on three things:
the VM-exit "load host state" control, whether VM-entry got far enough
to load L2's state, and whether L2 ran. For CET:

 1) VM_EXIT_LOAD_CET_STATE set: load L1's state from vmcs12's host
    fields, no matter what happened before. KVM already does this, and
    setting this control makes L1 immune to the bug below.

 2) Control clear, VM-entry loaded L2's CET state: the guest state is
    retained on the CPU, so L1 observes it. Copying vmcs12's guest
    fields into vmcs01 gives the right result.

 3) Control clear, VM-entry did not load L2's CET state (VM-entry
    failed before the guest-state loading phase): the CPU keeps L1's
    own state, so KVM must do nothing.

 4) Control clear, VM-entry did not load CET state, but L2 ran and
    exited normally: L2 may have changed the state while running, and
    vmcs12's guest fields hold what L2 left behind (guest CET state is
    always saved on VM-exit; there is no VM-exit control for it), so
    copying them is again correct.

Case 3) is broken today: KVM never syncs vmcs02 back to vmcs12 on the
VM-entry failure path, so vmcs12's guest fields still hold whatever L1
wrote with VMWRITE, and KVM stuffs that into vmcs01.

Note that one "VM-entry failed" flag is not enough to distinguish 2)
from 3): MSR loading happens after guest-state loading, so an
EXIT_REASON_MSR_LOAD_FAIL implies L2's state was loaded, whereas an
EXIT_REASON_INVALID_STATE does not.

The INVALID_STATE side of this is a modeling choice, not a hardware
guarantee. Per the SDM ("Checking and Loading Guest State"), guest-state
checking and loading occur concurrently, so whether L2's state was
loaded when the check fails is architecturally undefined.

With patch 1, we can make the behavior well-defined for KVM, by treating
the check as preceding the load, i.e. it emulates an INVALID_STATE
VM-entry failure as if no guest state was loaded at all (Case 3).

Option A (this series)
======================

Track how far VM-entry got, and use that at VM-exit to decide whether
vmcs12's guest fields are a faithful copy of what the CPU holds:

  enum nested_l2_state {
	L2_STATE_NOT_LOADED,		/* VM-entry failed before the
					   guest-state loading phase */
	L2_STATE_LOADED_FROM_VMCS12,
	L2_STATE_SAVED_TO_VMCS12,	/* L2 ran, state synced back */
  };

nested_vmx_enter_non_root_mode() sets L2_STATE_LOADED_FROM_VMCS12 right
after prepare_vmcs02() succeeds, __nested_vmx_vmexit() passes
L2_STATE_SAVED_TO_VMCS12, and nested_l2_state_is_live() folds the three
states plus the VM-entry load control into one predicate.

Pros: small, self-contained, no new state that outlives the transition
(the enum is a local + one parameter), and the per-feature cost is one
stage check in load_vmcs12_host_state().

Cons: the VM-exit path now has to look at vmcs12->vm_entry_controls,
which is odd, because that control belongs to VM-entry.

Option B (not implemented)
==========================

On a VM-exit, the host-state loading step only ever does one of two
things: load the register from the VMCS host field, or leave the register
alone. It does not care why the VM-exit happened, nor how far a failed
VM-entry got. But KVM cannot simply "leave the register alone", because
the live value sits in the guest fields of the current VMCS, so it moves
when KVM switches from vmcs02 back to vmcs01.

KVM already solves this for the L1->L2 direction, with
nested.pre_vmenter_{s_cet,ssp,ssp_tbl}: L1's live value is read from
vmcs01 before the switch, and written into vmcs02 if VM-entry does not
load CET state. The L2->L1 direction has no such variable and uses
vmcs12's guest fields instead. That is where the bug comes from.

So the other way to fix this is to keep those fields updated in both
directions. They would then hold more than the pre-VM-entry value, so
they'd want a new name as well (nested.cet_state, i.e. drop the
"pre_vmenter" prefix again :-)):

  nested_vmx_enter_non_root_mode()
	read the live state from vmcs01		/* unconditionally */
	switch to vmcs02
	guest-state checks fail  -> exit path, value is still L1's
	prepare_vmcs02() fails   -> exit path, value is still L1's
	/* guest-state loading phase */
	if (VM_ENTRY_LOAD_CET_STATE)
		value = vmcs12 guest fields
	write the value into vmcs02
	/* MSR loading phase; on failure the value is already correct */

  prepare_vmcs12()				/* real VM-exits only */
	read the live state from vmcs02 into vmcs12's guest fields
	and into the tracked value

  load_vmcs12_host_state()
	if (VM_EXIT_LOAD_CET_STATE)
		value = vmcs12 host fields
	write the value into vmcs01		/* unconditionally */

Pros: no history to reconstruct at VM-exit, no reference to the
VM-entry control from the VM-exit path, symmetric with the existing
L1->L2 handling, and it works for state that is not synced back to
vmcs12.

Cons: a wider diff (VM-entry path, prepare_vmcs02(), prepare_vmcs12(),
load_vmcs12_host_state()), and the tracked value is a "second copy" [*]
of live state, which introduces a third place where guest state lives,
next to vmcs01/vmcs02 and vmcs12, and every affected state needs its
own variable. So this is the approach that grows with the number of
features, whereas Option A's staging enum is shared by all of them.

[*]: "second copy" may be inaccurate, since several places already hold
guest state outside the VMCS: EFER and PAT are kept in vcpu->arch.*,
which survives the VMCS switch for free, while CET and BNDCFGS live only
in the VMCS and already need their own nested.pre_vmenter_* fields for
the L1->L2 direction. No single unified place is a pity indeed.

Extensibility
=============

Any feature with a VM-exit "load host state" (or "clear") control has to
answer the same question: with that control set/clear, what state to
retain for l1? So this is not only about CET:

 * EFER is correct, because vcpu->arch.efer holds L2's value and
   the vmx_set_efer() at the end of that if/else chain writes vmcs01
   unconditionally.

 * CET is broken, which is what patch 1 fixes.

 * BNDCFGS is broken: load_vmcs12_host_state() only handles
   VM_EXIT_CLEAR_BNDCFGS, so with that control clear L2's value is lost
   and L1 resumes with its own. MPX is limited to a few older CPUs.

 * PAT is broken: with VM_EXIT_LOAD_IA32_PAT clear, vmcs01.GUEST_IA32_PAT
   keeps L1's pre-VM-entry value while vcpu->arch.pat holds L2's, so L1
   resumes with its own PAT but reads L2's back with RDMSR.

 * (in future) FRED also needs to consider this.

Testing
=======

The selftest gives L1, L2 and vmcs12's host fields three different sets
of CET values, so that whichever one L1 ends up with tells us where it
came from. For each of the 4 combinations of VM_ENTRY_LOAD_CET_STATE and
VM_EXIT_LOAD_CET_STATE, it triggers both kinds of VM-entry failure and
then checks what L1 reads back:

 - EXIT_REASON_INVALID_STATE: guest CR0 with PG set and PE clear.
 - EXIT_REASON_MSR_LOAD_FAIL: a VM-entry MSR load list entry for
   MSR_IA32_UCODE_REV.

MSR_IA32_INTERRUPT_SSP_TABLE_ADDR is the primary observation point
because KVM always intercepts it and reads it straight out of
vmcs01.GUEST_INTR_SSP_TABLE. The test requires SHSTK and skips
otherwise.

Thanks and Best Regards,
Zhao
---
Zhao Liu (4):
  KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it
  KVM: selftests: Synchronize and update VMCS controls
  KVM: selftests: Synchronize and update VMCS encodings
  KVM: selftests: Test VM-entry failure handling for nested VM

 arch/x86/kvm/vmx/nested.c                     |  59 ++-
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/include/x86/processor.h     |   1 +
 tools/testing/selftests/kvm/include/x86/vmx.h | 489 ++++++++++--------
 .../selftests/kvm/include/x86/vmxfeatures.h   |  93 ++++
 .../x86/vmx_nested_entry_fail_state_test.c    | 311 +++++++++++
 6 files changed, 727 insertions(+), 227 deletions(-)
 create mode 100644 tools/testing/selftests/kvm/include/x86/vmxfeatures.h
 create mode 100644 tools/testing/selftests/kvm/x86/vmx_nested_entry_fail_state_test.c

-- 
2.34.1


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

* [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it
  2026-09-04  2:31 [RFC 0/4] KVM: nVMX: Fix guest (CET) state handling on VM-entry failure Zhao Liu
@ 2026-09-04  2:31 ` Zhao Liu
  2026-09-04  2:49   ` sashiko-bot
  2026-09-04 16:42   ` Sean Christopherson
  2026-09-04  2:31 ` [RFC 2/4] KVM: selftests: Synchronize and update VMCS controls Zhao Liu
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 8+ messages in thread
From: Zhao Liu @ 2026-09-04  2:31 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H . Peter Anvin, Shuah Khan
  Cc: Chao Gao, Xin Li, Sohil Mehta, kvm, linux-kernel, linux-kselftest,
	Zhao Liu

On a nested VM-exit that disables VM_EXIT_LOAD_CET_STATE, only copy L2's
CET state from vmcs12 to vmcs01 if VM-entry really loaded that state,
i.e. don't copy when VM-entry fails before loading guest state.

The state, that L1 should see after a L2 VM-exit, depends on three
things: the VM-exit load (host state) control, whether VM-entry loaded
L2's state, and whether L2 ran.

For CET, there are 4 cases:

 1) VM_EXIT_LOAD_CET_STATE is set. Load L1's CET state from vmcs12's
    host fields, no matter what happened before. KVM already does this.

 2) VM_EXIT_LOAD_CET_STATE is clear, and VM-entry loaded L2's CET
    state. Whether it's the normal VM-exit or VM-entry failure exit,
    the guest's (L2's) state should be retained, so copy vmcs12's guest
    fields into vmcs01 to give L1 the same result.

 3) VM_EXIT_LOAD_CET_STATE is clear, VM-entry didn't load L2's CET
    state, and L2 never ran. This is the typical case that VM-entry
    fails before loading guest state, the CPU keeps L1's own state, so
    do nothing.

 4) VM_EXIT_LOAD_CET_STATE is clear, VM-entry didn't load L2's CET
    state, but L2 ran and exited normally. The CPU keeps L1's state
    again, but L2 could have changed it while running, so still copy
    vmcs12's guest fields into vmcs01, because they hold what L2 left
    behind.

Case 3) is broken today. When VM-entry fails, KVM copies vmcs12's guest
CET fields into vmcs01 as long as VM_EXIT_LOAD_CET_STATE is clear, so L1
gets the state it wrote for L2 instead of its own state. KVM never syncs
vmcs02 back to vmcs12 on this path, so those guest fields still hold
what L1 wrote with VMWRITE.

To fix case 3), it's necessary to distinguish case 2), case 3) and case
4). But one "VM-entry failed" flag is not enough, since it only tells
whether L2 ran, and lacks the information about whether VM-entry loaded
L2's state - and this is important, EXIT_REASON_MSR_LOAD_FAIL is
triggered after guest state loading, but EXIT_REASON_INVALID_STATE is
not.

Note, SDM vol.3, chapter 29, "VM ENTRIES", does not guarantee the order
of the guest state check and the guest state load, however KVM can more
directly assume that the guest state load occurs after the check,
thereby simplifying the emulation of state handling when
EXIT_REASON_INVALID_STATE occurs (corresponding to Case 3). But MSR list
loading is after guest state loading, so at EXIT_REASON_MSR_LOAD_FAIL,
guest state has been loaded.

Therefore, to determine whether the VM-entry loaded L2's state and
whether L2 ran, introduce the nested_l2_state enumeration to mark the L2
guest state phase, thereby helping to distinguish between Case 2), Case
3), and Case 4) in a helper nested_l2_state_is_live().

This pattern can be reused to support additional features that have load
controls, such as BNDCFGS, PAT, and FRED.

Fixes: 625884996bff ("KVM: nVMX: Prepare for enabling CET support for nested guest")
Reported-by: Xin Li <xin@zytor.com>
Suggested-by: Chao Gao <chao.gao@intel.com>
Signed-off-by: Zhao Liu <zhao1.liu@intel.com>
---
 arch/x86/kvm/vmx/nested.c | 59 +++++++++++++++++++++++++++++++++++----
 1 file changed, 53 insertions(+), 6 deletions(-)

diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 151873407abd..35f0bf84b373 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -3613,8 +3613,45 @@ static int nested_vmx_check_permission(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
+/*
+ * Describe the loading state of L2 guest state, i.e. whether VM-Entry loaded
+ * L2's state from vmcs12 into vmcs02, and whether L2's state is synced back to
+ * vmcs12.
+ */
+enum nested_l2_state {
+	/* VM-entry failed before finishing loading L2's state. */
+	L2_STATE_NOT_LOADED,
+	/* VM-entry loaded L2's state from vmcs12 into vmcs02 before L2 runs. */
+	L2_STATE_LOADED_FROM_VMCS12,
+	/* L2 ran, and KVM saved L2's live state to vmcs12 from vmcs02 on VM-exit. */
+	L2_STATE_SAVED_TO_VMCS12,
+};
+
+/*
+ * Return true if L2's guest state in vmcs12 needs to be loaded into vmcs01,
+ * i.e. if L1 should observe L2's state retained on hardware when L1 runs.
+ * @vm_entry_load_control is the VM-Entry control that loads the state on
+ * VM-Entry.
+ *
+ * Note: this helper is used when the VM-exit load (host state) control is off.
+ * Otherwise, host state (L1 state) should be loaded into vmcs01.
+ */
+static bool nested_l2_state_is_live(struct vmcs12 *vmcs12,
+				    u32 vm_entry_load_control,
+				    enum nested_l2_state l2_state)
+{
+	/* normal VM-exit. */
+	if (l2_state == L2_STATE_SAVED_TO_VMCS12)
+		return true;
+
+	/* true iff VM-entry failed after loading L2's state. */
+	return l2_state == L2_STATE_LOADED_FROM_VMCS12 &&
+	       (vmcs12->vm_entry_controls & vm_entry_load_control);
+}
+
 static void load_vmcs12_host_state(struct kvm_vcpu *vcpu,
-				   struct vmcs12 *vmcs12);
+				   struct vmcs12 *vmcs12,
+				   enum nested_l2_state l2_state);
 
 /*
  * If from_vmentry is false, this is being called from state restore (either RSM
@@ -3636,6 +3673,7 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 		.basic = EXIT_REASON_INVALID_STATE,
 		.failed_vmentry = 1,
 	};
+	enum nested_l2_state l2_state = L2_STATE_NOT_LOADED;
 	u32 failed_index;
 
 	trace_kvm_nested_vmenter(kvm_rip_read(vcpu),
@@ -3700,6 +3738,13 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 		goto vmentry_fail_vmexit_guest_mode;
 	}
 
+	/*
+	 * VM-entry has completed the architectural guest-state loading phase;
+	 * MSRs are loaded after guest state, so failures below should retain
+	 * L2's state (see nested_l2_state_is_live()).
+	 */
+	l2_state = L2_STATE_LOADED_FROM_VMCS12;
+
 	if (from_vmentry) {
 		failed_index = nested_vmx_load_msr(vcpu,
 						   vmcs12->vm_entry_msr_load_addr,
@@ -3778,7 +3823,7 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 
 	nested_put_vmcs12_pages(vcpu);
 
-	load_vmcs12_host_state(vcpu, vmcs12);
+	load_vmcs12_host_state(vcpu, vmcs12, l2_state);
 	vmcs12->vm_exit_reason = exit_reason.full;
 	if (enable_shadow_vmcs || nested_vmx_is_evmptr12_valid(vmx))
 		vmx->nested.need_vmcs12_to_shadow_sync = true;
@@ -4798,7 +4843,8 @@ static void prepare_vmcs12(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
  * This function should be called when the active VMCS is L1's (vmcs01).
  */
 static void load_vmcs12_host_state(struct kvm_vcpu *vcpu,
-				   struct vmcs12 *vmcs12)
+				   struct vmcs12 *vmcs12,
+				   enum nested_l2_state l2_state)
 {
 	enum vm_entry_failure_code ignored;
 	struct kvm_segment seg;
@@ -4856,12 +4902,13 @@ static void load_vmcs12_host_state(struct kvm_vcpu *vcpu,
 	/*
 	 * Load CET state from host state if VM_EXIT_LOAD_CET_STATE is set.
 	 * otherwise CET state should be retained across VM-exit, i.e.,
-	 * guest values should be propagated from vmcs12 to vmcs01.
+	 * guest values should be propagated from vmcs12 to vmcs01, but only if
+	 * L2's CET state is live in hardware.
 	 */
 	if (vmcs12->vm_exit_controls & VM_EXIT_LOAD_CET_STATE)
 		vmcs_write_cet_state(vcpu, vmcs12->host_s_cet, vmcs12->host_ssp,
 				     vmcs12->host_ssp_tbl);
-	else
+	else if (nested_l2_state_is_live(vmcs12, VM_ENTRY_LOAD_CET_STATE, l2_state))
 		vmcs_write_cet_state(vcpu, vmcs12->guest_s_cet, vmcs12->guest_ssp,
 				     vmcs12->guest_ssp_tbl);
 
@@ -5193,7 +5240,7 @@ void __nested_vmx_vmexit(struct kvm_vcpu *vcpu, u32 vm_exit_reason,
 						       vmcs12->vm_exit_intr_error_code,
 						       KVM_ISA_VMX);
 
-		load_vmcs12_host_state(vcpu, vmcs12);
+		load_vmcs12_host_state(vcpu, vmcs12, L2_STATE_SAVED_TO_VMCS12);
 
 		/*
 		 * Process events if an injectable IRQ or NMI is pending, even
-- 
2.34.1


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

* [RFC 2/4] KVM: selftests: Synchronize and update VMCS controls
  2026-09-04  2:31 [RFC 0/4] KVM: nVMX: Fix guest (CET) state handling on VM-entry failure Zhao Liu
  2026-09-04  2:31 ` [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it Zhao Liu
@ 2026-09-04  2:31 ` Zhao Liu
  2026-09-04  2:31 ` [RFC 3/4] KVM: selftests: Synchronize and update VMCS encodings Zhao Liu
  2026-09-04  2:31 ` [RFC 4/4] KVM: selftests: Test VM-entry failure handling for nested VM Zhao Liu
  3 siblings, 0 replies; 8+ messages in thread
From: Zhao Liu @ 2026-09-04  2:31 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H . Peter Anvin, Shuah Khan
  Cc: Chao Gao, Xin Li, Sohil Mehta, kvm, linux-kernel, linux-kselftest,
	Zhao Liu

Copy and update VMCS controls definitions from arch/x86/include/asm/vmx.h.

And because VMX_FEATURE_##x is defined in
arch/x86/include/asm/vmxfeatures.h, copy this header file to selftest
as well.

Signed-off-by: Zhao Liu <zhao1.liu@intel.com>
---
 tools/testing/selftests/kvm/include/x86/vmx.h | 145 ++++++++++--------
 .../selftests/kvm/include/x86/vmxfeatures.h   |  93 +++++++++++
 2 files changed, 178 insertions(+), 60 deletions(-)
 create mode 100644 tools/testing/selftests/kvm/include/x86/vmxfeatures.h

diff --git a/tools/testing/selftests/kvm/include/x86/vmx.h b/tools/testing/selftests/kvm/include/x86/vmx.h
index 04f5e34dea3a..0c61cfdd3201 100644
--- a/tools/testing/selftests/kvm/include/x86/vmx.h
+++ b/tools/testing/selftests/kvm/include/x86/vmx.h
@@ -11,85 +11,110 @@
 #include <stdint.h>
 #include "processor.h"
 #include "apic.h"
+#include "vmxfeatures.h"
+
+#define VMCS_CONTROL_BIT(x)	BIT(VMX_FEATURE_##x & 0x1f)
 
 /*
  * Definitions of Primary Processor-Based VM-Execution Controls.
  */
-#define CPU_BASED_INTR_WINDOW_EXITING		0x00000004
-#define CPU_BASED_USE_TSC_OFFSETTING		0x00000008
-#define CPU_BASED_HLT_EXITING			0x00000080
-#define CPU_BASED_INVLPG_EXITING		0x00000200
-#define CPU_BASED_MWAIT_EXITING			0x00000400
-#define CPU_BASED_RDPMC_EXITING			0x00000800
-#define CPU_BASED_RDTSC_EXITING			0x00001000
-#define CPU_BASED_CR3_LOAD_EXITING		0x00008000
-#define CPU_BASED_CR3_STORE_EXITING		0x00010000
-#define CPU_BASED_CR8_LOAD_EXITING		0x00080000
-#define CPU_BASED_CR8_STORE_EXITING		0x00100000
-#define CPU_BASED_TPR_SHADOW			0x00200000
-#define CPU_BASED_NMI_WINDOW_EXITING		0x00400000
-#define CPU_BASED_MOV_DR_EXITING		0x00800000
-#define CPU_BASED_UNCOND_IO_EXITING		0x01000000
-#define CPU_BASED_USE_IO_BITMAPS		0x02000000
-#define CPU_BASED_MONITOR_TRAP			0x08000000
-#define CPU_BASED_USE_MSR_BITMAPS		0x10000000
-#define CPU_BASED_MONITOR_EXITING		0x20000000
-#define CPU_BASED_PAUSE_EXITING			0x40000000
-#define CPU_BASED_ACTIVATE_SECONDARY_CONTROLS	0x80000000
+#define CPU_BASED_INTR_WINDOW_EXITING           VMCS_CONTROL_BIT(INTR_WINDOW_EXITING)
+#define CPU_BASED_USE_TSC_OFFSETTING            VMCS_CONTROL_BIT(USE_TSC_OFFSETTING)
+#define CPU_BASED_HLT_EXITING                   VMCS_CONTROL_BIT(HLT_EXITING)
+#define CPU_BASED_INVLPG_EXITING                VMCS_CONTROL_BIT(INVLPG_EXITING)
+#define CPU_BASED_MWAIT_EXITING                 VMCS_CONTROL_BIT(MWAIT_EXITING)
+#define CPU_BASED_RDPMC_EXITING                 VMCS_CONTROL_BIT(RDPMC_EXITING)
+#define CPU_BASED_RDTSC_EXITING                 VMCS_CONTROL_BIT(RDTSC_EXITING)
+#define CPU_BASED_CR3_LOAD_EXITING		VMCS_CONTROL_BIT(CR3_LOAD_EXITING)
+#define CPU_BASED_CR3_STORE_EXITING		VMCS_CONTROL_BIT(CR3_STORE_EXITING)
+#define CPU_BASED_ACTIVATE_TERTIARY_CONTROLS	VMCS_CONTROL_BIT(TERTIARY_CONTROLS)
+#define CPU_BASED_CR8_LOAD_EXITING              VMCS_CONTROL_BIT(CR8_LOAD_EXITING)
+#define CPU_BASED_CR8_STORE_EXITING             VMCS_CONTROL_BIT(CR8_STORE_EXITING)
+#define CPU_BASED_TPR_SHADOW                    VMCS_CONTROL_BIT(VIRTUAL_TPR)
+#define CPU_BASED_NMI_WINDOW_EXITING		VMCS_CONTROL_BIT(NMI_WINDOW_EXITING)
+#define CPU_BASED_MOV_DR_EXITING                VMCS_CONTROL_BIT(MOV_DR_EXITING)
+#define CPU_BASED_UNCOND_IO_EXITING             VMCS_CONTROL_BIT(UNCOND_IO_EXITING)
+#define CPU_BASED_USE_IO_BITMAPS                VMCS_CONTROL_BIT(USE_IO_BITMAPS)
+#define CPU_BASED_MONITOR_TRAP_FLAG             VMCS_CONTROL_BIT(MONITOR_TRAP_FLAG)
+#define CPU_BASED_USE_MSR_BITMAPS               VMCS_CONTROL_BIT(USE_MSR_BITMAPS)
+#define CPU_BASED_MONITOR_EXITING               VMCS_CONTROL_BIT(MONITOR_EXITING)
+#define CPU_BASED_PAUSE_EXITING                 VMCS_CONTROL_BIT(PAUSE_EXITING)
+#define CPU_BASED_ACTIVATE_SECONDARY_CONTROLS   VMCS_CONTROL_BIT(SEC_CONTROLS)
 
 #define CPU_BASED_ALWAYSON_WITHOUT_TRUE_MSR	0x0401e172
 
 /*
  * Definitions of Secondary Processor-Based VM-Execution Controls.
  */
-#define SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES 0x00000001
-#define SECONDARY_EXEC_ENABLE_EPT		0x00000002
-#define SECONDARY_EXEC_DESC			0x00000004
-#define SECONDARY_EXEC_ENABLE_RDTSCP		0x00000008
-#define SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE	0x00000010
-#define SECONDARY_EXEC_ENABLE_VPID		0x00000020
-#define SECONDARY_EXEC_WBINVD_EXITING		0x00000040
-#define SECONDARY_EXEC_UNRESTRICTED_GUEST	0x00000080
-#define SECONDARY_EXEC_APIC_REGISTER_VIRT	0x00000100
-#define SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY	0x00000200
-#define SECONDARY_EXEC_PAUSE_LOOP_EXITING	0x00000400
-#define SECONDARY_EXEC_RDRAND_EXITING		0x00000800
-#define SECONDARY_EXEC_ENABLE_INVPCID		0x00001000
-#define SECONDARY_EXEC_ENABLE_VMFUNC		0x00002000
-#define SECONDARY_EXEC_SHADOW_VMCS		0x00004000
-#define SECONDARY_EXEC_RDSEED_EXITING		0x00010000
-#define SECONDARY_EXEC_ENABLE_PML		0x00020000
-#define SECONDARY_EPT_VE			0x00040000
-#define SECONDARY_ENABLE_XSAV_RESTORE		0x00100000
-#define SECONDARY_EXEC_TSC_SCALING		0x02000000
-
-#define PIN_BASED_EXT_INTR_MASK			0x00000001
-#define PIN_BASED_NMI_EXITING			0x00000008
-#define PIN_BASED_VIRTUAL_NMIS			0x00000020
-#define PIN_BASED_VMX_PREEMPTION_TIMER		0x00000040
-#define PIN_BASED_POSTED_INTR			0x00000080
+#define SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES VMCS_CONTROL_BIT(VIRT_APIC_ACCESSES)
+#define SECONDARY_EXEC_ENABLE_EPT               VMCS_CONTROL_BIT(EPT)
+#define SECONDARY_EXEC_DESC			VMCS_CONTROL_BIT(DESC_EXITING)
+#define SECONDARY_EXEC_ENABLE_RDTSCP		VMCS_CONTROL_BIT(RDTSCP)
+#define SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE   VMCS_CONTROL_BIT(VIRTUAL_X2APIC)
+#define SECONDARY_EXEC_ENABLE_VPID              VMCS_CONTROL_BIT(VPID)
+#define SECONDARY_EXEC_WBINVD_EXITING		VMCS_CONTROL_BIT(WBINVD_EXITING)
+#define SECONDARY_EXEC_UNRESTRICTED_GUEST	VMCS_CONTROL_BIT(UNRESTRICTED_GUEST)
+#define SECONDARY_EXEC_APIC_REGISTER_VIRT       VMCS_CONTROL_BIT(APIC_REGISTER_VIRT)
+#define SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY    VMCS_CONTROL_BIT(VIRT_INTR_DELIVERY)
+#define SECONDARY_EXEC_PAUSE_LOOP_EXITING	VMCS_CONTROL_BIT(PAUSE_LOOP_EXITING)
+#define SECONDARY_EXEC_RDRAND_EXITING		VMCS_CONTROL_BIT(RDRAND_EXITING)
+#define SECONDARY_EXEC_ENABLE_INVPCID		VMCS_CONTROL_BIT(INVPCID)
+#define SECONDARY_EXEC_ENABLE_VMFUNC            VMCS_CONTROL_BIT(VMFUNC)
+#define SECONDARY_EXEC_SHADOW_VMCS              VMCS_CONTROL_BIT(SHADOW_VMCS)
+#define SECONDARY_EXEC_ENCLS_EXITING		VMCS_CONTROL_BIT(ENCLS_EXITING)
+#define SECONDARY_EXEC_RDSEED_EXITING		VMCS_CONTROL_BIT(RDSEED_EXITING)
+#define SECONDARY_EXEC_ENABLE_PML               VMCS_CONTROL_BIT(PAGE_MOD_LOGGING)
+#define SECONDARY_EXEC_EPT_VIOLATION_VE		VMCS_CONTROL_BIT(EPT_VIOLATION_VE)
+#define SECONDARY_EXEC_PT_CONCEAL_VMX		VMCS_CONTROL_BIT(PT_CONCEAL_VMX)
+#define SECONDARY_EXEC_ENABLE_XSAVES		VMCS_CONTROL_BIT(XSAVES)
+#define SECONDARY_EXEC_MODE_BASED_EPT_EXEC	VMCS_CONTROL_BIT(MODE_BASED_EPT_EXEC)
+#define SECONDARY_EXEC_PT_USE_GPA		VMCS_CONTROL_BIT(PT_USE_GPA)
+#define SECONDARY_EXEC_TSC_SCALING              VMCS_CONTROL_BIT(TSC_SCALING)
+#define SECONDARY_EXEC_ENABLE_USR_WAIT_PAUSE	VMCS_CONTROL_BIT(USR_WAIT_PAUSE)
+#define SECONDARY_EXEC_BUS_LOCK_DETECTION	VMCS_CONTROL_BIT(BUS_LOCK_DETECTION)
+#define SECONDARY_EXEC_NOTIFY_VM_EXITING	VMCS_CONTROL_BIT(NOTIFY_VM_EXITING)
+
+/*
+ * Definitions of Tertiary Processor-Based VM-Execution Controls.
+ */
+#define TERTIARY_EXEC_IPI_VIRT			VMCS_CONTROL_BIT(IPI_VIRT)
+
+#define PIN_BASED_EXT_INTR_MASK                 VMCS_CONTROL_BIT(INTR_EXITING)
+#define PIN_BASED_NMI_EXITING                   VMCS_CONTROL_BIT(NMI_EXITING)
+#define PIN_BASED_VIRTUAL_NMIS                  VMCS_CONTROL_BIT(VIRTUAL_NMIS)
+#define PIN_BASED_VMX_PREEMPTION_TIMER          VMCS_CONTROL_BIT(PREEMPTION_TIMER)
+#define PIN_BASED_POSTED_INTR                   VMCS_CONTROL_BIT(POSTED_INTR)
 
 #define PIN_BASED_ALWAYSON_WITHOUT_TRUE_MSR	0x00000016
 
-#define VM_EXIT_SAVE_DEBUG_CONTROLS		0x00000004
-#define VM_EXIT_HOST_ADDR_SPACE_SIZE		0x00000200
-#define VM_EXIT_LOAD_IA32_PERF_GLOBAL_CTRL	0x00001000
-#define VM_EXIT_ACK_INTR_ON_EXIT		0x00008000
+#define VM_EXIT_SAVE_DEBUG_CONTROLS             0x00000004
+#define VM_EXIT_HOST_ADDR_SPACE_SIZE            0x00000200
+#define VM_EXIT_LOAD_IA32_PERF_GLOBAL_CTRL      0x00001000
+#define VM_EXIT_ACK_INTR_ON_EXIT                0x00008000
 #define VM_EXIT_SAVE_IA32_PAT			0x00040000
 #define VM_EXIT_LOAD_IA32_PAT			0x00080000
-#define VM_EXIT_SAVE_IA32_EFER			0x00100000
-#define VM_EXIT_LOAD_IA32_EFER			0x00200000
-#define VM_EXIT_SAVE_VMX_PREEMPTION_TIMER	0x00400000
+#define VM_EXIT_SAVE_IA32_EFER                  0x00100000
+#define VM_EXIT_LOAD_IA32_EFER                  0x00200000
+#define VM_EXIT_SAVE_VMX_PREEMPTION_TIMER       0x00400000
+#define VM_EXIT_CLEAR_BNDCFGS                   0x00800000
+#define VM_EXIT_PT_CONCEAL_PIP			0x01000000
+#define VM_EXIT_CLEAR_IA32_RTIT_CTL		0x02000000
+#define VM_EXIT_LOAD_CET_STATE                  0x10000000
+#define VM_EXIT_SAVE_IA32_PERF_GLOBAL_CTRL	0x40000000
 
 #define VM_EXIT_ALWAYSON_WITHOUT_TRUE_MSR	0x00036dff
 
-#define VM_ENTRY_LOAD_DEBUG_CONTROLS		0x00000004
-#define VM_ENTRY_IA32E_MODE			0x00000200
-#define VM_ENTRY_SMM				0x00000400
-#define VM_ENTRY_DEACT_DUAL_MONITOR		0x00000800
-#define VM_ENTRY_LOAD_IA32_PERF_GLOBAL_CTRL	0x00002000
+#define VM_ENTRY_LOAD_DEBUG_CONTROLS            0x00000004
+#define VM_ENTRY_IA32E_MODE                     0x00000200
+#define VM_ENTRY_SMM                            0x00000400
+#define VM_ENTRY_DEACT_DUAL_MONITOR             0x00000800
+#define VM_ENTRY_LOAD_IA32_PERF_GLOBAL_CTRL     0x00002000
 #define VM_ENTRY_LOAD_IA32_PAT			0x00004000
-#define VM_ENTRY_LOAD_IA32_EFER			0x00008000
+#define VM_ENTRY_LOAD_IA32_EFER                 0x00008000
+#define VM_ENTRY_LOAD_BNDCFGS                   0x00010000
+#define VM_ENTRY_PT_CONCEAL_PIP			0x00020000
+#define VM_ENTRY_LOAD_IA32_RTIT_CTL		0x00040000
+#define VM_ENTRY_LOAD_CET_STATE                 0x00100000
 
 #define VM_ENTRY_ALWAYSON_WITHOUT_TRUE_MSR	0x000011ff
 
diff --git a/tools/testing/selftests/kvm/include/x86/vmxfeatures.h b/tools/testing/selftests/kvm/include/x86/vmxfeatures.h
new file mode 100644
index 000000000000..0204c004aef8
--- /dev/null
+++ b/tools/testing/selftests/kvm/include/x86/vmxfeatures.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef SELFTEST_KVM_VMXFEATURES_H
+#define SELFTEST_KVM_VMXFEATURES_H
+
+/*
+ * Defines VMX CPU feature bits
+ */
+#define NVMXINTS			5 /* N 32-bit words worth of info */
+
+/*
+ * Note: If the comment begins with a quoted string, that string is used
+ * in /proc/cpuinfo instead of the macro name.  Otherwise, this feature bit
+ * is not displayed in /proc/cpuinfo at all.
+ */
+
+/* Pin-Based VM-Execution Controls, EPT/VPID, APIC and VM-Functions, word 0 */
+#define VMX_FEATURE_INTR_EXITING	( 0*32+  0) /* VM-Exit on vectored interrupts */
+#define VMX_FEATURE_NMI_EXITING		( 0*32+  3) /* VM-Exit on NMIs */
+#define VMX_FEATURE_VIRTUAL_NMIS	( 0*32+  5) /* "vnmi" NMI virtualization */
+#define VMX_FEATURE_PREEMPTION_TIMER	( 0*32+  6) /* "preemption_timer" VMX Preemption Timer */
+#define VMX_FEATURE_POSTED_INTR		( 0*32+  7) /* "posted_intr" Posted Interrupts */
+
+/* EPT/VPID features, scattered to bits 16-23 */
+#define VMX_FEATURE_INVVPID		( 0*32+ 16) /* "invvpid" INVVPID is supported */
+#define VMX_FEATURE_EPT_EXECUTE_ONLY	( 0*32+ 17) /* "ept_x_only" EPT entries can be execute only */
+#define VMX_FEATURE_EPT_AD		( 0*32+ 18) /* "ept_ad" EPT Accessed/Dirty bits */
+#define VMX_FEATURE_EPT_1GB		( 0*32+ 19) /* "ept_1gb" 1GB EPT pages */
+#define VMX_FEATURE_EPT_5LEVEL		( 0*32+ 20) /* "ept_5level" 5-level EPT paging */
+
+/* Aggregated APIC features 24-27 */
+#define VMX_FEATURE_FLEXPRIORITY	( 0*32+ 24) /* "flexpriority" TPR shadow + virt APIC */
+#define VMX_FEATURE_APICV	        ( 0*32+ 25) /* "apicv" TPR shadow + APIC reg virt + virt intr delivery + posted interrupts */
+
+/* VM-Functions, shifted to bits 28-31 */
+#define VMX_FEATURE_EPTP_SWITCHING	( 0*32+ 28) /* "eptp_switching" EPTP switching (in guest) */
+
+/* Primary Processor-Based VM-Execution Controls, word 1 */
+#define VMX_FEATURE_INTR_WINDOW_EXITING ( 1*32+  2) /* VM-Exit if INTRs are unblocked in guest */
+#define VMX_FEATURE_USE_TSC_OFFSETTING	( 1*32+  3) /* "tsc_offset" Offset hardware TSC when read in guest */
+#define VMX_FEATURE_HLT_EXITING		( 1*32+  7) /* VM-Exit on HLT */
+#define VMX_FEATURE_INVLPG_EXITING	( 1*32+  9) /* VM-Exit on INVLPG */
+#define VMX_FEATURE_MWAIT_EXITING	( 1*32+ 10) /* VM-Exit on MWAIT */
+#define VMX_FEATURE_RDPMC_EXITING	( 1*32+ 11) /* VM-Exit on RDPMC */
+#define VMX_FEATURE_RDTSC_EXITING	( 1*32+ 12) /* VM-Exit on RDTSC */
+#define VMX_FEATURE_CR3_LOAD_EXITING	( 1*32+ 15) /* VM-Exit on writes to CR3 */
+#define VMX_FEATURE_CR3_STORE_EXITING	( 1*32+ 16) /* VM-Exit on reads from CR3 */
+#define VMX_FEATURE_TERTIARY_CONTROLS	( 1*32+ 17) /* Enable Tertiary VM-Execution Controls */
+#define VMX_FEATURE_CR8_LOAD_EXITING	( 1*32+ 19) /* VM-Exit on writes to CR8 */
+#define VMX_FEATURE_CR8_STORE_EXITING	( 1*32+ 20) /* VM-Exit on reads from CR8 */
+#define VMX_FEATURE_VIRTUAL_TPR		( 1*32+ 21) /* "vtpr" TPR virtualization, a.k.a. TPR shadow */
+#define VMX_FEATURE_NMI_WINDOW_EXITING	( 1*32+ 22) /* VM-Exit if NMIs are unblocked in guest */
+#define VMX_FEATURE_MOV_DR_EXITING	( 1*32+ 23) /* VM-Exit on accesses to debug registers */
+#define VMX_FEATURE_UNCOND_IO_EXITING	( 1*32+ 24) /* VM-Exit on *all* IN{S} and OUT{S}*/
+#define VMX_FEATURE_USE_IO_BITMAPS	( 1*32+ 25) /* VM-Exit based on I/O port */
+#define VMX_FEATURE_MONITOR_TRAP_FLAG	( 1*32+ 27) /* "mtf" VMX single-step VM-Exits */
+#define VMX_FEATURE_USE_MSR_BITMAPS	( 1*32+ 28) /* VM-Exit based on MSR index */
+#define VMX_FEATURE_MONITOR_EXITING	( 1*32+ 29) /* VM-Exit on MONITOR (MWAIT's accomplice) */
+#define VMX_FEATURE_PAUSE_EXITING	( 1*32+ 30) /* VM-Exit on PAUSE (unconditionally) */
+#define VMX_FEATURE_SEC_CONTROLS	( 1*32+ 31) /* Enable Secondary VM-Execution Controls */
+
+/* Secondary Processor-Based VM-Execution Controls, word 2 */
+#define VMX_FEATURE_VIRT_APIC_ACCESSES	( 2*32+  0) /* "vapic" Virtualize memory mapped APIC accesses */
+#define VMX_FEATURE_EPT			( 2*32+  1) /* "ept" Extended Page Tables, a.k.a. Two-Dimensional Paging */
+#define VMX_FEATURE_DESC_EXITING	( 2*32+  2) /* VM-Exit on {S,L}*DT instructions */
+#define VMX_FEATURE_RDTSCP		( 2*32+  3) /* Enable RDTSCP in guest */
+#define VMX_FEATURE_VIRTUAL_X2APIC	( 2*32+  4) /* Virtualize X2APIC for the guest */
+#define VMX_FEATURE_VPID		( 2*32+  5) /* "vpid" Virtual Processor ID (TLB ASID modifier) */
+#define VMX_FEATURE_WBINVD_EXITING	( 2*32+  6) /* VM-Exit on WBINVD */
+#define VMX_FEATURE_UNRESTRICTED_GUEST	( 2*32+  7) /* "unrestricted_guest" Allow Big Real Mode and other "invalid" states */
+#define VMX_FEATURE_APIC_REGISTER_VIRT	( 2*32+  8) /* "vapic_reg" Hardware emulation of reads to the virtual-APIC */
+#define VMX_FEATURE_VIRT_INTR_DELIVERY	( 2*32+  9) /* "vid" Evaluation and delivery of pending virtual interrupts */
+#define VMX_FEATURE_PAUSE_LOOP_EXITING	( 2*32+ 10) /* "ple" Conditionally VM-Exit on PAUSE at CPL0 */
+#define VMX_FEATURE_RDRAND_EXITING	( 2*32+ 11) /* VM-Exit on RDRAND*/
+#define VMX_FEATURE_INVPCID		( 2*32+ 12) /* Enable INVPCID in guest */
+#define VMX_FEATURE_VMFUNC		( 2*32+ 13) /* Enable VM-Functions (leaf dependent) */
+#define VMX_FEATURE_SHADOW_VMCS		( 2*32+ 14) /* "shadow_vmcs" VMREAD/VMWRITE in guest can access shadow VMCS */
+#define VMX_FEATURE_ENCLS_EXITING	( 2*32+ 15) /* VM-Exit on ENCLS (leaf dependent) */
+#define VMX_FEATURE_RDSEED_EXITING	( 2*32+ 16) /* VM-Exit on RDSEED */
+#define VMX_FEATURE_PAGE_MOD_LOGGING	( 2*32+ 17) /* "pml" Log dirty pages into buffer */
+#define VMX_FEATURE_EPT_VIOLATION_VE	( 2*32+ 18) /* "ept_violation_ve" Conditionally reflect EPT violations as #VE exceptions */
+#define VMX_FEATURE_PT_CONCEAL_VMX	( 2*32+ 19) /* Suppress VMX indicators in Processor Trace */
+#define VMX_FEATURE_XSAVES		( 2*32+ 20) /* Enable XSAVES and XRSTORS in guest */
+#define VMX_FEATURE_MODE_BASED_EPT_EXEC	( 2*32+ 22) /* "ept_mode_based_exec" Enable separate EPT EXEC bits for supervisor vs. user */
+#define VMX_FEATURE_PT_USE_GPA		( 2*32+ 24) /* Processor Trace logs GPAs */
+#define VMX_FEATURE_TSC_SCALING		( 2*32+ 25) /* "tsc_scaling" Scale hardware TSC when read in guest */
+#define VMX_FEATURE_USR_WAIT_PAUSE	( 2*32+ 26) /* "usr_wait_pause" Enable TPAUSE, UMONITOR, UMWAIT in guest */
+#define VMX_FEATURE_ENCLV_EXITING	( 2*32+ 28) /* VM-Exit on ENCLV (leaf dependent) */
+#define VMX_FEATURE_BUS_LOCK_DETECTION	( 2*32+ 30) /* VM-Exit when bus lock caused */
+#define VMX_FEATURE_NOTIFY_VM_EXITING	( 2*32+ 31) /* "notify_vm_exiting" VM-Exit when no event windows after notify window */
+
+/* Tertiary Processor-Based VM-Execution Controls, word 3 */
+#define VMX_FEATURE_IPI_VIRT		( 3*32+  4) /* "ipi_virt" Enable IPI virtualization */
+#endif /* SELFTEST_KVM_VMXFEATURES_H */
-- 
2.34.1


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

* [RFC 3/4] KVM: selftests: Synchronize and update VMCS encodings
  2026-09-04  2:31 [RFC 0/4] KVM: nVMX: Fix guest (CET) state handling on VM-entry failure Zhao Liu
  2026-09-04  2:31 ` [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it Zhao Liu
  2026-09-04  2:31 ` [RFC 2/4] KVM: selftests: Synchronize and update VMCS controls Zhao Liu
@ 2026-09-04  2:31 ` Zhao Liu
  2026-09-04  2:31 ` [RFC 4/4] KVM: selftests: Test VM-entry failure handling for nested VM Zhao Liu
  3 siblings, 0 replies; 8+ messages in thread
From: Zhao Liu @ 2026-09-04  2:31 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H . Peter Anvin, Shuah Khan
  Cc: Chao Gao, Xin Li, Sohil Mehta, kvm, linux-kernel, linux-kselftest,
	Zhao Liu

Copy and update VMCS encodings from arch/x86/include/asm/vmx.h.

Signed-off-by: Zhao Liu <zhao1.liu@intel.com>
---
 tools/testing/selftests/kvm/include/x86/vmx.h | 344 ++++++++++--------
 1 file changed, 183 insertions(+), 161 deletions(-)

diff --git a/tools/testing/selftests/kvm/include/x86/vmx.h b/tools/testing/selftests/kvm/include/x86/vmx.h
index 0c61cfdd3201..4d095883658b 100644
--- a/tools/testing/selftests/kvm/include/x86/vmx.h
+++ b/tools/testing/selftests/kvm/include/x86/vmx.h
@@ -126,187 +126,209 @@
 
 #define EXIT_REASON_FAILED_VMENTRY	0x80000000
 
+/* VMCS Encodings */
 enum vmcs_field {
-	VIRTUAL_PROCESSOR_ID		= 0x00000000,
-	POSTED_INTR_NV			= 0x00000002,
-	GUEST_ES_SELECTOR		= 0x00000800,
-	GUEST_CS_SELECTOR		= 0x00000802,
-	GUEST_SS_SELECTOR		= 0x00000804,
-	GUEST_DS_SELECTOR		= 0x00000806,
-	GUEST_FS_SELECTOR		= 0x00000808,
-	GUEST_GS_SELECTOR		= 0x0000080a,
-	GUEST_LDTR_SELECTOR		= 0x0000080c,
-	GUEST_TR_SELECTOR		= 0x0000080e,
-	GUEST_INTR_STATUS		= 0x00000810,
+	VIRTUAL_PROCESSOR_ID            = 0x00000000,
+	POSTED_INTR_NV                  = 0x00000002,
+	LAST_PID_POINTER_INDEX		= 0x00000008,
+	GUEST_ES_SELECTOR               = 0x00000800,
+	GUEST_CS_SELECTOR               = 0x00000802,
+	GUEST_SS_SELECTOR               = 0x00000804,
+	GUEST_DS_SELECTOR               = 0x00000806,
+	GUEST_FS_SELECTOR               = 0x00000808,
+	GUEST_GS_SELECTOR               = 0x0000080a,
+	GUEST_LDTR_SELECTOR             = 0x0000080c,
+	GUEST_TR_SELECTOR               = 0x0000080e,
+	GUEST_INTR_STATUS               = 0x00000810,
 	GUEST_PML_INDEX			= 0x00000812,
-	HOST_ES_SELECTOR		= 0x00000c00,
-	HOST_CS_SELECTOR		= 0x00000c02,
-	HOST_SS_SELECTOR		= 0x00000c04,
-	HOST_DS_SELECTOR		= 0x00000c06,
-	HOST_FS_SELECTOR		= 0x00000c08,
-	HOST_GS_SELECTOR		= 0x00000c0a,
-	HOST_TR_SELECTOR		= 0x00000c0c,
-	IO_BITMAP_A			= 0x00002000,
-	IO_BITMAP_A_HIGH		= 0x00002001,
-	IO_BITMAP_B			= 0x00002002,
-	IO_BITMAP_B_HIGH		= 0x00002003,
-	MSR_BITMAP			= 0x00002004,
-	MSR_BITMAP_HIGH			= 0x00002005,
-	VM_EXIT_MSR_STORE_ADDR		= 0x00002006,
-	VM_EXIT_MSR_STORE_ADDR_HIGH	= 0x00002007,
-	VM_EXIT_MSR_LOAD_ADDR		= 0x00002008,
-	VM_EXIT_MSR_LOAD_ADDR_HIGH	= 0x00002009,
-	VM_ENTRY_MSR_LOAD_ADDR		= 0x0000200a,
-	VM_ENTRY_MSR_LOAD_ADDR_HIGH	= 0x0000200b,
+	HOST_ES_SELECTOR                = 0x00000c00,
+	HOST_CS_SELECTOR                = 0x00000c02,
+	HOST_SS_SELECTOR                = 0x00000c04,
+	HOST_DS_SELECTOR                = 0x00000c06,
+	HOST_FS_SELECTOR                = 0x00000c08,
+	HOST_GS_SELECTOR                = 0x00000c0a,
+	HOST_TR_SELECTOR                = 0x00000c0c,
+	IO_BITMAP_A                     = 0x00002000,
+	IO_BITMAP_A_HIGH                = 0x00002001,
+	IO_BITMAP_B                     = 0x00002002,
+	IO_BITMAP_B_HIGH                = 0x00002003,
+	MSR_BITMAP                      = 0x00002004,
+	MSR_BITMAP_HIGH                 = 0x00002005,
+	VM_EXIT_MSR_STORE_ADDR          = 0x00002006,
+	VM_EXIT_MSR_STORE_ADDR_HIGH     = 0x00002007,
+	VM_EXIT_MSR_LOAD_ADDR           = 0x00002008,
+	VM_EXIT_MSR_LOAD_ADDR_HIGH      = 0x00002009,
+	VM_ENTRY_MSR_LOAD_ADDR          = 0x0000200a,
+	VM_ENTRY_MSR_LOAD_ADDR_HIGH     = 0x0000200b,
 	PML_ADDRESS			= 0x0000200e,
 	PML_ADDRESS_HIGH		= 0x0000200f,
-	TSC_OFFSET			= 0x00002010,
-	TSC_OFFSET_HIGH			= 0x00002011,
-	VIRTUAL_APIC_PAGE_ADDR		= 0x00002012,
-	VIRTUAL_APIC_PAGE_ADDR_HIGH	= 0x00002013,
+	TSC_OFFSET                      = 0x00002010,
+	TSC_OFFSET_HIGH                 = 0x00002011,
+	VIRTUAL_APIC_PAGE_ADDR          = 0x00002012,
+	VIRTUAL_APIC_PAGE_ADDR_HIGH     = 0x00002013,
 	APIC_ACCESS_ADDR		= 0x00002014,
 	APIC_ACCESS_ADDR_HIGH		= 0x00002015,
-	POSTED_INTR_DESC_ADDR		= 0x00002016,
-	POSTED_INTR_DESC_ADDR_HIGH	= 0x00002017,
-	EPT_POINTER			= 0x0000201a,
-	EPT_POINTER_HIGH		= 0x0000201b,
-	EOI_EXIT_BITMAP0		= 0x0000201c,
-	EOI_EXIT_BITMAP0_HIGH		= 0x0000201d,
-	EOI_EXIT_BITMAP1		= 0x0000201e,
-	EOI_EXIT_BITMAP1_HIGH		= 0x0000201f,
-	EOI_EXIT_BITMAP2		= 0x00002020,
-	EOI_EXIT_BITMAP2_HIGH		= 0x00002021,
-	EOI_EXIT_BITMAP3		= 0x00002022,
-	EOI_EXIT_BITMAP3_HIGH		= 0x00002023,
-	VMREAD_BITMAP			= 0x00002026,
-	VMREAD_BITMAP_HIGH		= 0x00002027,
-	VMWRITE_BITMAP			= 0x00002028,
-	VMWRITE_BITMAP_HIGH		= 0x00002029,
-	XSS_EXIT_BITMAP			= 0x0000202C,
-	XSS_EXIT_BITMAP_HIGH		= 0x0000202D,
+	POSTED_INTR_DESC_ADDR           = 0x00002016,
+	POSTED_INTR_DESC_ADDR_HIGH      = 0x00002017,
+	VM_FUNCTION_CONTROL             = 0x00002018,
+	VM_FUNCTION_CONTROL_HIGH        = 0x00002019,
+	EPT_POINTER                     = 0x0000201a,
+	EPT_POINTER_HIGH                = 0x0000201b,
+	EOI_EXIT_BITMAP0                = 0x0000201c,
+	EOI_EXIT_BITMAP0_HIGH           = 0x0000201d,
+	EOI_EXIT_BITMAP1                = 0x0000201e,
+	EOI_EXIT_BITMAP1_HIGH           = 0x0000201f,
+	EOI_EXIT_BITMAP2                = 0x00002020,
+	EOI_EXIT_BITMAP2_HIGH           = 0x00002021,
+	EOI_EXIT_BITMAP3                = 0x00002022,
+	EOI_EXIT_BITMAP3_HIGH           = 0x00002023,
+	EPTP_LIST_ADDRESS               = 0x00002024,
+	EPTP_LIST_ADDRESS_HIGH          = 0x00002025,
+	VMREAD_BITMAP                   = 0x00002026,
+	VMREAD_BITMAP_HIGH              = 0x00002027,
+	VMWRITE_BITMAP                  = 0x00002028,
+	VMWRITE_BITMAP_HIGH             = 0x00002029,
+	VE_INFORMATION_ADDRESS		= 0x0000202A,
+	VE_INFORMATION_ADDRESS_HIGH	= 0x0000202B,
+	XSS_EXIT_BITMAP                 = 0x0000202C,
+	XSS_EXIT_BITMAP_HIGH            = 0x0000202D,
 	ENCLS_EXITING_BITMAP		= 0x0000202E,
 	ENCLS_EXITING_BITMAP_HIGH	= 0x0000202F,
-	TSC_MULTIPLIER			= 0x00002032,
-	TSC_MULTIPLIER_HIGH		= 0x00002033,
-	GUEST_PHYSICAL_ADDRESS		= 0x00002400,
-	GUEST_PHYSICAL_ADDRESS_HIGH	= 0x00002401,
-	VMCS_LINK_POINTER		= 0x00002800,
-	VMCS_LINK_POINTER_HIGH		= 0x00002801,
-	GUEST_IA32_DEBUGCTL		= 0x00002802,
-	GUEST_IA32_DEBUGCTL_HIGH	= 0x00002803,
+	TSC_MULTIPLIER                  = 0x00002032,
+	TSC_MULTIPLIER_HIGH             = 0x00002033,
+	TERTIARY_VM_EXEC_CONTROL	= 0x00002034,
+	TERTIARY_VM_EXEC_CONTROL_HIGH	= 0x00002035,
+	SHARED_EPT_POINTER		= 0x0000203C,
+	PID_POINTER_TABLE		= 0x00002042,
+	PID_POINTER_TABLE_HIGH		= 0x00002043,
+	GUEST_PHYSICAL_ADDRESS          = 0x00002400,
+	GUEST_PHYSICAL_ADDRESS_HIGH     = 0x00002401,
+	VMCS_LINK_POINTER               = 0x00002800,
+	VMCS_LINK_POINTER_HIGH          = 0x00002801,
+	GUEST_IA32_DEBUGCTL             = 0x00002802,
+	GUEST_IA32_DEBUGCTL_HIGH        = 0x00002803,
 	GUEST_IA32_PAT			= 0x00002804,
 	GUEST_IA32_PAT_HIGH		= 0x00002805,
 	GUEST_IA32_EFER			= 0x00002806,
 	GUEST_IA32_EFER_HIGH		= 0x00002807,
 	GUEST_IA32_PERF_GLOBAL_CTRL	= 0x00002808,
 	GUEST_IA32_PERF_GLOBAL_CTRL_HIGH= 0x00002809,
-	GUEST_PDPTR0			= 0x0000280a,
-	GUEST_PDPTR0_HIGH		= 0x0000280b,
-	GUEST_PDPTR1			= 0x0000280c,
-	GUEST_PDPTR1_HIGH		= 0x0000280d,
-	GUEST_PDPTR2			= 0x0000280e,
-	GUEST_PDPTR2_HIGH		= 0x0000280f,
-	GUEST_PDPTR3			= 0x00002810,
-	GUEST_PDPTR3_HIGH		= 0x00002811,
-	GUEST_BNDCFGS			= 0x00002812,
-	GUEST_BNDCFGS_HIGH		= 0x00002813,
+	GUEST_PDPTR0                    = 0x0000280a,
+	GUEST_PDPTR0_HIGH               = 0x0000280b,
+	GUEST_PDPTR1                    = 0x0000280c,
+	GUEST_PDPTR1_HIGH               = 0x0000280d,
+	GUEST_PDPTR2                    = 0x0000280e,
+	GUEST_PDPTR2_HIGH               = 0x0000280f,
+	GUEST_PDPTR3                    = 0x00002810,
+	GUEST_PDPTR3_HIGH               = 0x00002811,
+	GUEST_BNDCFGS                   = 0x00002812,
+	GUEST_BNDCFGS_HIGH              = 0x00002813,
+	GUEST_IA32_RTIT_CTL		= 0x00002814,
+	GUEST_IA32_RTIT_CTL_HIGH	= 0x00002815,
 	HOST_IA32_PAT			= 0x00002c00,
 	HOST_IA32_PAT_HIGH		= 0x00002c01,
 	HOST_IA32_EFER			= 0x00002c02,
 	HOST_IA32_EFER_HIGH		= 0x00002c03,
 	HOST_IA32_PERF_GLOBAL_CTRL	= 0x00002c04,
 	HOST_IA32_PERF_GLOBAL_CTRL_HIGH	= 0x00002c05,
-	PIN_BASED_VM_EXEC_CONTROL	= 0x00004000,
-	CPU_BASED_VM_EXEC_CONTROL	= 0x00004002,
-	EXCEPTION_BITMAP		= 0x00004004,
-	PAGE_FAULT_ERROR_CODE_MASK	= 0x00004006,
-	PAGE_FAULT_ERROR_CODE_MATCH	= 0x00004008,
-	CR3_TARGET_COUNT		= 0x0000400a,
-	VM_EXIT_CONTROLS		= 0x0000400c,
-	VM_EXIT_MSR_STORE_COUNT		= 0x0000400e,
-	VM_EXIT_MSR_LOAD_COUNT		= 0x00004010,
-	VM_ENTRY_CONTROLS		= 0x00004012,
-	VM_ENTRY_MSR_LOAD_COUNT		= 0x00004014,
-	VM_ENTRY_INTR_INFO_FIELD	= 0x00004016,
-	VM_ENTRY_EXCEPTION_ERROR_CODE	= 0x00004018,
-	VM_ENTRY_INSTRUCTION_LEN	= 0x0000401a,
-	TPR_THRESHOLD			= 0x0000401c,
-	SECONDARY_VM_EXEC_CONTROL	= 0x0000401e,
-	PLE_GAP				= 0x00004020,
-	PLE_WINDOW			= 0x00004022,
-	VM_INSTRUCTION_ERROR		= 0x00004400,
-	VM_EXIT_REASON			= 0x00004402,
-	VM_EXIT_INTR_INFO		= 0x00004404,
-	VM_EXIT_INTR_ERROR_CODE		= 0x00004406,
-	IDT_VECTORING_INFO_FIELD	= 0x00004408,
-	IDT_VECTORING_ERROR_CODE	= 0x0000440a,
-	VM_EXIT_INSTRUCTION_LEN		= 0x0000440c,
-	VMX_INSTRUCTION_INFO		= 0x0000440e,
-	GUEST_ES_LIMIT			= 0x00004800,
-	GUEST_CS_LIMIT			= 0x00004802,
-	GUEST_SS_LIMIT			= 0x00004804,
-	GUEST_DS_LIMIT			= 0x00004806,
-	GUEST_FS_LIMIT			= 0x00004808,
-	GUEST_GS_LIMIT			= 0x0000480a,
-	GUEST_LDTR_LIMIT		= 0x0000480c,
-	GUEST_TR_LIMIT			= 0x0000480e,
-	GUEST_GDTR_LIMIT		= 0x00004810,
-	GUEST_IDTR_LIMIT		= 0x00004812,
-	GUEST_ES_AR_BYTES		= 0x00004814,
-	GUEST_CS_AR_BYTES		= 0x00004816,
-	GUEST_SS_AR_BYTES		= 0x00004818,
-	GUEST_DS_AR_BYTES		= 0x0000481a,
-	GUEST_FS_AR_BYTES		= 0x0000481c,
-	GUEST_GS_AR_BYTES		= 0x0000481e,
-	GUEST_LDTR_AR_BYTES		= 0x00004820,
-	GUEST_TR_AR_BYTES		= 0x00004822,
-	GUEST_INTERRUPTIBILITY_INFO	= 0x00004824,
-	GUEST_ACTIVITY_STATE		= 0X00004826,
-	GUEST_SYSENTER_CS		= 0x0000482A,
-	VMX_PREEMPTION_TIMER_VALUE	= 0x0000482E,
-	HOST_IA32_SYSENTER_CS		= 0x00004c00,
-	CR0_GUEST_HOST_MASK		= 0x00006000,
-	CR4_GUEST_HOST_MASK		= 0x00006002,
-	CR0_READ_SHADOW			= 0x00006004,
-	CR4_READ_SHADOW			= 0x00006006,
-	CR3_TARGET_VALUE0		= 0x00006008,
-	CR3_TARGET_VALUE1		= 0x0000600a,
-	CR3_TARGET_VALUE2		= 0x0000600c,
-	CR3_TARGET_VALUE3		= 0x0000600e,
-	EXIT_QUALIFICATION		= 0x00006400,
-	GUEST_LINEAR_ADDRESS		= 0x0000640a,
-	GUEST_CR0			= 0x00006800,
-	GUEST_CR3			= 0x00006802,
-	GUEST_CR4			= 0x00006804,
-	GUEST_ES_BASE			= 0x00006806,
-	GUEST_CS_BASE			= 0x00006808,
-	GUEST_SS_BASE			= 0x0000680a,
-	GUEST_DS_BASE			= 0x0000680c,
-	GUEST_FS_BASE			= 0x0000680e,
-	GUEST_GS_BASE			= 0x00006810,
-	GUEST_LDTR_BASE			= 0x00006812,
-	GUEST_TR_BASE			= 0x00006814,
-	GUEST_GDTR_BASE			= 0x00006816,
-	GUEST_IDTR_BASE			= 0x00006818,
-	GUEST_DR7			= 0x0000681a,
-	GUEST_RSP			= 0x0000681c,
-	GUEST_RIP			= 0x0000681e,
-	GUEST_RFLAGS			= 0x00006820,
-	GUEST_PENDING_DBG_EXCEPTIONS	= 0x00006822,
-	GUEST_SYSENTER_ESP		= 0x00006824,
-	GUEST_SYSENTER_EIP		= 0x00006826,
-	HOST_CR0			= 0x00006c00,
-	HOST_CR3			= 0x00006c02,
-	HOST_CR4			= 0x00006c04,
-	HOST_FS_BASE			= 0x00006c06,
-	HOST_GS_BASE			= 0x00006c08,
-	HOST_TR_BASE			= 0x00006c0a,
-	HOST_GDTR_BASE			= 0x00006c0c,
-	HOST_IDTR_BASE			= 0x00006c0e,
-	HOST_IA32_SYSENTER_ESP		= 0x00006c10,
-	HOST_IA32_SYSENTER_EIP		= 0x00006c12,
-	HOST_RSP			= 0x00006c14,
-	HOST_RIP			= 0x00006c16,
+	PIN_BASED_VM_EXEC_CONTROL       = 0x00004000,
+	CPU_BASED_VM_EXEC_CONTROL       = 0x00004002,
+	EXCEPTION_BITMAP                = 0x00004004,
+	PAGE_FAULT_ERROR_CODE_MASK      = 0x00004006,
+	PAGE_FAULT_ERROR_CODE_MATCH     = 0x00004008,
+	CR3_TARGET_COUNT                = 0x0000400a,
+	VM_EXIT_CONTROLS                = 0x0000400c,
+	VM_EXIT_MSR_STORE_COUNT         = 0x0000400e,
+	VM_EXIT_MSR_LOAD_COUNT          = 0x00004010,
+	VM_ENTRY_CONTROLS               = 0x00004012,
+	VM_ENTRY_MSR_LOAD_COUNT         = 0x00004014,
+	VM_ENTRY_INTR_INFO_FIELD        = 0x00004016,
+	VM_ENTRY_EXCEPTION_ERROR_CODE   = 0x00004018,
+	VM_ENTRY_INSTRUCTION_LEN        = 0x0000401a,
+	TPR_THRESHOLD                   = 0x0000401c,
+	SECONDARY_VM_EXEC_CONTROL       = 0x0000401e,
+	PLE_GAP                         = 0x00004020,
+	PLE_WINDOW                      = 0x00004022,
+	NOTIFY_WINDOW                   = 0x00004024,
+	VM_INSTRUCTION_ERROR            = 0x00004400,
+	VM_EXIT_REASON                  = 0x00004402,
+	VM_EXIT_INTR_INFO               = 0x00004404,
+	VM_EXIT_INTR_ERROR_CODE         = 0x00004406,
+	IDT_VECTORING_INFO_FIELD        = 0x00004408,
+	IDT_VECTORING_ERROR_CODE        = 0x0000440a,
+	VM_EXIT_INSTRUCTION_LEN         = 0x0000440c,
+	VMX_INSTRUCTION_INFO            = 0x0000440e,
+	GUEST_ES_LIMIT                  = 0x00004800,
+	GUEST_CS_LIMIT                  = 0x00004802,
+	GUEST_SS_LIMIT                  = 0x00004804,
+	GUEST_DS_LIMIT                  = 0x00004806,
+	GUEST_FS_LIMIT                  = 0x00004808,
+	GUEST_GS_LIMIT                  = 0x0000480a,
+	GUEST_LDTR_LIMIT                = 0x0000480c,
+	GUEST_TR_LIMIT                  = 0x0000480e,
+	GUEST_GDTR_LIMIT                = 0x00004810,
+	GUEST_IDTR_LIMIT                = 0x00004812,
+	GUEST_ES_AR_BYTES               = 0x00004814,
+	GUEST_CS_AR_BYTES               = 0x00004816,
+	GUEST_SS_AR_BYTES               = 0x00004818,
+	GUEST_DS_AR_BYTES               = 0x0000481a,
+	GUEST_FS_AR_BYTES               = 0x0000481c,
+	GUEST_GS_AR_BYTES               = 0x0000481e,
+	GUEST_LDTR_AR_BYTES             = 0x00004820,
+	GUEST_TR_AR_BYTES               = 0x00004822,
+	GUEST_INTERRUPTIBILITY_INFO     = 0x00004824,
+	GUEST_ACTIVITY_STATE            = 0x00004826,
+	GUEST_SYSENTER_CS               = 0x0000482A,
+	VMX_PREEMPTION_TIMER_VALUE      = 0x0000482E,
+	HOST_IA32_SYSENTER_CS           = 0x00004c00,
+	CR0_GUEST_HOST_MASK             = 0x00006000,
+	CR4_GUEST_HOST_MASK             = 0x00006002,
+	CR0_READ_SHADOW                 = 0x00006004,
+	CR4_READ_SHADOW                 = 0x00006006,
+	CR3_TARGET_VALUE0               = 0x00006008,
+	CR3_TARGET_VALUE1               = 0x0000600a,
+	CR3_TARGET_VALUE2               = 0x0000600c,
+	CR3_TARGET_VALUE3               = 0x0000600e,
+	EXIT_QUALIFICATION              = 0x00006400,
+	GUEST_LINEAR_ADDRESS            = 0x0000640a,
+	GUEST_CR0                       = 0x00006800,
+	GUEST_CR3                       = 0x00006802,
+	GUEST_CR4                       = 0x00006804,
+	GUEST_ES_BASE                   = 0x00006806,
+	GUEST_CS_BASE                   = 0x00006808,
+	GUEST_SS_BASE                   = 0x0000680a,
+	GUEST_DS_BASE                   = 0x0000680c,
+	GUEST_FS_BASE                   = 0x0000680e,
+	GUEST_GS_BASE                   = 0x00006810,
+	GUEST_LDTR_BASE                 = 0x00006812,
+	GUEST_TR_BASE                   = 0x00006814,
+	GUEST_GDTR_BASE                 = 0x00006816,
+	GUEST_IDTR_BASE                 = 0x00006818,
+	GUEST_DR7                       = 0x0000681a,
+	GUEST_RSP                       = 0x0000681c,
+	GUEST_RIP                       = 0x0000681e,
+	GUEST_RFLAGS                    = 0x00006820,
+	GUEST_PENDING_DBG_EXCEPTIONS    = 0x00006822,
+	GUEST_SYSENTER_ESP              = 0x00006824,
+	GUEST_SYSENTER_EIP              = 0x00006826,
+	GUEST_S_CET                     = 0x00006828,
+	GUEST_SSP                       = 0x0000682a,
+	GUEST_INTR_SSP_TABLE            = 0x0000682c,
+	HOST_CR0                        = 0x00006c00,
+	HOST_CR3                        = 0x00006c02,
+	HOST_CR4                        = 0x00006c04,
+	HOST_FS_BASE                    = 0x00006c06,
+	HOST_GS_BASE                    = 0x00006c08,
+	HOST_TR_BASE                    = 0x00006c0a,
+	HOST_GDTR_BASE                  = 0x00006c0c,
+	HOST_IDTR_BASE                  = 0x00006c0e,
+	HOST_IA32_SYSENTER_ESP          = 0x00006c10,
+	HOST_IA32_SYSENTER_EIP          = 0x00006c12,
+	HOST_RSP                        = 0x00006c14,
+	HOST_RIP                        = 0x00006c16,
+	HOST_S_CET                      = 0x00006c18,
+	HOST_SSP                        = 0x00006c1a,
+	HOST_INTR_SSP_TABLE             = 0x00006c1c
 };
 
 struct vmx_msr_entry {
-- 
2.34.1


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

* [RFC 4/4] KVM: selftests: Test VM-entry failure handling for nested VM
  2026-09-04  2:31 [RFC 0/4] KVM: nVMX: Fix guest (CET) state handling on VM-entry failure Zhao Liu
                   ` (2 preceding siblings ...)
  2026-09-04  2:31 ` [RFC 3/4] KVM: selftests: Synchronize and update VMCS encodings Zhao Liu
@ 2026-09-04  2:31 ` Zhao Liu
  2026-09-04  2:50   ` sashiko-bot
  3 siblings, 1 reply; 8+ messages in thread
From: Zhao Liu @ 2026-09-04  2:31 UTC (permalink / raw)
  To: Sean Christopherson, Paolo Bonzini, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, x86, H . Peter Anvin, Shuah Khan
  Cc: Chao Gao, Xin Li, Sohil Mehta, kvm, linux-kernel, linux-kselftest,
	Zhao Liu

Add a test for the CET state L1 sees after a nested VM-entry fails. What
L1 should see depends on two controls: the VM-entry "load CET state"
control, which tells whether L2's state was ever loaded, and the VM-exit
"load CET state" control, which tells whether L1's state is reloaded from
vmcs12's host-state area.

A failed VM-entry is a VM-exit, but it leaves nested mode in an unusual
way: vmcs12's guest state was never copied back from vmcs02. So when KVM
copies vmcs12->guest_* into vmcs01 while emulating the VM-exit, it
overwrites the CET state that L1 is really running with, using L2's
values.

Test all four combinations of the two controls, with both failure points,
and check L1's S_CET and INT_SSP_TAB:

  exit  entry  failure      what L1 should see
  ----  -----  -----------  -------------------------------------
   1     x     either       vmcs12's host state
   0     0     either       L1's own state, L2's was never loaded
   0     1     MSR-load     vmcs12's guest state
   0     1     guest-state  L1's own state, see below

The last row is the only case where KVM is more precise than the SDM
requires. The SDM vol. 3C, section 29.3, "Checking and Loading Guest
State" says guest state is checked and loaded at the same time, so a
guest-state failure may be found after some state is already loaded. In
other words, the CET state after such a failure is undefined, unless the
VM-exit control reloads the host state.

The VM-entry MSR-load list is different: it is handled only after guest
state is loaded, so a failure there always keeps whatever the VM-entry
loaded.

For that undefined case, KVM picks the simplest well-defined behavior: it
emulates every EXIT_REASON_INVALID_STATE VM-entry failure as if no guest
state was loaded at all, so L1 keeps its own state. Note, this holds even
though KVM does have guest-state checks that run after L2's CET state is
written into vmcs02, e.g. vmx_guest_state_valid() and
nested_vmx_load_cr3() in prepare_vmcs02(); the failed VM-entry throws
vmcs02 away, and L1's state never left vmcs01. Test KVM's behavior
here.

This test case uses the two failure points, that covers KVM's two exit
paths: a bad guest CR0 (PG set, PE clear) gives EXIT_REASON_INVALID_STATE,
and a read-only MSR in the VM-entry MSR-load list triggers
EXIT_REASON_MSR_LOAD_FAIL.

Only CET state is checked for now, and other state loaded by the
VM-entry/VM-exit controls can be added later.

Signed-off-by: Zhao Liu <zhao1.liu@intel.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/include/x86/processor.h     |   1 +
 .../x86/vmx_nested_entry_fail_state_test.c    | 311 ++++++++++++++++++
 3 files changed, 313 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/vmx_nested_entry_fail_state_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39..352ae003183a 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -134,6 +134,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_apicv_updates_test
 TEST_GEN_PROGS_x86 += x86/vmx_exception_with_invalid_guest_state
 TEST_GEN_PROGS_x86 += x86/vmx_msrs_test
 TEST_GEN_PROGS_x86 += x86/vmx_invalid_nested_guest_state
+TEST_GEN_PROGS_x86 += x86/vmx_nested_entry_fail_state_test
 TEST_GEN_PROGS_x86 += x86/vmx_nested_la57_state_test
 TEST_GEN_PROGS_x86 += x86/apic_bus_clock_test
 TEST_GEN_PROGS_x86 += x86/xapic_ipi_test
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
index 6e6f70035508..870ef1bb1ba1 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -79,6 +79,7 @@ const char *ex_str(int vector);
 #define X86_CR4_SMEP		(1ul << 20)
 #define X86_CR4_SMAP		(1ul << 21)
 #define X86_CR4_PKE		(1ul << 22)
+#define X86_CR4_CET		(1ul << 23)
 
 struct xstate_header {
 	u64				xstate_bv;
diff --git a/tools/testing/selftests/kvm/x86/vmx_nested_entry_fail_state_test.c b/tools/testing/selftests/kvm/x86/vmx_nested_entry_fail_state_test.c
new file mode 100644
index 000000000000..ed0cb045ed53
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/vmx_nested_entry_fail_state_test.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Nested VM-entry failure state test
+ *
+ * Copyright (C) 2026, Intel, Inc.
+ */
+#include <asm/msr-index.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "test_util.h"
+#include "vmx.h"
+
+static gva_t entry_msr_load_list_gva;
+static gpa_t entry_msr_load_list_gpa;
+
+enum state_src {
+	STATE_SRC_L1,		/* L1's own state */
+	STATE_SRC_VMCS12_GUEST,	/* vmcs12->guest_*, i.e. L2's state */
+	STATE_SRC_VMCS12_HOST,	/* vmcs12->host_*, i.e. host state to load */
+	NR_STATE_SRCS,
+};
+
+static bool has_shstk;
+struct cet_state {
+	u64 s_cet;
+	u64 ssp;
+	u64 ssp_tbl;
+};
+
+struct test_state {
+	struct cet_state cet;
+};
+
+/*
+ * In practice, states of STATE_SRC_L1 and STATE_SRC_VMCS12_HOST should be
+ * same, i.e., the state of STATE_SRC_VMCS12_HOST is copied from host (L1).
+ *
+ * But in this test, to distinguish the "load (host) state" operation, make
+ * STATE_SRC_VMCS12_HOST different from STATE_SRC_L1.
+ */
+static const struct test_state states[NR_STATE_SRCS] = {
+	[STATE_SRC_L1] = {
+		.cet = {
+			.s_cet		= CET_SHSTK_EN,
+			/*
+			 * SSP is not an MSR; the L1 SSP must be configured
+			 * using the KVM_SET_ONE_REG ioctl. To simplify the
+			 * test logic, checking the other MSRs is enough to
+			 * identify the state source, so that there's no need
+			 * to configure different SSPs.
+			 */
+			.ssp		= 0x0000111111111000UL,
+			.ssp_tbl	= 0x0000123456789000UL,
+		}
+	},
+	[STATE_SRC_VMCS12_GUEST] = {
+		.cet = {
+			.s_cet		= CET_SHSTK_EN | CET_WRSS_EN,
+			.ssp		= 0x0000111111111000UL,
+			.ssp_tbl	= 0x00000abcabcab000UL,
+		}
+	},
+	[STATE_SRC_VMCS12_HOST] = {
+		.cet = {
+			.s_cet		= CET_WRSS_EN,
+			.ssp		= 0x0000111111111000UL,
+			.ssp_tbl	= 0x00007edcba987000UL,
+		}
+	},
+};
+
+/* VM-entry-load and VM-exit-load, each 0 or 1. There are 4 combinations in total. */
+#define NR_CTRL_COMBOS		4
+/* each load control combination includes 2 cases with different VM-exit reasons. */
+#define NR_CASES		(NR_CTRL_COMBOS * 2)
+
+/* Define what state the hardware should retain in different cases. */
+static enum state_src get_expected_state_src(bool entry_load, bool exit_load,
+					     uint32_t exit_reason)
+{
+	if (exit_load)
+		return STATE_SRC_VMCS12_HOST;
+
+	/* no entry load, no exit load - L1's own state is retained. */
+	if (!entry_load)
+		return STATE_SRC_L1;
+
+	/*
+	 * From the Intel SDM volume 3, chapter 29.3 "CHECKING AND LOADING
+	 * GUEST STATE":
+	 *   The following operations take place concurrently:
+	 *     (1) the guest-state area of the VMCS is checked to ensure that,
+	 *         after the VM entry completes, the state of the logical
+	 *         processor is consistent with IA-32 and Intel 64
+	 *         architectures;
+	 *     (2) processor state is loaded from the guest-state area or as
+	 *         specified by the VM-entry control fields;
+	 *     and (3) address-range monitoring is cleared.
+	 *   Because the checking and the loading occur concurrently, a failure
+	 *   may be discovered only after some state has been loaded.
+	 *
+	 * I.e. the state left behind by a guest-state failure is
+	 * architecturally undefined. KVM picks the simplest well-defined
+	 * behavior: an EXIT_REASON_INVALID_STATE VM-entry failure is emulated
+	 * as if no guest state was loaded at all, so L1 keeps its own state.
+	 * Note, that holds even for KVM's guest-state checks that run after
+	 * L2's CET state is written into vmcs02, e.g. vmx_guest_state_valid()
+	 * and nested_vmx_load_cr3() in prepare_vmcs02(); the failed VM-entry
+	 * throws vmcs02 away while L1's state never left vmcs01.
+	 */
+	if (exit_reason == EXIT_REASON_INVALID_STATE)
+		return STATE_SRC_L1;
+
+	return STATE_SRC_VMCS12_GUEST;
+}
+
+static void l1_load_own_state(void)
+{
+	if (has_shstk) {
+		const struct cet_state *cet = &states[STATE_SRC_L1].cet;
+
+		wrmsr(MSR_IA32_S_CET, cet->s_cet);
+		wrmsr(MSR_IA32_INT_SSP_TAB, cet->ssp_tbl);
+	}
+}
+
+static void l1_program_vmcs12_cet(bool entry_load, bool exit_load)
+{
+	const struct cet_state *guest = &states[STATE_SRC_VMCS12_GUEST].cet;
+	const struct cet_state *host = &states[STATE_SRC_VMCS12_HOST].cet;
+	u64 entry_ctrl = vmreadz(VM_ENTRY_CONTROLS) & ~VM_ENTRY_LOAD_CET_STATE;
+	u64 exit_ctrl = vmreadz(VM_EXIT_CONTROLS) & ~VM_EXIT_LOAD_CET_STATE;
+
+	GUEST_ASSERT(!vmwrite(GUEST_S_CET, guest->s_cet));
+	GUEST_ASSERT(!vmwrite(GUEST_SSP, guest->ssp));
+	GUEST_ASSERT(!vmwrite(GUEST_INTR_SSP_TABLE, guest->ssp_tbl));
+	GUEST_ASSERT(!vmwrite(HOST_S_CET, host->s_cet));
+	GUEST_ASSERT(!vmwrite(HOST_SSP, host->ssp));
+	GUEST_ASSERT(!vmwrite(HOST_INTR_SSP_TABLE, host->ssp_tbl));
+
+	if (entry_load) {
+		entry_ctrl |= VM_ENTRY_LOAD_CET_STATE;
+
+		/*
+		 * Enable CET for L2 -- although it's not required for this test,
+		 * since L2 never runs (VM-entry always fails), so these never
+		 * reach real hardware -- just trying to emulate a L2 guest running
+		 * CET.
+		 */
+		GUEST_ASSERT(!vmwrite(GUEST_CR0, vmreadz(GUEST_CR0) | X86_CR0_WP));
+		GUEST_ASSERT(!vmwrite(GUEST_CR4, vmreadz(GUEST_CR4) | X86_CR4_CET));
+	}
+	if (exit_load)
+		exit_ctrl |= VM_EXIT_LOAD_CET_STATE;
+
+	GUEST_ASSERT(!vmwrite(VM_ENTRY_CONTROLS, entry_ctrl));
+	GUEST_ASSERT(!vmwrite(VM_EXIT_CONTROLS, exit_ctrl));
+}
+
+static void l1_program_vmcs12_state(bool entry_load, bool exit_load)
+{
+	if (has_shstk)
+		l1_program_vmcs12_cet(entry_load, exit_load);
+}
+
+/*
+ * Set the invalid guest state to fail the VM-entry check
+ * -- triggering a VM-exit (EXIT_REASON_INVALID_STATE).
+ */
+static void l1_break_guest_state(void)
+{
+	u64 cr0 = vmreadz(GUEST_CR0);
+
+	GUEST_ASSERT(!vmwrite(GUEST_CR0, (cr0 | X86_CR0_PG) & ~X86_CR0_PE));
+}
+
+/*
+ * Set the invalid MSR load list to fail the VM-entry check
+ * -- triggering a VM-exit (EXIT_REASON_MSR_LOAD_FAIL).
+ */
+static void l1_break_msr_load_list(void)
+{
+	struct vmx_msr_entry *list = (void *)entry_msr_load_list_gva;
+
+	list[0] = (struct vmx_msr_entry){
+		.index = MSR_IA32_UCODE_REV,
+		.reserved = 0,
+		.value = 0,
+	};
+
+	GUEST_ASSERT(!vmwrite(VM_ENTRY_MSR_LOAD_ADDR, entry_msr_load_list_gpa));
+	GUEST_ASSERT(!vmwrite(VM_ENTRY_MSR_LOAD_COUNT, 1));
+}
+
+static void l1_check_observed_cet(bool entry_load, bool exit_load, uint32_t exit_reason)
+{
+	enum state_src src = get_expected_state_src(entry_load, exit_load, exit_reason);
+	const struct cet_state *expect = &states[src].cet;
+	u64 s_cet = rdmsr(MSR_IA32_S_CET);
+	u64 ssp_tbl = rdmsr(MSR_IA32_INT_SSP_TAB);
+
+	__GUEST_ASSERT(s_cet == expect->s_cet && ssp_tbl == expect->ssp_tbl,
+		       "entry_load=%d exit_load=%d exit_reason=%u: "
+		       "expect src %d S_CET=%#lx INT_SSP_TAB=%#lx, "
+		       "got S_CET=%#lx INT_SSP_TAB=%#lx",
+		       entry_load, exit_load, exit_reason, src, expect->s_cet,
+		       expect->ssp_tbl, s_cet, ssp_tbl);
+}
+
+static void l1_check_observed_state(bool entry_load, bool exit_load, uint32_t exit_reason)
+{
+	if (has_shstk)
+		l1_check_observed_cet(entry_load, exit_load, exit_reason);
+}
+
+static void l1_run_case(struct vmx_pages *vmx, bool entry_load, bool exit_load,
+			uint32_t exit_reason)
+{
+	l1_load_own_state();
+
+	GUEST_ASSERT(load_vmcs(vmx));
+	prepare_vmcs(vmx, NULL);
+
+	l1_program_vmcs12_state(entry_load, exit_load);
+
+	switch (exit_reason) {
+	case EXIT_REASON_INVALID_STATE:
+		l1_break_guest_state();
+		break;
+	case EXIT_REASON_MSR_LOAD_FAIL:
+		l1_break_msr_load_list();
+		break;
+	default:
+		GUEST_FAIL("unexpected exit reason %u", exit_reason);
+	}
+
+	GUEST_ASSERT(!vmlaunch());
+	GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON),
+			EXIT_REASON_FAILED_VMENTRY | exit_reason);
+
+	l1_check_observed_state(entry_load, exit_load, exit_reason);
+
+	GUEST_SYNC(0);
+}
+
+static void l1_guest_code(struct vmx_pages *vmx)
+{
+	int ctrl;
+
+	GUEST_ASSERT(prepare_for_vmx_operation(vmx));
+
+	for (ctrl = 0; ctrl < NR_CTRL_COMBOS; ctrl++) {
+		bool entry_load = ctrl & BIT(0);
+		bool exit_load = ctrl & BIT(1);
+
+		/* 2 cases with different VM-exit reasons. */
+		l1_run_case(vmx, entry_load, exit_load, EXIT_REASON_INVALID_STATE);
+		l1_run_case(vmx, entry_load, exit_load, EXIT_REASON_MSR_LOAD_FAIL);
+	}
+
+	GUEST_DONE();
+}
+
+int main(int argc, char *argv[])
+{
+	gva_t vmx_pages_gva;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+	int ncases = 0;
+
+	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
+
+	has_shstk = kvm_cpu_has(X86_FEATURE_SHSTK);
+	TEST_REQUIRE(has_shstk);
+
+	vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+
+	entry_msr_load_list_gva = vm_alloc_page(vm);
+	entry_msr_load_list_gpa = addr_gva2gpa(vm, entry_msr_load_list_gva);
+	sync_global_to_guest(vm, entry_msr_load_list_gva);
+	sync_global_to_guest(vm, entry_msr_load_list_gpa);
+
+	sync_global_to_guest(vm, has_shstk);
+
+	vcpu_alloc_vmx(vm, &vmx_pages_gva);
+	vcpu_args_set(vcpu, 1, vmx_pages_gva);
+
+	for (;;) {
+		vcpu_run(vcpu);
+		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+		switch (get_ucall(vcpu, &uc)) {
+		case UCALL_SYNC:
+			ncases++;
+			break;
+		case UCALL_DONE:
+			TEST_ASSERT(ncases == NR_CASES, "L1 ran %d cases, expected %d",
+				    ncases, NR_CASES);
+			goto done;
+		case UCALL_ABORT:
+			REPORT_GUEST_ASSERT(uc);
+		default:
+			TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
+		}
+	}
+done:
+	kvm_vm_free(vm);
+	return 0;
+}
-- 
2.34.1


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

* Re: [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it
  2026-09-04  2:31 ` [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it Zhao Liu
@ 2026-09-04  2:49   ` sashiko-bot
  2026-09-04 16:42   ` Sean Christopherson
  1 sibling, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-09-04  2:49 UTC (permalink / raw)
  To: Zhao Liu; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] KVM fails to propagate L2's modified BNDCFGS and PAT to L1 when VM_EXIT_CLEAR_BNDCFGS or VM_EXIT_LOAD_IA32_PAT is clear.
--

commit cf134f2b2249852af5c9f30ff344481f932e8567
Author: Zhao Liu <zhao1.liu@intel.com>

KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it

On a nested VM-exit that disables VM_EXIT_LOAD_CET_STATE, only copy L2's
CET state from vmcs12 to vmcs01 if VM-entry really loaded that state,
i.e. don't copy when VM-entry fails before loading guest state.

> diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
> index 151873407abd3..35f0bf84b373a 100644
> --- a/arch/x86/kvm/vmx/nested.c
> +++ b/arch/x86/kvm/vmx/nested.c

[ ... ]

> @@ -4856,12 +4902,13 @@ static void load_vmcs12_host_state(struct kvm_vcpu *vcpu,
>  	/*
>  	 * Load CET state from host state if VM_EXIT_LOAD_CET_STATE is set.
>  	 * otherwise CET state should be retained across VM-exit, i.e.,
> -	 * guest values should be propagated from vmcs12 to vmcs01.
> +	 * guest values should be propagated from vmcs12 to vmcs01, but only if
> +	 * L2's CET state is live in hardware.
>  	 */
>  	if (vmcs12->vm_exit_controls & VM_EXIT_LOAD_CET_STATE)
>  		vmcs_write_cet_state(vcpu, vmcs12->host_s_cet, vmcs12->host_ssp,
>  				     vmcs12->host_ssp_tbl);
> -	else
> +	else if (nested_l2_state_is_live(vmcs12, VM_ENTRY_LOAD_CET_STATE, l2_state))
>  		vmcs_write_cet_state(vcpu, vmcs12->guest_s_cet, vmcs12->guest_ssp,
>  				     vmcs12->guest_ssp_tbl);
>  

[Severity: High]
This isn't a bug introduced by this patch, but this pre-existing logic error
applies to BNDCFGS and PAT in arch/x86/kvm/vmx/nested.c:load_vmcs12_host_state()
as well.

If VM_EXIT_CLEAR_BNDCFGS is false, KVM skips updating vmcs01's BNDCFGS:

    /* If not VM_EXIT_CLEAR_BNDCFGS, the L2 value propagates to L1.  */
    if (vmcs12->vm_exit_controls & VM_EXIT_CLEAR_BNDCFGS)
        vmcs_write64(GUEST_BNDCFGS, 0);

This causes L1 to incorrectly resume with its original state rather than
retaining L2's modified state.

Similarly, if VM_EXIT_LOAD_IA32_PAT is false, KVM skips updating vmcs01's PAT:

    if (vmcs12->vm_exit_controls & VM_EXIT_LOAD_IA32_PAT) {
        vmcs_write64(GUEST_IA32_PAT, vmcs12->host_ia32_pat);
        vcpu->arch.pat = vmcs12->host_ia32_pat;
    }

This causes L1 to incorrectly resume with its original PAT instead of retaining
L2's modified state.

In both cases, a malicious or buggy L1 hypervisor that disables these VM-exit
controls could cause L1 to resume with the wrong state (its pre-VMENTRY state
instead of L2's state). This breaks the architectural guarantee of state
preservation on VM-exit and leads to guest hypervisor state corruption.

Should we also propagate L2's modified BNDCFGS and PAT to vmcs01 when these
controls are clear, perhaps reusing the new nested_l2_state_is_live() logic?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260904023105.1167376-1-zhao1.liu@intel.com?part=1

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

* Re: [RFC 4/4] KVM: selftests: Test VM-entry failure handling for nested VM
  2026-09-04  2:31 ` [RFC 4/4] KVM: selftests: Test VM-entry failure handling for nested VM Zhao Liu
@ 2026-09-04  2:50   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-09-04  2:50 UTC (permalink / raw)
  To: Zhao Liu; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The test configures an architecturally invalid VMCS12 host CET state, causing physical hardware to reject KVM's L1 VM-Entry.
- [High] L1 configures `MSR_IA32_S_CET` without enabling `CR4.CET` and `CR0.WP`, causing physical hardware to reject KVM's L1 VM-Entry.
--

commit 0167d3a43f196a36484ad8300e0c72caf7470746
Author: Zhao Liu <zhao1.liu@intel.com>

KVM: selftests: Test VM-entry failure handling for nested VM
    
This commit adds a test for the CET state L1 sees after a nested VM-entry
fails. It tests all four combinations of the two VM-entry/exit load CET
state controls with two different failure points. It then checks L1's
S_CET and INT_SSP_TAB MSRs to verify the expected behavior.

> diff --git a/tools/testing/selftests/kvm/x86/vmx_nested_entry_fail_state_test.c b/tools/testing/selftests/kvm/x86/vmx_nested_entry_fail_state_test.c
> new file mode 100644
> index 0000000000000..ed0cb045ed53a
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86/vmx_nested_entry_fail_state_test.c
[ ... ]
> +static const struct test_state states[NR_STATE_SRCS] = {
> +	[STATE_SRC_L1] = {
> +		.cet = {
> +			.s_cet		= CET_SHSTK_EN,
> +			/*
> +			 * SSP is not an MSR; the L1 SSP must be configured
> +			 * using the KVM_SET_ONE_REG ioctl. To simplify the
> +			 * test logic, checking the other MSRs is enough to
> +			 * identify the state source, so that there's no need
> +			 * to configure different SSPs.
> +			 */
> +			.ssp		= 0x0000111111111000UL,
> +			.ssp_tbl	= 0x0000123456789000UL,
> +		}
> +	},
> +	[STATE_SRC_VMCS12_GUEST] = {
> +		.cet = {
> +			.s_cet		= CET_SHSTK_EN | CET_WRSS_EN,
> +			.ssp		= 0x0000111111111000UL,
> +			.ssp_tbl	= 0x00000abcabcab000UL,
> +		}
> +	},
> +	[STATE_SRC_VMCS12_HOST] = {
> +		.cet = {
> +			.s_cet		= CET_WRSS_EN,

[Severity: High]
Does the states array configure an architecturally invalid VMCS12 host CET
state?

According to the Intel SDM (Vol 3C, Section 26.2.3), if SHSTK_EN is 0,
WRSS_EN must also be 0. Here, it sets CET_WRSS_EN (WRSS_EN=1) while SHSTK_EN
is 0.

Because KVM currently does not validate this specific rule, it accepts the
invalid host state and loads it into L1's state during the nested VM-exit.

When KVM then performs a physical vmresume to enter L1, the physical CPU
enforces the rule, rejects the VM-Entry with EXIT_REASON_INVALID_STATE (33),
and causes KVM to inject a KVM_EXIT_INTERNAL_ERROR, crashing the selftest.

> +			.ssp		= 0x0000111111111000UL,
> +			.ssp_tbl	= 0x00007edcba987000UL,
> +		}
> +	},
> +};
[ ... ]
> +static void l1_load_own_state(void)
> +{
> +	if (has_shstk) {
> +		const struct cet_state *cet = &states[STATE_SRC_L1].cet;
> +
> +		wrmsr(MSR_IA32_S_CET, cet->s_cet);
> +		wrmsr(MSR_IA32_INT_SSP_TAB, cet->ssp_tbl);

[Severity: High]
Will this write to MSR_IA32_S_CET cause physical hardware to reject KVM's L1
VM-Entry?

In l1_load_own_state(), L1 writes CET_SHSTK_EN to MSR_IA32_S_CET, but the
test never enables X86_CR4_CET or X86_CR0_WP in L1's control registers.

KVM statically enables VM_ENTRY_LOAD_CET_STATE for L1 guests when CET is
supported, which means the physical CPU enforces guest state checks on L1
during vmlaunch or vmresume.

According to the Intel SDM (Vol 3C, Section 26.3.1.1), if
VM_ENTRY_LOAD_CET_STATE is 1 and IA32_S_CET.SHSTK_EN is 1, then CR4.CET and
CR0.WP must both be 1.

Because L1's guest state in vmcs01 has SHSTK_EN=1 but CR4.CET=0 and CR0.WP=0,
the physical hardware will reject the VM-Entry into L1 with
EXIT_REASON_INVALID_STATE (33).

This results in KVM intercepting the failure, injecting
KVM_EXIT_INTERNAL_ERROR, and crashing the selftest.

Should the test explicitly set X86_CR4_CET and X86_CR0_WP before configuring
CET?

> +	}
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260904023105.1167376-1-zhao1.liu@intel.com?part=4

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

* Re: [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it
  2026-09-04  2:31 ` [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it Zhao Liu
  2026-09-04  2:49   ` sashiko-bot
@ 2026-09-04 16:42   ` Sean Christopherson
  1 sibling, 0 replies; 8+ messages in thread
From: Sean Christopherson @ 2026-09-04 16:42 UTC (permalink / raw)
  To: Zhao Liu
  Cc: Paolo Bonzini, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, x86, H . Peter Anvin, Shuah Khan, Chao Gao, Xin Li,
	Sohil Mehta, kvm, linux-kernel, linux-kselftest

On Fri, Sep 04, 2026, Zhao Liu wrote:
> On a nested VM-exit that disables VM_EXIT_LOAD_CET_STATE, only copy L2's
> CET state from vmcs12 to vmcs01 if VM-entry really loaded that state,
> i.e. don't copy when VM-entry fails before loading guest state.
>
> The state, that L1 should see after a L2 VM-exit, depends on three
> things: the VM-exit load (host state) control, whether VM-entry loaded
> L2's state, and whether L2 ran.

No, it depends on four things.  The three things you listed, plus uarch-specific
ordering of checks and loads of guest state.  The SDM says:

  the following operations take place concurrently:
  
    (1) the guest-state area of the VMCS is checked to ensure that, after the
        VM entry completes, the state of the logical processor is consistent
	with IA-32 and Intel 64 architectures;
    (2) processor state is loaded from the guest-state area or as specified by
        the VM-entry control fields; and (3) address-range monitoring is cleared.

  Because the checking and the loading occur concurrently, a failure may be
  discovered only after some state has been loaded. For this reason, the logical
  processor responds to such failures by loading state from the host-state area,
  as it would for a VM exit.

So KVM is well within its rights to load vmcs01 state from vmcs12 even on VM-Exit
due to a failed VM-Entry.  More at the very bottom (below the first diff).
 
> For CET, there are 4 cases:
> 
>  1) VM_EXIT_LOAD_CET_STATE is set. Load L1's CET state from vmcs12's
>     host fields, no matter what happened before. KVM already does this.
> 
>  2) VM_EXIT_LOAD_CET_STATE is clear, and VM-entry loaded L2's CET
>     state. Whether it's the normal VM-exit or VM-entry failure exit,
>     the guest's (L2's) state should be retained, so copy vmcs12's guest
>     fields into vmcs01 to give L1 the same result.
> 
>  3) VM_EXIT_LOAD_CET_STATE is clear, VM-entry didn't load L2's CET
>     state, and L2 never ran. This is the typical case that VM-entry
>     fails before loading guest state, the CPU keeps L1's own state, so
>     do nothing.
> 
>  4) VM_EXIT_LOAD_CET_STATE is clear, VM-entry didn't load L2's CET
>     state, but L2 ran and exited normally. The CPU keeps L1's state
>     again, but L2 could have changed it while running, so still copy
>     vmcs12's guest fields into vmcs01, because they hold what L2 left
>     behind.

...

> @@ -3700,6 +3738,13 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
>  		goto vmentry_fail_vmexit_guest_mode;
>  	}
>  
> +	/*
> +	 * VM-entry has completed the architectural guest-state loading phase;
> +	 * MSRs are loaded after guest state, so failures below should retain
> +	 * L2's state (see nested_l2_state_is_live()).
> +	 */
> +	l2_state = L2_STATE_LOADED_FROM_VMCS12;

This works, but IMO is unnecessarily convoluted.  KVM doesn't need to manually
query vmcs12 entry controls, we can and should instead call sync_vmcs02_to_vmcs12()
in the failed VM-Entry path if vmcs02 has been prepared with vmcs12 state.  Then
the only thing that needs to be communicated to load_vmcs12_host_state() is
whether or not vmcs02 was prepared.  This would make KVM consistent with how it
handles guest state on failed VM-Entry VM-Exits that occur because of hardware's
consistency checks (KVM only validates a subset of guest state).

So I'm fairly certain it's just the below change (I also tweaked the comment about
CET state because it's not at all obvious why vmcs12 would hold the correct state).

diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 47599e7312cf..cb6a910eed2e 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -3613,8 +3613,9 @@ static int nested_vmx_check_permission(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
-static void load_vmcs12_host_state(struct kvm_vcpu *vcpu,
-				   struct vmcs12 *vmcs12);
+static void load_vmcs12_host_state(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
+				   bool prepared_vmcs02);
+static void sync_vmcs02_to_vmcs12(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12);
 
 /*
  * If from_vmentry is false, this is being called from state restore (either RSM
@@ -3636,6 +3637,7 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 		.basic = EXIT_REASON_INVALID_STATE,
 		.failed_vmentry = 1,
 	};
+	bool prepared_vmcs02 = false;
 	u32 failed_index;
 
 	trace_kvm_nested_vmenter(kvm_rip_read(vcpu),
@@ -3700,6 +3702,8 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 		goto vmentry_fail_vmexit_guest_mode;
 	}
 
+	prepared_vmcs02 = true;
+
 	if (from_vmentry) {
 		failed_index = nested_vmx_load_msr(vcpu,
 						   vmcs12->vm_entry_msr_load_addr,
@@ -3758,6 +3762,9 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 	 * 26.7 "VM-entry failures during or after loading guest state".
 	 */
 vmentry_fail_vmexit_guest_mode:
+	if (prepared_vmcs02)
+		sync_vmcs02_to_vmcs12(vcpu, vmcs12);
+
 	if (vmcs12->cpu_based_vm_exec_control & CPU_BASED_USE_TSC_OFFSETTING)
 		vcpu->arch.tsc_offset -= vmcs12->tsc_offset;
 
@@ -3778,7 +3785,7 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 
 	nested_put_vmcs12_pages(vcpu);
 
-	load_vmcs12_host_state(vcpu, vmcs12);
+	load_vmcs12_host_state(vcpu, vmcs12, prepared_vmcs02);
 	vmcs12->vm_exit_reason = exit_reason.full;
 	if (enable_shadow_vmcs || nested_vmx_is_evmptr12_valid(vmx))
 		vmx->nested.need_vmcs12_to_shadow_sync = true;
@@ -4797,8 +4804,8 @@ static void prepare_vmcs12(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
  * Failures During or After Loading Guest State").
  * This function should be called when the active VMCS is L1's (vmcs01).
  */
-static void load_vmcs12_host_state(struct kvm_vcpu *vcpu,
-				   struct vmcs12 *vmcs12)
+static void load_vmcs12_host_state(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
+				   bool prepared_vmcs02)
 {
 	enum vm_entry_failure_code ignored;
 	struct kvm_segment seg;
@@ -4854,14 +4861,15 @@ static void load_vmcs12_host_state(struct kvm_vcpu *vcpu,
 		vmcs_write64(GUEST_BNDCFGS, 0);
 
 	/*
-	 * Load CET state from host state if VM_EXIT_LOAD_CET_STATE is set.
-	 * otherwise CET state should be retained across VM-exit, i.e.,
-	 * guest values should be propagated from vmcs12 to vmcs01.
+	 * If CET state should be retained across VM-exit, i.e. isn't loaded
+	 * from host state fields, and vmcs02 was prepared with guest state and
+	 * thus synchronized back to vmcs12 (CET state is unconditionally saved
+	 * on VM-Exit), then propagate the guest's values from vmcs12 to vmcs01.
 	 */
 	if (vmcs12->vm_exit_controls & VM_EXIT_LOAD_CET_STATE)
 		vmcs_write_cet_state(vcpu, vmcs12->host_s_cet, vmcs12->host_ssp,
 				     vmcs12->host_ssp_tbl);
-	else
+	else if (prepared_vmcs02)
 		vmcs_write_cet_state(vcpu, vmcs12->guest_s_cet, vmcs12->guest_ssp,
 				     vmcs12->guest_ssp_tbl);
 
@@ -5193,7 +5201,7 @@ void __nested_vmx_vmexit(struct kvm_vcpu *vcpu, u32 vm_exit_reason,
 						       vmcs12->vm_exit_intr_error_code,
 						       KVM_ISA_VMX);
 
-		load_vmcs12_host_state(vcpu, vmcs12);
+		load_vmcs12_host_state(vcpu, vmcs12, true);
 
 		/*
 		 * Process events if an injectable IRQ or NMI is pending, even

As for nitpicking the SDM, KVM doesn't *need* to wait until prepare_vmcs02()
completes cleanly, KVM just needs to guarantee that vmcs12 holds the correct state
if L2 state is loaded from vmcs12 on VM-Exit.  Because even on failure,
prepare_vmcs02() has already loaded (most) guest state into vmcs02.  So we could
sync vmcs02=>vmcs12 on any failure after switching to vmcs02, if we adjusted
prepare_vmcs02() to fully prepare vmcs02 before do its final consistency checks.
I.e. we could do the below on top.

However, as much as I want to be pedantic on this point, I don't think we should
actually do the below.  I combed through the flows and can't find anything that
would result in loading the wrong L1 state if KVM mostly prepares vmcs02 but
doesn't do sync_vmcs02_to_vmcs12().  And marking vmcs02 as prepared if and only
if it's fully prepared is much more obviously correct.

diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index cb6a910eed2e..68f3b09a1531 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -2843,25 +2843,8 @@ static int prepare_vmcs02(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
 	/* Note: may modify VM_ENTRY/EXIT_CONTROLS and GUEST/HOST_IA32_EFER */
 	vmx_set_efer(vcpu, vcpu->arch.efer);
 
-	/*
-	 * Guest state is invalid and unrestricted guest is disabled,
-	 * which means L1 attempted VMEntry to L2 with invalid state.
-	 * Fail the VMEntry.
-	 *
-	 * However when force loading the guest state (SMM exit or
-	 * loading nested state after migration, it is possible to
-	 * have invalid guest state now, which will be later fixed by
-	 * restoring L2 register state
-	 */
-	if (CC(from_vmentry && !vmx_guest_state_valid(vcpu))) {
-		*entry_failure_code = ENTRY_FAIL_DEFAULT;
-		return -EINVAL;
-	}
-
-	/* Shadow page tables on either EPT or shadow page tables. */
-	if (nested_vmx_load_cr3(vcpu, vmcs12->guest_cr3, nested_cpu_has_ept(vmcs12),
-				from_vmentry, entry_failure_code))
-		return -EINVAL;
+	kvm_rsp_write(vcpu, vmcs12->guest_rsp);
+	kvm_rip_write(vcpu, vmcs12->guest_rip);
 
 	/*
 	 * Immediately write vmcs02.GUEST_CR3.  It will be propagated to vmcs12
@@ -2882,6 +2865,50 @@ static int prepare_vmcs02(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
 		vmcs_write64(GUEST_PDPTR3, vmcs12->guest_pdptr3);
 	}
 
+	/*
+	 * DO NOT write vmcs02 after this point!  I.e. load all L2 guest state
+	 * into vmcs02 before performining *any* consistency checks on vmcs12
+	 * guest state.  KVM loads vmcs12 guest from vmcs02 on *all* VM-Exits,
+	 * including VM-Exits due to failed VM-Entry.  Doing so avoids having
+	 * to track which fields are live in vmcs12, and when.  While counter-
+	 * intuitive, loading guest state before it is checked is explicitly
+	 * allowed by the SDM, which says:
+	 *
+	 * the following operations take place concurrently:
+	 *
+	 *  (1) the guest-state area of the VMCS is checked to ensure that,
+	 *      after the VM entry completes, the state of the logical processor
+	 *      is consistent with IA-32 and Intel 64 architectures;
+	 *  (2) processor state is loaded from the guest-state area or as
+	 *      specified by the VM-entry control fields;
+	 *  (3) and address-range monitoring is cleared.
+	 *
+	 * Because the checking and the loading occur concurrently, a failure
+	 * may be discovered only after some state has been loaded. For this
+	 * reason, the logical processor responds to such failures by loading
+	 * state from the host-state area, as it would for a VM exit.
+	 */
+
+	/*
+	 * Guest state is invalid and unrestricted guest is disabled,
+	 * which means L1 attempted VMEntry to L2 with invalid state.
+	 * Fail the VMEntry.
+	 *
+	 * However when force loading the guest state (SMM exit or
+	 * loading nested state after migration, it is possible to
+	 * have invalid guest state now, which will be later fixed by
+	 * restoring L2 register state
+	 */
+	if (CC(from_vmentry && !vmx_guest_state_valid(vcpu))) {
+		*entry_failure_code = ENTRY_FAIL_DEFAULT;
+		return -EINVAL;
+	}
+
+	/* Shadow page tables on either EPT or shadow page tables. */
+	if (nested_vmx_load_cr3(vcpu, vmcs12->guest_cr3, nested_cpu_has_ept(vmcs12),
+				from_vmentry, entry_failure_code))
+		return -EINVAL;
+
 	if ((vmcs12->vm_entry_controls & VM_ENTRY_LOAD_IA32_PERF_GLOBAL_CTRL) &&
 	    kvm_pmu_has_perf_global_ctrl(vcpu_to_pmu(vcpu)) &&
 	    WARN_ON_ONCE(__kvm_emulate_msr_write(vcpu, MSR_CORE_PERF_GLOBAL_CTRL,
@@ -2890,9 +2917,6 @@ static int prepare_vmcs02(struct kvm_vcpu *vcpu, struct vmcs12 *vmcs12,
 		return -EINVAL;
 	}
 
-	kvm_rsp_write(vcpu, vmcs12->guest_rsp);
-	kvm_rip_write(vcpu, vmcs12->guest_rip);
-
 	/*
 	 * It was observed that genuine Hyper-V running in L1 doesn't reset
 	 * 'hv_clean_fields' by itself, it only sets the corresponding dirty
@@ -3696,14 +3720,19 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 
 	enter_guest_mode(vcpu);
 
+	/*
+	 * Mark vmcs02 as having been prepared, even if preparation ultimately
+	 * fails, as the final consistency checks performed by prepare_vmcs02()
+	 * are done only after vmcs02 has been loaded with guest state.
+	 */
+	prepared_vmcs02 = true;
+
 	if (prepare_vmcs02(vcpu, vmcs12, from_vmentry, &entry_failure_code)) {
 		exit_reason.basic = EXIT_REASON_INVALID_STATE;
 		vmcs12->exit_qualification = entry_failure_code;
 		goto vmentry_fail_vmexit_guest_mode;
 	}
 
-	prepared_vmcs02 = true;
-
 	if (from_vmentry) {
 		failed_index = nested_vmx_load_msr(vcpu,
 						   vmcs12->vm_entry_msr_load_addr,
@@ -3762,8 +3791,7 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 	 * 26.7 "VM-entry failures during or after loading guest state".
 	 */
 vmentry_fail_vmexit_guest_mode:
-	if (prepared_vmcs02)
-		sync_vmcs02_to_vmcs12(vcpu, vmcs12);
+	sync_vmcs02_to_vmcs12(vcpu, vmcs12);
 
 	if (vmcs12->cpu_based_vm_exec_control & CPU_BASED_USE_TSC_OFFSETTING)
 		vcpu->arch.tsc_offset -= vmcs12->tsc_offset;
@@ -3775,6 +3803,8 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu,
 	 */
 	kvm_service_local_tlb_flush_requests(vcpu);
 
+	sync_vmcs02_to_vmcs12(vcpu, vmcs12);
+
 	leave_guest_mode(vcpu);
 
 vmentry_fail_vmexit:


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

end of thread, other threads:[~2026-09-04 16:42 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-04  2:31 [RFC 0/4] KVM: nVMX: Fix guest (CET) state handling on VM-entry failure Zhao Liu
2026-09-04  2:31 ` [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it Zhao Liu
2026-09-04  2:49   ` sashiko-bot
2026-09-04 16:42   ` Sean Christopherson
2026-09-04  2:31 ` [RFC 2/4] KVM: selftests: Synchronize and update VMCS controls Zhao Liu
2026-09-04  2:31 ` [RFC 3/4] KVM: selftests: Synchronize and update VMCS encodings Zhao Liu
2026-09-04  2:31 ` [RFC 4/4] KVM: selftests: Test VM-entry failure handling for nested VM Zhao Liu
2026-09-04  2:50   ` sashiko-bot

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