public inbox for stable@vger.kernel.org
 help / color / mirror / Atom feed
From: Sean Christopherson <seanjc@google.com>
To: Yosry Ahmed <yosry.ahmed@linux.dev>
Cc: Paolo Bonzini <pbonzini@redhat.com>,
	kvm@vger.kernel.org, linux-kernel@vger.kernel.org,
	 stable@vger.kernel.org
Subject: Re: [PATCH 1/4] KVM: nSVM: Sync next_rip to cached vmcb12 after VMRUN of L2
Date: Tue, 10 Feb 2026 16:09:11 -0800	[thread overview]
Message-ID: <aYvIpwjsJ50Ns4ho@google.com> (raw)
In-Reply-To: <ck57mmdt5phh64cadoqxylw5q2b72ffmabmlzmpphaf27lbtxw@4kscovf6ahve>

On Tue, Feb 10, 2026, Yosry Ahmed wrote:
> On Tue, Feb 10, 2026 at 11:20:19AM -0800, Sean Christopherson wrote:
> > > > Actually, I take that back, I have no idea how this code works.  How does e.g.
> > > > exit_info_1 not get clobbered on save/restore?
> > > 
> > > I *think* KVM always sets the error_code and exit_info_* fields before
> > > synthesizing a #VMEXIT to L1, usually right before calling
> > > nested_svm_vmeit(), so no chance for save/restore in between.
> > 
> > Ugh, right, KVM generally doesn't recognize signals until after invoking the exit
> > handler.  Actually, even that isn't the key, it's that this flaw only affects
> > "in/out" fields, as you note above.  Heh, and even that probably isn't entirely
> > precise, as it's really "in/out fields that KVM consumes while running L2 are
> > buggy".  E.g. in this case, nested_vmcb02_prepare_control() pulls next_rip for
> > vmcb02 from the cache.
> > 
> > 	if (guest_cpu_cap_has(vcpu, X86_FEATURE_NRIPS))
> > 		vmcb02->control.next_rip    = svm->nested.ctl.next_rip;
> > 	else if (boot_cpu_has(X86_FEATURE_NRIPS))
> > 		vmcb02->control.next_rip    = vmcb12_rip;
> 
> Hmm I thin a pure "out" field would be affected if it's consumed by KVM
> later on, after save+restore is possible, right?

True.

> Do you mean that it just happens that the currently affected fields are
> "in/out" fields? Or is there a reason why pure "out" fields cannot be
> affected? AFAICT, these fields are lost after save+restore.

I was essentially assuming KVM wouldn't ever consume pure out fields from the
cache.


> > > > In other words, AFAICT, nested.ctl.int_ctl is special in that KVM needs it to be
> > > > up-to-date at all times, *and* it needs to copied back to vmcb12 (or userspace).
> > > 
> > > Hmm actually looking at nested.ctl.int_ctl, I don't think it's that special.
> > > Most KVM usages are checking "in" bits, i.e. whether some features (e.g.
> > > vGIF) are enabled or not.
> > >
> > > The "out" bits seem to only be consumed by svm_clear_vintr(), and I
> > > think this can be worked around.
> > 
> > OMG that code makes my head hurt.  Isn't that code just this?
> > 
> > 	/*
> > 	 * Drop int_ctl fields related to VINTR injection.  If L2 is active,
> > 	 * restore the virtual IRQ flag and its vector from vmcb12 now that KVM
> > 	 * is done usurping virtual IRQs for its own purposes.
> > 	 */
> > 	svm->vmcb01.ptr->control.int_ctl &= ~V_IRQ_INJECTION_BITS_MASK;
> > 
> > 	if (is_guest_mode(&svm->vcpu)) {
> > 		svm->vmcb->control.int_ctl = (svm->vmcb->control.int_ctl & ~V_IRQ_MASK) |
> > 					     (svm->nested.ctl.int_ctl & V_IRQ_MASK);
> 
> Also V_INTR_PRIO_MASK, I think?

Ugh, yes.  And I think V_IGN_TPR as well?  I can't tell if that's a bug or not.
It looks like a bug.  AFAICT, svm_set_vintr() uses whatever V_IGN_TPR_MASK value
happens to be in vmcb02.  I don't see how that can be desirable.

> But otherwise yeah I think that's what the function is doing
> more-or-less.
> 
> > 		svm->vmcb->control.int_vector = svm->nested.ctl.int_vector;
> > 	} else {
> > 		WARN_ON_ONCE(svm->vmcb != svm->vmcb01.ptr);
> > 	}
> > 
> > 	svm_clr_intercept(svm, INTERCEPT_VINTR);
> > 	vmcb_mark_dirty(svm->vmcb, VMCB_INTR);
> > 
> > > So maybe we don't really need to keep it up-to-date in the cache at all
> > > times.
> > 
> > Yeah, IMO that approach is unnecessarily convoluted.  The actual logic isn't all
> > that complex, all of the complexity comes from juggling state between the cache
> > and vmcb02 just so that the cache can be authoritative.  Given that we failed
> > miserably in actually making the cache authoritative, e.g. see the nested #VMEXIT
> > flow, I think we should kill the entire concept and instead maintain an *exact*
> 
> When you say *exact* snapshot, do you mean move all the sanitizing logic
> recently introduced in __nested_copy_vmcb_control_to_cache() (by Kevin
> and myself) to sanitize vmcb02 instead?

