From: Zhao Liu <zhao1.liu@intel.com>
To: Sean Christopherson <seanjc@google.com>,
Paolo Bonzini <pbonzini@redhat.com>,
Thomas Gleixner <tglx@kernel.org>, Ingo Molnar <mingo@redhat.com>,
Borislav Petkov <bp@alien8.de>,
Dave Hansen <dave.hansen@linux.intel.com>,
x86@kernel.org, "H . Peter Anvin" <hpa@zytor.com>,
Shuah Khan <shuah@kernel.org>
Cc: Chao Gao <chao.gao@intel.com>, Xin Li <xin@zytor.com>,
Sohil Mehta <sohil.mehta@intel.com>,
kvm@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-kselftest@vger.kernel.org, Zhao Liu <zhao1.liu@intel.com>
Subject: [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it
Date: Fri, 4 Sep 2026 10:31:02 +0800 [thread overview]
Message-ID: <20260904023105.1167376-2-zhao1.liu@intel.com> (raw)
In-Reply-To: <20260904023105.1167376-1-zhao1.liu@intel.com>
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
next prev parent reply other threads:[~2026-09-04 2:31 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
2026-09-04 16:42 ` [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if VM-entry didn't load it Sean Christopherson
2026-09-07 12:47 ` Zhao Liu
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
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=20260904023105.1167376-2-zhao1.liu@intel.com \
--to=zhao1.liu@intel.com \
--cc=bp@alien8.de \
--cc=chao.gao@intel.com \
--cc=dave.hansen@linux.intel.com \
--cc=hpa@zytor.com \
--cc=kvm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=pbonzini@redhat.com \
--cc=seanjc@google.com \
--cc=shuah@kernel.org \
--cc=sohil.mehta@intel.com \
--cc=tglx@kernel.org \
--cc=x86@kernel.org \
--cc=xin@zytor.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
all inboxes | Powered by JetHome®