The AMD APM states that if VMMCALL instruction is not intercepted, the
instruction raises a #UD exception.
Create a vmmcall exit handler that generates a #UD if a VMMCALL exit
from L2 is being handled by L0, which means that L1 did not intercept
the VMMCALL instruction.
Co-developed-by: Sean Christopherson <seanjc@google.com>
Co-developed-by: Yosry Ahmed <yosry.ahmed@linux.dev>
Signed-off-by: Kevin Cheng <chengkev@google.com>
---
arch/x86/kvm/svm/svm.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index fc1b8707bb00c..482495ad72d22 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -3179,6 +3179,20 @@ static int bus_lock_exit(struct kvm_vcpu *vcpu)
return 0;
}
+static int vmmcall_interception(struct kvm_vcpu *vcpu)
+{
+ /*
+ * If VMMCALL from L2 is not intercepted by L1, the instruction raises a
+ * #UD exception
+ */
+ if (is_guest_mode(vcpu)) {
+ kvm_queue_exception(vcpu, UD_VECTOR);
+ return 1;
+ }
+
+ return kvm_emulate_hypercall(vcpu);
+}
+
static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
[SVM_EXIT_READ_CR0] = cr_interception,
[SVM_EXIT_READ_CR3] = cr_interception,
@@ -3229,7 +3243,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
[SVM_EXIT_TASK_SWITCH] = task_switch_interception,
[SVM_EXIT_SHUTDOWN] = shutdown_interception,
[SVM_EXIT_VMRUN] = vmrun_interception,
- [SVM_EXIT_VMMCALL] = kvm_emulate_hypercall,
+ [SVM_EXIT_VMMCALL] = vmmcall_interception,
[SVM_EXIT_VMLOAD] = vmload_interception,
[SVM_EXIT_VMSAVE] = vmsave_interception,
[SVM_EXIT_STGI] = stgi_interception,
--
2.52.0.351.gbe84eed79e-goog
On Tue, Jan 06, 2026, Kevin Cheng wrote:
> The AMD APM states that if VMMCALL instruction is not intercepted, the
> instruction raises a #UD exception.
>
> Create a vmmcall exit handler that generates a #UD if a VMMCALL exit
> from L2 is being handled by L0, which means that L1 did not intercept
> the VMMCALL instruction.
>
> Co-developed-by: Sean Christopherson <seanjc@google.com>
> Co-developed-by: Yosry Ahmed <yosry.ahmed@linux.dev>
Co-developed-by requires a SoB. As Yosry noted off-list, he only provided the
comment, and I have feedback on that :-) Unless Yosry objects, just drop his.
Co-developed-by.
Ditt for me, just give me
Suggested-by: Sean Christopherson <seanjc@google.com>
I don't need a Co-developed-by for a tossing a code snippet your way. though I
appreciate the offer. :-)
> Signed-off-by: Kevin Cheng <chengkev@google.com>
> ---
> arch/x86/kvm/svm/svm.c | 16 +++++++++++++++-
> 1 file changed, 15 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
> index fc1b8707bb00c..482495ad72d22 100644
> --- a/arch/x86/kvm/svm/svm.c
> +++ b/arch/x86/kvm/svm/svm.c
> @@ -3179,6 +3179,20 @@ static int bus_lock_exit(struct kvm_vcpu *vcpu)
> return 0;
> }
>
> +static int vmmcall_interception(struct kvm_vcpu *vcpu)
> +{
> + /*
> + * If VMMCALL from L2 is not intercepted by L1, the instruction raises a
> + * #UD exception
> + */
Mentioning L2 and L1 is confusing. It reads like arbitrary KVM behavior. And
IMO the most notable thing is what's missing: an intercept check. _That_ is
worth commenting, e.g.
/*
* VMMCALL #UDs if it's not intercepted, and KVM reaches this point if
* and only if the VMCALL intercept is not set in vmcb12.
*/
> + if (is_guest_mode(vcpu)) {
> + kvm_queue_exception(vcpu, UD_VECTOR);
> + return 1;
> + }
> +
> + return kvm_emulate_hypercall(vcpu);
> +}
> +
> static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
> [SVM_EXIT_READ_CR0] = cr_interception,
> [SVM_EXIT_READ_CR3] = cr_interception,
> @@ -3229,7 +3243,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
> [SVM_EXIT_TASK_SWITCH] = task_switch_interception,
> [SVM_EXIT_SHUTDOWN] = shutdown_interception,
> [SVM_EXIT_VMRUN] = vmrun_interception,
> - [SVM_EXIT_VMMCALL] = kvm_emulate_hypercall,
> + [SVM_EXIT_VMMCALL] = vmmcall_interception,
> [SVM_EXIT_VMLOAD] = vmload_interception,
> [SVM_EXIT_VMSAVE] = vmsave_interception,
> [SVM_EXIT_STGI] = stgi_interception,
> --
> 2.52.0.351.gbe84eed79e-goog
>
On Tue, Jan 06, 2026 at 10:29:59AM -0800, Sean Christopherson wrote:
> On Tue, Jan 06, 2026, Kevin Cheng wrote:
> > The AMD APM states that if VMMCALL instruction is not intercepted, the
> > instruction raises a #UD exception.
> >
> > Create a vmmcall exit handler that generates a #UD if a VMMCALL exit
> > from L2 is being handled by L0, which means that L1 did not intercept
> > the VMMCALL instruction.
> >
> > Co-developed-by: Sean Christopherson <seanjc@google.com>
> > Co-developed-by: Yosry Ahmed <yosry.ahmed@linux.dev>
>
> Co-developed-by requires a SoB. As Yosry noted off-list, he only provided the
> comment, and I have feedback on that :-) Unless Yosry objects, just drop his.
> Co-developed-by.
Yup, no objections.
>
> Ditt for me, just give me
>
> Suggested-by: Sean Christopherson <seanjc@google.com>
>
> I don't need a Co-developed-by for a tossing a code snippet your way. though I
> appreciate the offer. :-)
>
> > Signed-off-by: Kevin Cheng <chengkev@google.com>
> > ---
> > arch/x86/kvm/svm/svm.c | 16 +++++++++++++++-
> > 1 file changed, 15 insertions(+), 1 deletion(-)
> >
> > diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
> > index fc1b8707bb00c..482495ad72d22 100644
> > --- a/arch/x86/kvm/svm/svm.c
> > +++ b/arch/x86/kvm/svm/svm.c
> > @@ -3179,6 +3179,20 @@ static int bus_lock_exit(struct kvm_vcpu *vcpu)
> > return 0;
> > }
> >
> > +static int vmmcall_interception(struct kvm_vcpu *vcpu)
> > +{
> > + /*
> > + * If VMMCALL from L2 is not intercepted by L1, the instruction raises a
> > + * #UD exception
> > + */
>
> Mentioning L2 and L1 is confusing. It reads like arbitrary KVM behavior. And
> IMO the most notable thing is what's missing: an intercept check. _That_ is
> worth commenting, e.g.
>
> /*
> * VMMCALL #UDs if it's not intercepted, and KVM reaches this point if
> * and only if the VMCALL intercept is not set in vmcb12.
Nit: VMMCALL
> */
>
Would it be too paranoid to WARN if the L1 intercept is set here?
WARN_ON_ONCE(vmcb12_is_intercept(&svm->nested.ctl, INTERCEPT_VMMCALL));
> > + if (is_guest_mode(vcpu)) {
> > + kvm_queue_exception(vcpu, UD_VECTOR);
> > + return 1;
> > + }
> > +
> > + return kvm_emulate_hypercall(vcpu);
> > +}
> > +
> > static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
> > [SVM_EXIT_READ_CR0] = cr_interception,
> > [SVM_EXIT_READ_CR3] = cr_interception,
> > @@ -3229,7 +3243,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = {
> > [SVM_EXIT_TASK_SWITCH] = task_switch_interception,
> > [SVM_EXIT_SHUTDOWN] = shutdown_interception,
> > [SVM_EXIT_VMRUN] = vmrun_interception,
> > - [SVM_EXIT_VMMCALL] = kvm_emulate_hypercall,
> > + [SVM_EXIT_VMMCALL] = vmmcall_interception,
> > [SVM_EXIT_VMLOAD] = vmload_interception,
> > [SVM_EXIT_VMSAVE] = vmsave_interception,
> > [SVM_EXIT_STGI] = stgi_interception,
> > --
> > 2.52.0.351.gbe84eed79e-goog
> >
On Tue, Jan 06, 2026, Yosry Ahmed wrote:
> On Tue, Jan 06, 2026 at 10:29:59AM -0800, Sean Christopherson wrote:
> > > +static int vmmcall_interception(struct kvm_vcpu *vcpu)
> > > +{
> > > + /*
> > > + * If VMMCALL from L2 is not intercepted by L1, the instruction raises a
> > > + * #UD exception
> > > + */
> >
> > Mentioning L2 and L1 is confusing. It reads like arbitrary KVM behavior. And
> > IMO the most notable thing is what's missing: an intercept check. _That_ is
> > worth commenting, e.g.
> >
> > /*
> > * VMMCALL #UDs if it's not intercepted, and KVM reaches this point if
> > * and only if the VMCALL intercept is not set in vmcb12.
>
> Nit: VMMCALL
>
> > */
> >
>
> Would it be too paranoid to WARN if the L1 intercept is set here?
Yes. At some point we have to rely on not being completely inept :-D, and more
importantly this is something that should be trivial easy to validate via tests.
My hesitation for such a check is that adding a WARN here begs the question of
what makes _this_ particular handler special, i.e. why doesn't every other handler
also check that an exit shouldn't have been routed to L1? At that point we'd be
replicating much of the routing logic into every exit handler.
And it _still_ wouldn't guarantee correctness, e.g. wouldn't detect the case where
KVM incorrectly forwarded a VMMCALL to L1, i.e. we still need the aforementioned
tests, and so I see the WARN as an overall net-negative.
> WARN_ON_ONCE(vmcb12_is_intercept(&svm->nested.ctl, INTERCEPT_VMMCALL));
On Tue, Jan 06, 2026 at 03:38:57PM -0800, Sean Christopherson wrote:
> On Tue, Jan 06, 2026, Yosry Ahmed wrote:
> > On Tue, Jan 06, 2026 at 10:29:59AM -0800, Sean Christopherson wrote:
> > > > +static int vmmcall_interception(struct kvm_vcpu *vcpu)
> > > > +{
> > > > + /*
> > > > + * If VMMCALL from L2 is not intercepted by L1, the instruction raises a
> > > > + * #UD exception
> > > > + */
> > >
> > > Mentioning L2 and L1 is confusing. It reads like arbitrary KVM behavior. And
> > > IMO the most notable thing is what's missing: an intercept check. _That_ is
> > > worth commenting, e.g.
> > >
> > > /*
> > > * VMMCALL #UDs if it's not intercepted, and KVM reaches this point if
> > > * and only if the VMCALL intercept is not set in vmcb12.
> >
> > Nit: VMMCALL
> >
> > > */
> > >
> >
> > Would it be too paranoid to WARN if the L1 intercept is set here?
>
> Yes. At some point we have to rely on not being completely inept :-D, and more
> importantly this is something that should be trivial easy to validate via tests.
>
> My hesitation for such a check is that adding a WARN here begs the question of
> what makes _this_ particular handler special, i.e. why doesn't every other handler
> also check that an exit shouldn't have been routed to L1? At that point we'd be
> replicating much of the routing logic into every exit handler.
I briefly thought about this, and I thought since we're explicitly
calling out the dependency on L1 not intercepting this here, a WARN
would make sense. Anyway, your argument make sense. I was going to
suggested adding a WARN in svm_invoke_exit_handler() instead, something
like:
WARN_ON_ONCE(vmcb12_is_intercept(&svm->nested.ctl, exit_code));
But this doesn't work for all cases (e.g. SVM_EXIT_MSR, SVM_EXIT_IOIO,
etc). We can exclude these cases, but this point maintaining the WARN
becomes a burden in itself. I give up :)
>
> And it _still_ wouldn't guarantee correctness, e.g. wouldn't detect the case where
> KVM incorrectly forwarded a VMMCALL to L1, i.e. we still need the aforementioned
> tests, and so I see the WARN as an overall net-negative.
>
> > WARN_ON_ONCE(vmcb12_is_intercept(&svm->nested.ctl, INTERCEPT_VMMCALL));
> Mentioning L2 and L1 is confusing. It reads like arbitrary KVM behavior. And > IMO the most notable thing is what's missing: an intercept check. _That_ is > worth commenting, e.g. > > /* > * VMMCALL #UDs if it's not intercepted, and KVM reaches this point if > * and only if the VMCALL intercept is not set in vmcb12. > */ Not intercepting VMMCALL is stated to be an unconditional VMRUN failure. APM Vol3 15.5 Canonicalization and Consistency Checks. The "VMMCALL was not intercepted" condition is probably what the pipeline really checks, but really it means "in root mode". In most nested virt scenarios, L1 knows it's in a VM and can use VMMCALL for host facilities. ~Andrew
On Tue, Jan 06, 2026, Andrew Cooper wrote: > > Mentioning L2 and L1 is confusing. It reads like arbitrary KVM behavior. And > > IMO the most notable thing is what's missing: an intercept check. _That_ is > > worth commenting, e.g. > > > > /* > > * VMMCALL #UDs if it's not intercepted, and KVM reaches this point if > > * and only if the VMCALL intercept is not set in vmcb12. > > */ > > Not intercepting VMMCALL is stated to be an unconditional VMRUN > failure. APM Vol3 15.5 Canonicalization and Consistency Checks. Hrm, I can't find that. I see: The VMRUN intercept bit is clear. but I don't see anything about VMMCALL being a mandatory intercept. > > The "VMMCALL was not intercepted" condition is probably what the > pipeline really checks, but really it means "in root mode". > > In most nested virt scenarios, L1 knows it's in a VM and can use VMMCALL > for host facilities. > > ~Andrew
On 06/01/2026 6:57 pm, Sean Christopherson wrote: > On Tue, Jan 06, 2026, Andrew Cooper wrote: >>> Mentioning L2 and L1 is confusing. It reads like arbitrary KVM behavior. And >>> IMO the most notable thing is what's missing: an intercept check. _That_ is >>> worth commenting, e.g. >>> >>> /* >>> * VMMCALL #UDs if it's not intercepted, and KVM reaches this point if >>> * and only if the VMCALL intercept is not set in vmcb12. >>> */ >> Not intercepting VMMCALL is stated to be an unconditional VMRUN >> failure. APM Vol3 15.5 Canonicalization and Consistency Checks. > Hrm, I can't find that. I see: > > The VMRUN intercept bit is clear. > > but I don't see anything about VMMCALL being a mandatory intercept. Gah. I even double checked before sending, but I'm apparently completely blind to the difference between VMRUN and VMMCALL. Sorry for the noise. ~Andrew
© 2016 - 2026 Red Hat, Inc.