Oh, no, not _that_ exact.  More "unchanged after emulated VMRUN"

> That would be annoying. For example, for the intercepts, sanitizing
> vmcb02 (but not vmcb12) means that we need to also add checks in the
> exit path (i.e. in nested_svm_intercept() or even
> vmcb12_is_intercept()), as vmcb12 could have illegal intercepts.
> 
> > snapshot of vmcb12's controls, and then make vmcb02 authoritative.  We'll still
> > need the logic in nested_sync_control_from_vmcb02() to updated int_ctl on save
> > or #VMEXIT if KVM is still intercepting VINTR for its own purposes, but at least
> > the code will be contained.
> 
> Yeah. We should leave it out of the vmcb12 cache though.

+1

> > Then to make it all but impossible to re-introduce this mess, do something like
> > this so that someone would have to go way out of their way to try and modify the
> > cache.
> > 
> > diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
> > index ebd7b36b1ceb..2de6305be9ce 100644
> > --- a/arch/x86/kvm/svm/svm.h
> > +++ b/arch/x86/kvm/svm/svm.h
> > @@ -199,14 +199,13 @@ struct svm_nested_state {
> >          * we cannot inject a nested vmexit yet.  */
> >         bool nested_run_pending;
> >  
> > -       /* cache for control fields of the guest */
> > -       struct vmcb_ctrl_area_cached ctl;
> > -
> >         /*
> > -        * Note: this struct is not kept up-to-date while L2 runs; it is only
> > -        * valid within nested_svm_vmrun.
> > +        * An opaque, read-only cache of vmcb12 controls, used to query L1's
> > +        * controls while running L2, e.g. to route intercepts appropriately.
> > +        * All reads are routed through accessors to make it all but impossible
> > +        * for KVM to clobber its snapshot of vmcb12.
> >          */
> > -       struct vmcb_save_area_cached save;
> 
> Is dropping the cached save area intentional?

Yes, I think we should try to drop it.  Assuming the comment is correct and it
really only 

> We can drop it and make it a local vaiable in nested_svm_vmrun(), and
> plumb it all the way down. But it could be too big for the stack.

It's 48 bytes, there's no way that's too big.

> Allocating it every time isn't nice either.

> Do you mean to also make it opaque?

I'd prefer to drop it.

> > +       u8 __vmcb12_ctrl[sizeof(struct vmcb_ctrl_area_cached)];
> 
> We have a lot of accesses to svm->nested.ctl, so we'll need a lot of
> clutter to cast the field in all of these places.
> 
> Maybe we add a read-only accessor that returns a pointer to a constant
> struct?

That's what I said :-D

	* All reads are routed through accessors to make it all but impossible
	* for KVM to clobber its snapshot of vmcb12.

There might be a lot of helpers, but I bet it's less than nVMX has for vmcs12.

> >         bool initialized;
> > 
> > > > Part of me wants to remove these two fields entirely:
> > > > 
> > > > 	/* cache for control fields of the guest */
> > > > 	struct vmcb_ctrl_area_cached ctl;
> > > > 
> > > > 	/*
> > > > 	 * Note: this struct is not kept up-to-date while L2 runs; it is only
> > > > 	 * valid within nested_svm_vmrun.
> > > > 	 */
> > > > 	struct vmcb_save_area_cached save;
> > > > 
> > > > and instead use "full" caches only for the duration of nested_svm_vmrun().  Or
> > > > hell, just copy the entire vmcb12 and throw the cached structures in the garbage.
> > > > But that'll probably end in a game of whack-a-mole as things get moved back in.
> > > 
> > > Yeah, KVM needs to keep some of the fields around :/
> > 
> > For me, that's totally fine.  As above, the problem I see is that there is no
> > single source of truth, i.e. that the authoritative state is spread across vmcb02
> > and the cache.
> > 
> > > > So rather than do something totally drastic, I think we should kill
> > > > nested_copy_vmcb_cache_to_control() and replace it with a "save control" flow.
> > > > And then have it share code as much code as possible with nested_svm_vmexit(),
> > > > and fixup nested_svm_vmexit() to not pull from svm->nested.ctl unnecessarily.
> > > > Which, again AFICT, is pretty much limited to int_ctl: either vmcb02 is
> > > > authoritative, or KVM shouldn't be updating vmcb12, and so only the "save control"
> > > > for KVM_GET_NESTED_STATE needs to copy from the cache to the migrated vmcb12.
> > > 
> > > I think this works if we draw a clear extinction between "in","out", and
> > > "in/out" fields, which is not great because some fields (like int_ctl)
> > > have different directions for different bits :/
> > > 
> > > But if we do draw that distinction, and have helpers that copy fields
> > > based on direction, things become more intuitive:
> > > 
> > > During nested VMRUN, we use the "in" and "in/out" fields from cached
> > > vmcb12 to construct vmcb02 through nested_vmcb02_prepare_control().
> > > 
> > > During save, we save "in" fields from the cached vmcb12, "out" and
> > > "in/out" fields from vmcb02.
> > > 
> > > During restore, we use the "in" and "in/out" fields from the restored
> > > payload to construct vmcb02 through nested_vmcb02_prepare_control(), AND
> > > update the "out" fields as well from the payload.
> > 
> > Why the last part?  If L2 is active, then the pure "out" fields are guaranteed
> > to be written on nested #VMEXIT.  Anything else simply can't work.
> 
> I think it just so happens that all pure "out" fields are consumed by
> KVM before save+restore is possible, but it is possible for an "out"
> field to be used by KVM at a later point, or copied from vmcb02 to
> vmcb12 during nested #VMEXIT (e.g. if KVM exits to L1 directly after
> save+restore, before running L2).

