* [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
@ 2025-10-14 23:10 Sean Christopherson
2025-10-15 0:22 ` dan.j.williams
` (3 more replies)
0 siblings, 4 replies; 12+ messages in thread
From: Sean Christopherson @ 2025-10-14 23:10 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: kvm, linux-kernel, Kai Huang, Xiaoyao Li, Rick Edgecombe
Add VMX exit handlers for SEAMCALL and TDCALL, and a SEAMCALL handler for
TDX, to inject a #UD if a non-TD guest attempts to execute SEAMCALL or
TDCALL, or if a TD guest attempst to execute SEAMCALL. Neither SEAMCALL
nor TDCALL is gated by any software enablement other than VMXON, and so
will generate a VM-Exit instead of e.g. a native #UD when executed from
the guest kernel.
Note! No unprivilege DoS of the L1 kernel is possible as TDCALL and
SEAMCALL #GP at CPL > 0, and the CPL check is performed prior to the VMX
non-root (VM-Exit) check, i.e. userspace can't crash the VM. And for a
nested guest, KVM forwards unknown exits to L1, i.e. an L2 kernel can
crash itself, but not L1.
Note #2! The Intel® Trust Domain CPU Architectural Extensions spec's
pseudocode shows the CPL > 0 check for SEAMCALL coming _after_ the VM-Exit,
but that appears to be a documentation bug (likely because the CPL > 0
check was incorrectly bundled with other lower-priority #GP checks).
Testing on SPR and EMR shows that the CPL > 0 check is performed before
the VMX non-root check, i.e. SEAMCALL #GPs when executed in usermode.
Note #3! The aforementioned Trust Domain spec uses confusing pseudocde
that says that SEAMCALL will #UD if executed "inSEAM", but "inSEAM"
specifically means in SEAM Root Mode, i.e. in the TDX-Module. The long-
form description explicitly states that SEAMCALL generates an exit when
executed in "SEAM VMX non-root operation".
Cc: stable@vger.kernel.org
Cc: Kai Huang <kai.huang@intel.com>
Cc: Xiaoyao Li <xiaoyao.li@intel.com>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/include/uapi/asm/vmx.h | 1 +
arch/x86/kvm/vmx/nested.c | 8 ++++++++
arch/x86/kvm/vmx/tdx.c | 3 +++
arch/x86/kvm/vmx/vmx.c | 8 ++++++++
4 files changed, 20 insertions(+)
diff --git a/arch/x86/include/uapi/asm/vmx.h b/arch/x86/include/uapi/asm/vmx.h
index 9792e329343e..1baa86dfe029 100644
--- a/arch/x86/include/uapi/asm/vmx.h
+++ b/arch/x86/include/uapi/asm/vmx.h
@@ -93,6 +93,7 @@
#define EXIT_REASON_TPAUSE 68
#define EXIT_REASON_BUS_LOCK 74
#define EXIT_REASON_NOTIFY 75
+#define EXIT_REASON_SEAMCALL 76
#define EXIT_REASON_TDCALL 77
#define EXIT_REASON_MSR_READ_IMM 84
#define EXIT_REASON_MSR_WRITE_IMM 85
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 76271962cb70..f64a1eb241b6 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -6728,6 +6728,14 @@ static bool nested_vmx_l1_wants_exit(struct kvm_vcpu *vcpu,
case EXIT_REASON_NOTIFY:
/* Notify VM exit is not exposed to L1 */
return false;
+ case EXIT_REASON_SEAMCALL:
+ case EXIT_REASON_TDCALL:
+ /*
+ * SEAMCALL and TDCALL unconditionally VM-Exit, but aren't
+ * virtualized by KVM for L1 hypervisors, i.e. L1 should
+ * never want or expect such an exit.
+ */
+ return true;
default:
return true;
}
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index 097304bf1e1d..7326c68f9909 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -2127,6 +2127,9 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
return tdx_emulate_mmio(vcpu);
case EXIT_REASON_EPT_VIOLATION:
return tdx_handle_ept_violation(vcpu);
+ case EXIT_REASON_SEAMCALL:
+ kvm_queue_exception(vcpu, UD_VECTOR);
+ return 1;
case EXIT_REASON_OTHER_SMI:
/*
* Unlike VMX, SMI in SEAM non-root mode (i.e. when
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 546272a5d34d..d1b34b7ca4a3 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -6033,6 +6033,12 @@ static int handle_vmx_instruction(struct kvm_vcpu *vcpu)
return 1;
}
+static int handle_tdx_instruction(struct kvm_vcpu *vcpu)
+{
+ kvm_queue_exception(vcpu, UD_VECTOR);
+ return 1;
+}
+
#ifndef CONFIG_X86_SGX_KVM
static int handle_encls(struct kvm_vcpu *vcpu)
{
@@ -6158,6 +6164,8 @@ static int (*kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
[EXIT_REASON_ENCLS] = handle_encls,
[EXIT_REASON_BUS_LOCK] = handle_bus_lock_vmexit,
[EXIT_REASON_NOTIFY] = handle_notify,
+ [EXIT_REASON_SEAMCALL] = handle_tdx_instruction,
+ [EXIT_REASON_TDCALL] = handle_tdx_instruction,
[EXIT_REASON_MSR_READ_IMM] = handle_rdmsr_imm,
[EXIT_REASON_MSR_WRITE_IMM] = handle_wrmsr_imm,
};
base-commit: 6b36119b94d0b2bb8cea9d512017efafd461d6ac
--
2.51.0.788.g6d19910ace-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-14 23:10 [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL Sean Christopherson
@ 2025-10-15 0:22 ` dan.j.williams
2025-10-15 13:56 ` Sean Christopherson
2025-10-15 1:13 ` Chao Gao
` (2 subsequent siblings)
3 siblings, 1 reply; 12+ messages in thread
From: dan.j.williams @ 2025-10-15 0:22 UTC (permalink / raw)
To: Sean Christopherson, Sean Christopherson, Paolo Bonzini
Cc: kvm, linux-kernel, Kai Huang, Xiaoyao Li, Rick Edgecombe
Sean Christopherson wrote:
> Add VMX exit handlers for SEAMCALL and TDCALL, and a SEAMCALL handler for
> TDX, to inject a #UD if a non-TD guest attempts to execute SEAMCALL or
> TDCALL, or if a TD guest attempst to execute SEAMCALL. Neither SEAMCALL
> nor TDCALL is gated by any software enablement other than VMXON, and so
> will generate a VM-Exit instead of e.g. a native #UD when executed from
> the guest kernel.
>
> Note! No unprivilege DoS of the L1 kernel is possible as TDCALL and
> SEAMCALL #GP at CPL > 0, and the CPL check is performed prior to the VMX
> non-root (VM-Exit) check, i.e. userspace can't crash the VM. And for a
> nested guest, KVM forwards unknown exits to L1, i.e. an L2 kernel can
> crash itself, but not L1.
>
> Note #2! The Intel® Trust Domain CPU Architectural Extensions spec's
> pseudocode shows the CPL > 0 check for SEAMCALL coming _after_ the VM-Exit,
> but that appears to be a documentation bug (likely because the CPL > 0
> check was incorrectly bundled with other lower-priority #GP checks).
> Testing on SPR and EMR shows that the CPL > 0 check is performed before
> the VMX non-root check, i.e. SEAMCALL #GPs when executed in usermode.
Filed an errata for this.
> Note #3! The aforementioned Trust Domain spec uses confusing pseudocde
> that says that SEAMCALL will #UD if executed "inSEAM", but "inSEAM"
> specifically means in SEAM Root Mode, i.e. in the TDX-Module. The long-
> form description explicitly states that SEAMCALL generates an exit when
> executed in "SEAM VMX non-root operation".
This one I am not following. Is this mixing the #UD and exit cases? The
long form says inSEAM generates #UD and that is consistent with the
"64-Bit Mode Exceptions" table.
For exit it says: "When invoked in SEAM VMX non-root operation or legacy
VMX non-root operation, this instruction can cause a VM exit".
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-14 23:10 [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL Sean Christopherson
2025-10-15 0:22 ` dan.j.williams
@ 2025-10-15 1:13 ` Chao Gao
2025-10-15 3:11 ` Xiaoyao Li
2025-10-15 10:36 ` Xiaoyao Li
2025-10-15 13:38 ` Binbin Wu
3 siblings, 1 reply; 12+ messages in thread
From: Chao Gao @ 2025-10-15 1:13 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Kai Huang, Xiaoyao Li,
Rick Edgecombe
>--- a/arch/x86/kvm/vmx/nested.c
>+++ b/arch/x86/kvm/vmx/nested.c
>@@ -6728,6 +6728,14 @@ static bool nested_vmx_l1_wants_exit(struct kvm_vcpu *vcpu,
> case EXIT_REASON_NOTIFY:
> /* Notify VM exit is not exposed to L1 */
> return false;
>+ case EXIT_REASON_SEAMCALL:
>+ case EXIT_REASON_TDCALL:
>+ /*
>+ * SEAMCALL and TDCALL unconditionally VM-Exit, but aren't
>+ * virtualized by KVM for L1 hypervisors, i.e. L1 should
>+ * never want or expect such an exit.
>+ */
>+ return true;
> default:
> return true;
> }
>diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
>index 097304bf1e1d..7326c68f9909 100644
>--- a/arch/x86/kvm/vmx/tdx.c
>+++ b/arch/x86/kvm/vmx/tdx.c
>@@ -2127,6 +2127,9 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
> return tdx_emulate_mmio(vcpu);
> case EXIT_REASON_EPT_VIOLATION:
> return tdx_handle_ept_violation(vcpu);
>+ case EXIT_REASON_SEAMCALL:
>+ kvm_queue_exception(vcpu, UD_VECTOR);
Emm, the host cannot inject exceptions to TDX guests. Right?
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-15 1:13 ` Chao Gao
@ 2025-10-15 3:11 ` Xiaoyao Li
2025-10-15 13:20 ` Sean Christopherson
0 siblings, 1 reply; 12+ messages in thread
From: Xiaoyao Li @ 2025-10-15 3:11 UTC (permalink / raw)
To: Chao Gao, Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Kai Huang, Rick Edgecombe
On 10/15/2025 9:13 AM, Chao Gao wrote:
>> --- a/arch/x86/kvm/vmx/nested.c
>> +++ b/arch/x86/kvm/vmx/nested.c
>> @@ -6728,6 +6728,14 @@ static bool nested_vmx_l1_wants_exit(struct kvm_vcpu *vcpu,
>> case EXIT_REASON_NOTIFY:
>> /* Notify VM exit is not exposed to L1 */
>> return false;
>> + case EXIT_REASON_SEAMCALL:
>> + case EXIT_REASON_TDCALL:
>> + /*
>> + * SEAMCALL and TDCALL unconditionally VM-Exit, but aren't
>> + * virtualized by KVM for L1 hypervisors, i.e. L1 should
>> + * never want or expect such an exit.
>> + */
>> + return true;
>> default:
>> return true;
>> }
>> diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
>> index 097304bf1e1d..7326c68f9909 100644
>> --- a/arch/x86/kvm/vmx/tdx.c
>> +++ b/arch/x86/kvm/vmx/tdx.c
>> @@ -2127,6 +2127,9 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
>> return tdx_emulate_mmio(vcpu);
>> case EXIT_REASON_EPT_VIOLATION:
>> return tdx_handle_ept_violation(vcpu);
>> + case EXIT_REASON_SEAMCALL:
>> + kvm_queue_exception(vcpu, UD_VECTOR);
>
> Emm, the host cannot inject exceptions to TDX guests. Right?
Right.
I also tested in TD guest. Actually, the TDX module injects #UD to TD
guest on SEAMCALL exit.
The behavior is documented in 11.7.1 "Unconditionally Blocked
Instructions" of TDX module base spec:
Instructions that causes a #UD Unconditionally
- SEAMCALL, SEAMRET
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-14 23:10 [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL Sean Christopherson
2025-10-15 0:22 ` dan.j.williams
2025-10-15 1:13 ` Chao Gao
@ 2025-10-15 10:36 ` Xiaoyao Li
2025-10-15 13:57 ` Sean Christopherson
2025-10-15 13:38 ` Binbin Wu
3 siblings, 1 reply; 12+ messages in thread
From: Xiaoyao Li @ 2025-10-15 10:36 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: kvm, linux-kernel, Kai Huang, Rick Edgecombe
On 10/15/2025 7:10 AM, Sean Christopherson wrote:
> Add VMX exit handlers for SEAMCALL and TDCALL, and a SEAMCALL handler for
> TDX, to inject a #UD if a non-TD guest attempts to execute SEAMCALL or
> TDCALL, or if a TD guest attempst to execute SEAMCALL.
> Neither SEAMCALL
> nor TDCALL is gated by any software enablement other than VMXON, and so
> will generate a VM-Exit instead of e.g. a native #UD when executed from
> the guest kernel.
It's true only on the hardware with SEAM support.
On older hardware without SEAM support, SEAMCALL/TDCALL gets native #UD.
> Note! No unprivilege DoS of the L1 kernel is possible as TDCALL and
> SEAMCALL #GP at CPL > 0, and the CPL check is performed prior to the VMX
> non-root (VM-Exit) check, i.e. userspace can't crash the VM. And for a
> nested guest, KVM forwards unknown exits to L1, i.e. an L2 kernel can
> crash itself, but not L1.
>
> Note #2! The Intel® Trust Domain CPU Architectural Extensions spec's
> pseudocode shows the CPL > 0 check for SEAMCALL coming _after_ the VM-Exit,
> but that appears to be a documentation bug (likely because the CPL > 0
> check was incorrectly bundled with other lower-priority #GP checks).
> Testing on SPR and EMR shows that the CPL > 0 check is performed before
> the VMX non-root check, i.e. SEAMCALL #GPs when executed in usermode.
>
> Note #3! The aforementioned Trust Domain spec uses confusing pseudocde
> that says that SEAMCALL will #UD if executed "inSEAM", but "inSEAM"
> specifically means in SEAM Root Mode, i.e. in the TDX-Module. The long-
> form description explicitly states that SEAMCALL generates an exit when
> executed in "SEAM VMX non-root operation".
>
> Cc: stable@vger.kernel.org
> Cc: Kai Huang <kai.huang@intel.com>
> Cc: Xiaoyao Li <xiaoyao.li@intel.com>
> Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
> Signed-off-by: Sean Christopherson <seanjc@google.com>
> ---
> arch/x86/include/uapi/asm/vmx.h | 1 +
> arch/x86/kvm/vmx/nested.c | 8 ++++++++
> arch/x86/kvm/vmx/tdx.c | 3 +++
> arch/x86/kvm/vmx/vmx.c | 8 ++++++++
> 4 files changed, 20 insertions(+)
>
> diff --git a/arch/x86/include/uapi/asm/vmx.h b/arch/x86/include/uapi/asm/vmx.h
> index 9792e329343e..1baa86dfe029 100644
> --- a/arch/x86/include/uapi/asm/vmx.h
> +++ b/arch/x86/include/uapi/asm/vmx.h
> @@ -93,6 +93,7 @@
> #define EXIT_REASON_TPAUSE 68
> #define EXIT_REASON_BUS_LOCK 74
> #define EXIT_REASON_NOTIFY 75
> +#define EXIT_REASON_SEAMCALL 76
> #define EXIT_REASON_TDCALL 77
> #define EXIT_REASON_MSR_READ_IMM 84
> #define EXIT_REASON_MSR_WRITE_IMM 85
> diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
> index 76271962cb70..f64a1eb241b6 100644
> --- a/arch/x86/kvm/vmx/nested.c
> +++ b/arch/x86/kvm/vmx/nested.c
> @@ -6728,6 +6728,14 @@ static bool nested_vmx_l1_wants_exit(struct kvm_vcpu *vcpu,
> case EXIT_REASON_NOTIFY:
> /* Notify VM exit is not exposed to L1 */
> return false;
> + case EXIT_REASON_SEAMCALL:
> + case EXIT_REASON_TDCALL:
> + /*
> + * SEAMCALL and TDCALL unconditionally VM-Exit, but aren't
> + * virtualized by KVM for L1 hypervisors, i.e. L1 should
> + * never want or expect such an exit.
> + */
The i.e. part is confusing? It is exactly forwarding the EXITs to L1,
while it says L1 should never want or expect such an exit.
> + return true;
> default:
> return true;
> }
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-15 3:11 ` Xiaoyao Li
@ 2025-10-15 13:20 ` Sean Christopherson
0 siblings, 0 replies; 12+ messages in thread
From: Sean Christopherson @ 2025-10-15 13:20 UTC (permalink / raw)
To: Xiaoyao Li
Cc: Chao Gao, Paolo Bonzini, kvm, linux-kernel, Kai Huang,
Rick Edgecombe
On Wed, Oct 15, 2025, Xiaoyao Li wrote:
> On 10/15/2025 9:13 AM, Chao Gao wrote:
> > > --- a/arch/x86/kvm/vmx/nested.c
> > > +++ b/arch/x86/kvm/vmx/nested.c
> > > @@ -6728,6 +6728,14 @@ static bool nested_vmx_l1_wants_exit(struct kvm_vcpu *vcpu,
> > > case EXIT_REASON_NOTIFY:
> > > /* Notify VM exit is not exposed to L1 */
> > > return false;
> > > + case EXIT_REASON_SEAMCALL:
> > > + case EXIT_REASON_TDCALL:
> > > + /*
> > > + * SEAMCALL and TDCALL unconditionally VM-Exit, but aren't
> > > + * virtualized by KVM for L1 hypervisors, i.e. L1 should
> > > + * never want or expect such an exit.
> > > + */
> > > + return true;
> > > default:
> > > return true;
> > > }
> > > diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
> > > index 097304bf1e1d..7326c68f9909 100644
> > > --- a/arch/x86/kvm/vmx/tdx.c
> > > +++ b/arch/x86/kvm/vmx/tdx.c
> > > @@ -2127,6 +2127,9 @@ int tdx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t fastpath)
> > > return tdx_emulate_mmio(vcpu);
> > > case EXIT_REASON_EPT_VIOLATION:
> > > return tdx_handle_ept_violation(vcpu);
> > > + case EXIT_REASON_SEAMCALL:
> > > + kvm_queue_exception(vcpu, UD_VECTOR);
> >
> > Emm, the host cannot inject exceptions to TDX guests. Right?
>
> Right.
Oh, yeah, duh.
> I also tested in TD guest. Actually, the TDX module injects #UD to TD guest
> on SEAMCALL exit.
>
> The behavior is documented in 11.7.1 "Unconditionally Blocked Instructions"
> of TDX module base spec:
>
> Instructions that causes a #UD Unconditionally
>
> - SEAMCALL, SEAMRET
Nice! I'll add a WARN_ON_ONCE() if KVM sees a SEAMCALL TD-Exit. The SEAMRET
documentation in the TDX-Module spec is amusing since the CPU is supposed to
generate a #UD for SEAMRET, i.e. it's not like the TDX-Module has any choice in
the matter :-)
Thanks!
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-14 23:10 [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL Sean Christopherson
` (2 preceding siblings ...)
2025-10-15 10:36 ` Xiaoyao Li
@ 2025-10-15 13:38 ` Binbin Wu
3 siblings, 0 replies; 12+ messages in thread
From: Binbin Wu @ 2025-10-15 13:38 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Kai Huang, Xiaoyao Li,
Rick Edgecombe
On 10/15/2025 7:10 AM, Sean Christopherson wrote:
> Add VMX exit handlers for SEAMCALL and TDCALL, and a SEAMCALL handler for
> TDX, to inject a #UD if a non-TD guest attempts to execute SEAMCALL or
> TDCALL, or if a TD guest attempst to execute SEAMCALL. Neither SEAMCALL
attempst -> attempts
But I guess this will be re-phrased as a native #UD is expected when a TD guest
attempts to execute SEAMCALL.
> nor TDCALL is gated by any software enablement other than VMXON, and so
> will generate a VM-Exit instead of e.g. a native #UD when executed from
> the guest kernel.
>
> Note! No unprivilege DoS of the L1 kernel is possible as TDCALL and
unprivilege -> unprivileged
> SEAMCALL #GP at CPL > 0, and the CPL check is performed prior to the VMX
> non-root (VM-Exit) check, i.e. userspace can't crash the VM. And for a
> nested guest, KVM forwards unknown exits to L1, i.e. an L2 kernel can
> crash itself, but not L1.
>
> Note #2! The Intel® Trust Domain CPU Architectural Extensions spec's
> pseudocode shows the CPL > 0 check for SEAMCALL coming _after_ the VM-Exit,
> but that appears to be a documentation bug (likely because the CPL > 0
> check was incorrectly bundled with other lower-priority #GP checks).
> Testing on SPR and EMR shows that the CPL > 0 check is performed before
> the VMX non-root check, i.e. SEAMCALL #GPs when executed in usermode.
>
> Note #3! The aforementioned Trust Domain spec uses confusing pseudocde
pseudocde -> pseudocode
But I guess this note will be dropped as explained by Dan?
> that says that SEAMCALL will #UD if executed "inSEAM", but "inSEAM"
> specifically means in SEAM Root Mode, i.e. in the TDX-Module. The long-
> form description explicitly states that SEAMCALL generates an exit when
> executed in "SEAM VMX non-root operation".
>
...
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-15 0:22 ` dan.j.williams
@ 2025-10-15 13:56 ` Sean Christopherson
2025-10-15 15:49 ` dan.j.williams
0 siblings, 1 reply; 12+ messages in thread
From: Sean Christopherson @ 2025-10-15 13:56 UTC (permalink / raw)
To: dan.j.williams
Cc: Paolo Bonzini, kvm, linux-kernel, Kai Huang, Xiaoyao Li,
Rick Edgecombe
On Tue, Oct 14, 2025, dan.j.williams@intel.com wrote:
> Sean Christopherson wrote:
> > Note #3! The aforementioned Trust Domain spec uses confusing pseudocde
> > that says that SEAMCALL will #UD if executed "inSEAM", but "inSEAM"
> > specifically means in SEAM Root Mode, i.e. in the TDX-Module. The long-
> > form description explicitly states that SEAMCALL generates an exit when
> > executed in "SEAM VMX non-root operation".
>
> This one I am not following. Is this mixing the #UD and exit cases? The
> long form says inSEAM generates #UD and that is consistent with the
> "64-Bit Mode Exceptions" table.
I'm calling out that "inSEAM" is confusing. It really should be "in SEAM root
operation" to eliminate ambiguity and to be consistent with how the SDM documents
VMX. For VMX, the SDM describes three states:
1. VMX operation
2. VMX root operation
3. VMX non-root operation
Where #1 is a superset that covers both #2 and #3. The SEAMCALL pseudocode is a
perfect example as it references all three in quick succession:
IF not in VMX operation or inSMM or inSEAM or ((IA32_EFER.LMA & CS.L) == 0)
^^^^^^^^^^^^^
THEN #UD;
ELSIF in VMX non-root operation
^^^^^^^^^^^^^^^^^^^^^^
THEN VMexit(“basic reason” = SEAMCALL,
“VM exit from VMX root operation” (bit 29) = 0);
^^^^^^^^^^^^^^^^^^
ELSIF CPL > 0 or IA32_SEAMRR_MASK.VALID == 0 or “events blocking by MOV-SS”
THEN #GP(0);
The same should hold true for SEAM. E.g. earlier on, the spec explicitly states:
The TD VMs execute in SEAM VMX non-root operation.
as well as:
The physical address bits reserved for encoding TDX private KeyID are meant to
be treated as reserved bits when not in SEAM operation.
Since the TD guest obviously needs to consume its private KeyID, that means that
SEAM also has three states:
1. SEAM operation
2. SEAM VMX root operation
3. SEAM VMX non-root operation
Where #1 is again a superset that covers both #2 and #3.
IMO, any reasonable reading of "inSEAM" is that it is talking about #1, in which
case the pseudocode effectively says that SEAMCALL should #UD if executed in
"SEAM VMX non-root operation", but that's obviously not the case based on the
statement below as well as the TDX-Module code.
Furthermore, the only transitions for"inSEAM" are that it's set to '1' by SEAMCALL,
and cleared to '0' by SEAMRET. That implies that it's _not_ cleared by VM-Enter
from SEAM VMX root operation to SEAM VMX non-root operation, which reinforces my
reading of "inSEAM == SEAM operation".
> For exit it says: "When invoked in SEAM VMX non-root operation or legacy
> VMX non-root operation, this instruction can cause a VM exit".
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-15 10:36 ` Xiaoyao Li
@ 2025-10-15 13:57 ` Sean Christopherson
2025-10-15 14:45 ` Xiaoyao Li
0 siblings, 1 reply; 12+ messages in thread
From: Sean Christopherson @ 2025-10-15 13:57 UTC (permalink / raw)
To: Xiaoyao Li; +Cc: Paolo Bonzini, kvm, linux-kernel, Kai Huang, Rick Edgecombe
On Wed, Oct 15, 2025, Xiaoyao Li wrote:
> On 10/15/2025 7:10 AM, Sean Christopherson wrote:
> > diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
> > index 76271962cb70..f64a1eb241b6 100644
> > --- a/arch/x86/kvm/vmx/nested.c
> > +++ b/arch/x86/kvm/vmx/nested.c
> > @@ -6728,6 +6728,14 @@ static bool nested_vmx_l1_wants_exit(struct kvm_vcpu *vcpu,
> > case EXIT_REASON_NOTIFY:
> > /* Notify VM exit is not exposed to L1 */
> > return false;
> > + case EXIT_REASON_SEAMCALL:
> > + case EXIT_REASON_TDCALL:
> > + /*
> > + * SEAMCALL and TDCALL unconditionally VM-Exit, but aren't
> > + * virtualized by KVM for L1 hypervisors, i.e. L1 should
> > + * never want or expect such an exit.
> > + */
>
> The i.e. part is confusing? It is exactly forwarding the EXITs to L1, while
> it says L1 should never want or expect such an exit.
Gah, the comment is right, the code is wrong.
/facepalm
I even tried to explicitly test this, but I put the TDCALL and SEAMCALL in L1
instead of L2.
diff --git a/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c b/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c
index a100ee5f0009..1d7ef7d2d381 100644
--- a/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c
+++ b/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c
@@ -23,11 +23,17 @@ static void l2_guest_code(void)
: : [port] "d" (ARBITRARY_IO_PORT) : "rax");
}
+#define tdcall ".byte 0x66,0x0f,0x01,0xcc"
+#define seamcall ".byte 0x66,0x0f,0x01,0xcf"
+
static void l1_guest_code(struct vmx_pages *vmx_pages)
{
#define L2_GUEST_STACK_SIZE 64
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
+ TEST_ASSERT_EQ(kvm_asm_safe(tdcall), UD_VECTOR);
+ TEST_ASSERT_EQ(kvm_asm_safe(seamcall), UD_VECTOR);
+
GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages));
GUEST_ASSERT(load_vmcs(vmx_pages));
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-15 13:57 ` Sean Christopherson
@ 2025-10-15 14:45 ` Xiaoyao Li
2025-10-16 18:28 ` Sean Christopherson
0 siblings, 1 reply; 12+ messages in thread
From: Xiaoyao Li @ 2025-10-15 14:45 UTC (permalink / raw)
To: Sean Christopherson
Cc: Paolo Bonzini, kvm, linux-kernel, Kai Huang, Rick Edgecombe
On 10/15/2025 9:57 PM, Sean Christopherson wrote:
> On Wed, Oct 15, 2025, Xiaoyao Li wrote:
>> On 10/15/2025 7:10 AM, Sean Christopherson wrote:
>>> diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
>>> index 76271962cb70..f64a1eb241b6 100644
>>> --- a/arch/x86/kvm/vmx/nested.c
>>> +++ b/arch/x86/kvm/vmx/nested.c
>>> @@ -6728,6 +6728,14 @@ static bool nested_vmx_l1_wants_exit(struct kvm_vcpu *vcpu,
>>> case EXIT_REASON_NOTIFY:
>>> /* Notify VM exit is not exposed to L1 */
>>> return false;
>>> + case EXIT_REASON_SEAMCALL:
>>> + case EXIT_REASON_TDCALL:
>>> + /*
>>> + * SEAMCALL and TDCALL unconditionally VM-Exit, but aren't
>>> + * virtualized by KVM for L1 hypervisors, i.e. L1 should
>>> + * never want or expect such an exit.
>>> + */
>>
>> The i.e. part is confusing? It is exactly forwarding the EXITs to L1, while
>> it says L1 should never want or expect such an exit.
>
> Gah, the comment is right, the code is wrong.
So the intent was to return false here? to let L0 handle the exit?
Then I have a question, why not implement it in
nested_vmx_l0_wants_exit()? what's the reason and rule here?
> /facepalm
>
> I even tried to explicitly test this, but I put the TDCALL and SEAMCALL in L1
> instead of L2.
>
> diff --git a/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c b/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c
> index a100ee5f0009..1d7ef7d2d381 100644
> --- a/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c
> +++ b/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c
> @@ -23,11 +23,17 @@ static void l2_guest_code(void)
> : : [port] "d" (ARBITRARY_IO_PORT) : "rax");
> }
>
> +#define tdcall ".byte 0x66,0x0f,0x01,0xcc"
> +#define seamcall ".byte 0x66,0x0f,0x01,0xcf"
> +
> static void l1_guest_code(struct vmx_pages *vmx_pages)
> {
> #define L2_GUEST_STACK_SIZE 64
> unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
>
> + TEST_ASSERT_EQ(kvm_asm_safe(tdcall), UD_VECTOR);
> + TEST_ASSERT_EQ(kvm_asm_safe(seamcall), UD_VECTOR);
> +
> GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages));
> GUEST_ASSERT(load_vmcs(vmx_pages));
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-15 13:56 ` Sean Christopherson
@ 2025-10-15 15:49 ` dan.j.williams
0 siblings, 0 replies; 12+ messages in thread
From: dan.j.williams @ 2025-10-15 15:49 UTC (permalink / raw)
To: Sean Christopherson, dan.j.williams
Cc: Paolo Bonzini, kvm, linux-kernel, Kai Huang, Xiaoyao Li,
Rick Edgecombe
Sean Christopherson wrote:
[..]
> IMO, any reasonable reading of "inSEAM" is that it is talking about #1, in which
> case the pseudocode effectively says that SEAMCALL should #UD if executed in
> "SEAM VMX non-root operation", but that's obviously not the case based on the
> statement below as well as the TDX-Module code.
>
> Furthermore, the only transitions for"inSEAM" are that it's set to '1' by SEAMCALL,
> and cleared to '0' by SEAMRET. That implies that it's _not_ cleared by VM-Enter
> from SEAM VMX root operation to SEAM VMX non-root operation, which reinforces my
> reading of "inSEAM == SEAM operation".
Ah, got it, I see it now. Added this need for clarification to the
errata ticket, and already got an ack on your Note2.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL
2025-10-15 14:45 ` Xiaoyao Li
@ 2025-10-16 18:28 ` Sean Christopherson
0 siblings, 0 replies; 12+ messages in thread
From: Sean Christopherson @ 2025-10-16 18:28 UTC (permalink / raw)
To: Xiaoyao Li; +Cc: Paolo Bonzini, kvm, linux-kernel, Kai Huang, Rick Edgecombe
On Wed, Oct 15, 2025, Xiaoyao Li wrote:
> On 10/15/2025 9:57 PM, Sean Christopherson wrote:
> > On Wed, Oct 15, 2025, Xiaoyao Li wrote:
> > > On 10/15/2025 7:10 AM, Sean Christopherson wrote:
> > > > diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
> > > > index 76271962cb70..f64a1eb241b6 100644
> > > > --- a/arch/x86/kvm/vmx/nested.c
> > > > +++ b/arch/x86/kvm/vmx/nested.c
> > > > @@ -6728,6 +6728,14 @@ static bool nested_vmx_l1_wants_exit(struct kvm_vcpu *vcpu,
> > > > case EXIT_REASON_NOTIFY:
> > > > /* Notify VM exit is not exposed to L1 */
> > > > return false;
> > > > + case EXIT_REASON_SEAMCALL:
> > > > + case EXIT_REASON_TDCALL:
> > > > + /*
> > > > + * SEAMCALL and TDCALL unconditionally VM-Exit, but aren't
> > > > + * virtualized by KVM for L1 hypervisors, i.e. L1 should
> > > > + * never want or expect such an exit.
> > > > + */
> > >
> > > The i.e. part is confusing? It is exactly forwarding the EXITs to L1, while
> > > it says L1 should never want or expect such an exit.
> >
> > Gah, the comment is right, the code is wrong.
>
> So the intent was to return false here? to let L0 handle the exit?
Correct.
> Then I have a question, why not implement it in nested_vmx_l0_wants_exit()?
> what's the reason and rule here?
The basic gist of "l0/l1 wants exit" is that each entity (L0 and L1) should act
independently. And if both L0 and L1 "want" the exit, then L0 wins.
E.g. for EXIT_REASON_EXTERNAL_INTERRUPT, KVM _always_ wants the exit because KVM
unconditionally runs with PIN_BASED_EXT_INTR_MASK set. But L1 might want the
exit too, i.e. if it too is running with PIN_BASED_EXT_INTR_MASK. But L1 doesn't
get the exit because L0's desire trumps L1.
Other exit are less straightfoward. E.g. EXIT_REASON_EXCEPTION_NMI is filled with
conditionals because KVM needs to determine if the exception was due to something
KVM did, i.e. if the exception needs to be resolved by KVM, or if it the exception
isn't at all related to KVM and thus isn't "want" by L0. But the exception may
still be routed to L0, e.g. if L1 doesn't want it.
For this particular case, L1 _can't_ want the exit, because the exit simply shouldn't
exist from L1's perspective. Whether or not L0 wants the exit can't really be
known, because that would require predicting the future. E.g. in the unlikely
case that KVM somehow virtualized some piece of TDX and thus exposed SEAMCALL
and/or TDCALL exits to L1, then nested_vmx_l1_wants_exit() _must_ be updated,
if only to consult the L1 VMXON state. But L0's wants may or may not change;
if there are no scenarios where KVM/L0 "wants" the exit, then there wouldn't be
a need to modify nested_vmx_l0_wants_exit().
And so the most future-resistant location for this particular case is
nested_vmx_l1_wants_exit().
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2025-10-16 18:28 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-14 23:10 [PATCH] KVM: VMX: Inject #UD if guest tries to execute SEAMCALL or TDCALL Sean Christopherson
2025-10-15 0:22 ` dan.j.williams
2025-10-15 13:56 ` Sean Christopherson
2025-10-15 15:49 ` dan.j.williams
2025-10-15 1:13 ` Chao Gao
2025-10-15 3:11 ` Xiaoyao Li
2025-10-15 13:20 ` Sean Christopherson
2025-10-15 10:36 ` Xiaoyao Li
2025-10-15 13:57 ` Sean Christopherson
2025-10-15 14:45 ` Xiaoyao Li
2025-10-16 18:28 ` Sean Christopherson
2025-10-15 13:38 ` Binbin Wu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox