Hi Sean, Thanks for your feedback!
On Fri, Sep 04, 2026 at 09:42:02AM -0700, Sean Christopherson wrote: > Date: Fri, 4 Sep 2026 09:42:02 -0700 > From: Sean Christopherson <[email protected]> > Subject: Re: [RFC 1/4] KVM: nVMX: Don't copy L2's CET state to L1 if > VM-entry didn't load it > > 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). Yes, I agree. I think the specific way KVM handles guest state can also be considered "uarch-specific" and is not part of the ABI guarantee. > 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). I tested this solution, and it passed the selftest in patch 4. Thanks! But when I think more deeply about what the sync_vmcs02_to_vmcs12() function actually does, I think there are still two points worth discussing: 1. We call sync_vmcs02_to_vmcs12() along the vm-entry failure path; for CET, this effectively overwrites the CET guest in vmcs12 with the expected L2 CET state. Per SDM 29.8, regarding VM-entry failure handling - it states: Although this process resembles that of a VM exit, many steps taken during a VM exit do not occur for these VM-entry failures: • Most VM-exit information fields are not updated (see step 1 above). • The valid bit in the injected-event identification field is not cleared. -> • The guest-state area is not modified. • No MSRs are saved into the VM-exit MSR-store area. The key point here I want to refer is that the guest state should remain unchanged. And considering this case, when VM_ENTRY_LOAD_CET_STATE is cleared, KVM tries to load L1's CET state to vmcs02 (vmcs01->vmcs02), but sync_vmcs02_to_vmcs12() loads vmcs02 CET states back to vmcs12 (vmcs02->vmcs12), which changes the guest state and such vmcs12 guest state is visible to L1 (so, the whole states transition path is: vmcs01->vmcs02->vmcs12, the vmcs12 is changed!). 2. I feel sync_vmcs02_to_vmcs12() might be doing too much. One issue is it updates vmcs12->guest_activity_state based on vcpu->arch.mp_state, However, the "correct" vcpu->arch.mp_state is set based on vmcs12->guest_activity_state only after the VM-entry succeeds (see nested_vmx_run). Therefore, in an extreme case (just code reading, not tested yet), GUEST_ACTIVITY_HLT might be set to guest_activity_state, but because the vm-entry fails, the value of vcpu->arch.mp_state is not updated, so sync_vmcs02_to_vmcs12() re-calculates an incorrect guest_activity_state. Another issue is about vmcs12->guest_linear_address. Per SDM 29.8 again (the 1st point I listed above), guest_linear_address, as the VM-exit information field, is not necessary to be updated. So, in short, I think sync_vmcs02_to_vmcs12() updates too much state, and it seems we shouldn't directly modify the guest state visible to L1 (i.e., the guest state in vmcs12). > 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); Based on the two points mentioned above, does Option B (which I described in my cover letter) seem more reasonable? Instead of updatding almost all states to vmcs12, we can re-use pre_enter_* states: vmentry_fail_vmexit_guest_mode: if (prepared_vmcs02) vmcs_read_cet_state(vcpu, &vmx->nested.pre_vmenter_s_cet, &vmx->nested.pre_vmenter_ssp, &vmx->nested.pre_vmenter_ssp_tbl); ... > -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); then update L1's state based on pre_vmenter_*, and keep vmcs12 guest states upchanged: else if (prepared_vmcs02) vmcs_write_cet_state(vcpu, vmx->nested.pre_vmenter_s_cet, vmx->nested.pre_vmenter_ssp, vmx->nested.pre_vmenter_ssp_tbl); In addition, in this way, pre_vmenter_* should also be synchronized on the normal exit path with sync_vmcs02_to_vmcs12(). However, I’m still hesitant because this intermediate state seems too fragmented. But considering that vmcs12 is L1 visible — and shouldn’t be modified arbitrarily — maybe an intermediate state like this is necessary? Or maybe we should just go back to my original patch, i.e., Option A, which eliminates the intermediate state. > 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. Yes, I think both approaches - whether to wait until prepare_vmcs02() completes cleanly or not - are consistent with the SDM; they can be viewed as differences in the KVM uarch implementation :-) What the user sees is the hardware state and the guest VMCS state as presented under a given load control combination. Thanks, Zhao