Ya.

> next_rip and int_state fall in this bucket, it just happens to also be
> an "in" field.
> 
> For example, if support for decode assists is added, there will be cases
> where KVM just copies insn_bytes from vmcb02 to vmcb12 on nested
> #VMEXIT. If insn_bytes is lost on save+restore, and KVM immediately
> exits to L1 after restore, insn_bytes is lost.
> 
> So we need to also save+restore pure "out" fields, which we do not do
> today.

Hmm, strictly speaking, no.  We'd be fixing a bug that doesn't exist, yet.  But
the word yet...

> > > During synthesized #VMEXIT, we save the "out" and "in/out" fields from
> > > vmcb02 (shared part with save/restore).
> > 
> > Yeah, that all works.  We could also treat save() as an extension of #VMEXIT, but
> > that could make KVM_GET_NESTED_STATE non-idempotent (which might already be the
> > case for VMX?).  I.e. we could sync vmcb02 to vmcb12 (cache), and then copy that
> > to userspace.
> 
> I think this will require adding more fields to the cache, but wait, we
> already have a lot of "out" fields there but I don't think they are
> being used at all..
> 
> Anyway, this may make things simpler. Instead of pulling different
> fields from either cached vmcb12 and vmcb02, we always combine them
> first. I will keep that in mind.
> 
> > 
> > > The save/restore changes would need a flag to avoid restoring garbage
> > > from an older KVM.
> > 
> > I don't follow.  I was thinking we'd only change how KVM maintains authoritative
> > state while runnign L2, i.e. not make any changes (other than fixes) to the
> > serialized state for save/restore.
> 
> I thought we're not currently saving "out" fields at all, but
> apparently we are, we just do not use them in svm_set_nested_state(). So
> we probably do not need a flag. Even if some fields are not currently
> copied, I assume KVM restoring garbage from an older KVM is no worse
> than having uninitialized garbage :)

Heh, yep.

> I think this will be annoying when new fields are added, like
> insn_bytes. Perhaps at some point we move to just serializing the entire
> combined vmcb02/vmcb12 control area and add a flag for that.

If we do it now, can we avoid the flag?

  reply	other threads:[~2026-02-11  0:09 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <20260210005449.3125133-1-yosry.ahmed@linux.dev>
2026-02-10  0:54 ` [PATCH 1/4] KVM: nSVM: Sync next_rip to cached vmcb12 after VMRUN of L2 Yosry Ahmed
2026-02-10  1:49   ` Sean Christopherson
2026-02-10 16:25     ` Yosry Ahmed
2026-02-10 19:20       ` Sean Christopherson
2026-02-10 22:19         ` Yosry Ahmed
2026-02-11  0:09           ` Sean Christopherson [this message]
2026-02-11  0:27             ` Yosry Ahmed
2026-02-11  0:39               ` Sean Christopherson
2026-02-11  1:02                 ` Yosry Ahmed
2026-02-21  0:03                   ` Sean Christopherson
2026-02-21  9:11                     ` Yosry Ahmed
2026-02-23 16:59                       ` Sean Christopherson
2026-02-23 17:21                         ` Yosry Ahmed
2026-02-23 20:23                           ` Sean Christopherson
2026-02-10  0:54 ` [PATCH 2/4] KVM: nSVM: Sync int_state " Yosry Ahmed

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=aYvIpwjsJ50Ns4ho@google.com \
    --to=seanjc@google.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=pbonzini@redhat.com \
    --cc=stable@vger.kernel.org \
    --cc=yosry.ahmed@linux.dev \
    /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