Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits
@ 2026-08-27  3:18 Binbin Wu
  2026-08-27  3:18 ` [PATCH v3 1/4] KVM: TDX: Track configurable CPUID bits allowed by KVM Binbin Wu
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Binbin Wu @ 2026-08-27  3:18 UTC (permalink / raw)
  To: linux-kernel, kvm
  Cc: seanjc, pbonzini, dave.hansen, andrew.cooper3, nik.borisov, kas,
	rick.p.edgecombe, xiaoyao.li, chao.gao, binbin.wu

Hi,

The purpose of this patch series is to prevent userspace from enabling
host state clobbering features that KVM does not support for TDX.  A host
state clobbering feature exposed on a new TDX module/platform can corrupt
host state if KVM does not explicitly save and restore the related MSR(s)
across host/guest transitions.  If such a feature is blindly exposed to
and used by a TD, the host will behave unexpectedly.

Except for a few fixed-1 bits required for basic TDX support, host state
clobbering features are either directly configurable or gated by TD
ATTRIBUTES/XFAM.  So an allowlist covering only the directly configurable
CPUID bits, plus the corresponding filtering and validation, is sufficient
to serve the purpose while keeping the code footprint small.

Open question for Sean/Paolo
============================
This revision comes long after Sean confirmed the overall direction on
RFC v2 [1], due to internal discussions about a potential backward
compatibility issue: a CPUID field can change virtualization type to
directly configurable.  The TDX module can maintain backward
compatibility in most cases; the problematic case is a single bit that
was effectively "1" and non-configurable becoming directly configurable.
The only scenario we can envision for that is an x86 feature deprecation.

In that case the TDX module has to decide how to handle deprecation,
especially with respect to TD migration compatibility.  That
pre-existing VMs with the deprecated feature enabled cannot migrate from
an old platform to a new one is a problem shared by TDX and VMX, but the
TDX module additionally has to decide:
- Whether to allow TDs newly created on old platforms to migrate to new
  platforms.
- Whether to allow TDs created on new platforms to migrate to old
  platforms.

There are two options:
1. Do not offer the VMM the ability to configure the bit, i.e. simply
   report it as zero on platforms that no longer support the feature.
   This disallows both migration cases above.
2. Make the bit directly configurable so the VMM can clear it even on
   platforms that still support the feature, allowing both migration
   cases above.  However, since the bit is not in KVM's allowlist,
   userspace would no longer be able to enable it on old platforms after
   a TDX module update.

Adding all supported CPUID bits to the allowlist, regardless of their
virtualization type in the TDX module, would avoid the compatibility
issue for option 2, at the cost of a significantly larger series.

Given that x86 feature deprecation is expected to be very rare, and that
Intel will discuss such cases with the community beforehand, does the
approach taken by this series look acceptable?  If so, the goal for this
revision is to collect detailed feedback and, where appropriate,
Reviewed-by tags.

Expected host state clobbering behavior for TDX
===============================================
We also want to call for discussions about the expected host state
clobbering behavior for TDX here for future features.

For a normal VMX guest, VM entry/exit behavior for a given piece of CPU
state is architecturally defined: state is either switched by hardware via
VMCS host/guest fields, or left as the guest value on VM exit and managed
by KVM in software.

For TDs, the host/guest transition goes through TDH.VP.ENTER, and what the
TDX module does with a given piece of host state is defined by the TDX
module ABI rather than by the x86 architecture.

What we would like to align on is the expected baseline behavior of
TDH.VP.ENTER for future features.  The proposal is to have TDX simply
match VMX behavior, i.e. on return from TDH.VP.ENTER, state that VMX would
restore from the VMCS host fields is restored, and state that VMX would
leave as the guest value is clobbered.  That keeps a single model for VMM,
and means enabling a new feature for TDs requires the same work flow as
enabling it for VMX.

FRED is a useful concrete example.  Under VMX, the FRED host state in
IA32_FRED_CONFIG, IA32_FRED_STKLVLS, IA32_FRED_RSP1-3 and
IA32_FRED_SSP1-3 is covered by the VMCS host-state area, so the TDX module
is expected to restore these MSRs on TDH.VP.ENTER return.  IA32_FRED_RSP0
and IA32_PL0_SSP (a.k.a. IA32_FRED_SSP0) are handled by software, so the
TDX module is expected to clobber them on TDH.VP.ENTER return.

The series
==========

Starting with v2 [2], the series takes a simpler, TDX-contained approach
instead of the comprehensive CPUID paranoid verification framework across
VMX, SVM, and TDX proposed in v1 [3].  It validates only the TDX directly
configurable CPUID bits, which are reported by the TDX module in
CPUID_CONFIG fields that the VMM can configure for a TD.  All filtering
and validation logic is isolated within TDX code.  This is sufficient to
address the host clobbering issue because:
- The TDX module will not introduce new host state clobbering features
  that are fixed-1.
- Non-directly configurable feature bits (i.e. features controlled by XFAM
  or ATTRIBUTES) cannot be enabled by userspace without KVM's support.

Specifically, this series builds a KVM-side allowlist of supported TDX
directly configurable CPUID bits to:
 - Filter KVM_TDX_CAPABILITIES
   Replace the hardcoded denylist to only report configurable bits that
   KVM explicitly supports.
 - Validate KVM_TDX_INIT_VM
   Reject any configurable bit that the TDX module allows but KVM does
   not support, as well as CPUID entries with an unexpected subleaf.

With this allowlist, newly added TDX configurable CPUID bits will not be
exposed to userspace until KVM explicitly opts in after fulfilling the
necessary virtualization requirements.

The allowlist consists of two parts:
- Feature CPUID bits, which are tracked in tdx_cpu_cfg_caps[] following
  the organization of kvm_cpu_caps[].  It holds KVM's supported masks for
  the TDX configurable CPUID feature bits.
- Non-feature multi-bit fields, which are handled at runtime for the CPUID
  registers holding such fields.

For compatibility with older TDX modules that report CORE_CAPABILITIES as
fixed-1, report CORE_CAPABILITIES as configurable even though KVM does not
support guest access to MSR_IA32_CORE_CAPS.  This allows userspace to keep
enabling CORE_CAPABILITIES when the bit changes from fixed-1 to directly
configurable, and allows userspace to infer that the bit is no longer
fixed-1 so it can adjust its expectations.

Known limitation:
- The series does not check KVM_SET_CPUID2 input for consistency with the
  CPUID configuration supplied through KVM_TDX_INIT_VM.  This is not
  required for host safety today because KVM does not use its vCPU CPUID
  model to decide whether to manage host-clobbering state for TDs.  A
  consistency check should be added if KVM ever starts relying on the vCPU
  CPUID model for such decisions.

Changes from v2 [2]:
- Drop the dedicated data structure introduced in v2; track only feature
  bits following the organization of kvm_cpu_caps[], and handle
  non-feature bits at runtime. (Sean)
- Add AMX_COMPLEX since it has been defined in the CPUID virtualization
  doc.
- Add CPUID.0x24.0.EBX[7:0] into allow list. There is a mismatch of the
  description about CPUID.0x24.0.EBX[7:0], which is listed as
  "XFAM & CPUID_Enabled & Native" but should be "XFAM & CPUID_Enabled &
  Configured & Native".
- Move the patch for CORE_CAPABILITIES earlier in the series to avoid
  breaking userspace during bisection. (Xiaoyao)
- Report CORE_CAPABILITIES as a configurable bit to userspace for
  backward compatibility, but drop the MSR_IA32_CORE_CAPS access code.
- Use two versions of macros to distinguish whether a supported TDX
  configurable CPUID bit should be checked against KVM's common CPU
  capabilities.
- Validate KVM_TDX_INIT_VM input against the allowlist instead of masking
  it, and additionally reject entries whose index does not match the
  TDX sysinfo configuration. (Sashiko)

Changes from v1 [3]:
 - Drop the overarching CPUID paranoid verification framework across
   VMX/SVM/TDX and the opt-in interface. (Sean)
 - Shift focus entirely to isolating and validating TDX directly
   configurable CPUID bits.

The series follows the CSV version of the CPUID virtualization document
from "Intel TDX Module ABI Definitions" [4], updated in June 2026.

AI tools were used to:
- Check the coverage of the directly configurable CPUID bits in the
  document against the allowlist in this series.
- Polish the cover letter and commit messages.
- Do code review.

[1] https://lore.kernel.org/kvm/aj1fqZEcApyd4sxi@google.com
[2] https://lore.kernel.org/kvm/20260604023314.3907511-1-binbin.wu@linux.intel.com
[3] https://lore.kernel.org/kvm/20260417073610.3246316-1-binbin.wu@linux.intel.com
[4] https://cdrdv2.intel.com/v1/dl/getContent/795381

Binbin Wu (4):
  KVM: TDX: Track configurable CPUID bits allowed by KVM
  KVM: TDX: Report CORE_CAPABILITIES as configurable
  KVM: TDX: Filter configurable CPUID bits
  KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM

 arch/x86/kvm/vmx/tdx.c | 258 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 229 insertions(+), 29 deletions(-)


base-commit: 76671054f9a1ff6abb976583cd8da37650acdc97
-- 
2.46.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v3 1/4] KVM: TDX: Track configurable CPUID bits allowed by KVM
  2026-08-27  3:18 [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Binbin Wu
@ 2026-08-27  3:18 ` Binbin Wu
  2026-08-27  3:18 ` [PATCH v3 2/4] KVM: TDX: Report CORE_CAPABILITIES as configurable Binbin Wu
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 10+ messages in thread
From: Binbin Wu @ 2026-08-27  3:18 UTC (permalink / raw)
  To: linux-kernel, kvm
  Cc: seanjc, pbonzini, dave.hansen, andrew.cooper3, nik.borisov, kas,
	rick.p.edgecombe, xiaoyao.li, chao.gao, binbin.wu

Add tdx_cpu_cfg_caps[] to track the subset of TDX directly configurable
CPUID feature bits that KVM supports, and build the masks during TDX
hardware setup via tdx_initialize_cpu_cfg_caps().

The TDX module reports the CPUID bits that the VMM can directly configure
for a TD, but KVM cannot blindly expose all reported bits to userspace.
Certain features imply additional architectural state, e.g. one or more
MSRs, that KVM must explicitly manage across host/guest transitions to
prevent host state corruption.

Today KVM relies on a hardcoded denylist, i.e. it clears a few known
problematic bits, e.g. TSX and WAITPKG, and passes everything else through.
A denylist is fundamentally fragile while an allowlist inverts the default,
i.e. unknown configurable bits are hidden and not allowed to be enabled
until KVM explicitly opts in.

Except for a few fixed-1 bits required for basic TDX support, host state
clobbering features are either directly configurable or gated by TD
ATTRIBUTES/XFAM.  Tracking only the directly configurable feature bits is
therefore sufficient to serve the purpose while keeping the code footprint
small.

Organize tdx_cpu_cfg_caps[] following kvm_cpu_caps[] so that the masks can
be built with the similar feature-name based initializers.  CPUID registers
that hold directly configurable non-feature (multi-bit) fields are handled
separately.

The allowlist is consumed by later patches to filter KVM_TDX_CAPABILITIES
and to reject unsupported CPUID input to KVM_TDX_INIT_VM, so that newly
introduced TDX directly configurable CPUID feature bits stay hidden from
userspace until KVM explicitly opts in.

Add comments as placeholders for HLE, RTM and WAITPKG, which KVM doesn't
support for TDX yet.

Signed-off-by: Binbin Wu <binbin.wu@linux.intel.com>
---
v3:
- Drop the new data structure in v2 and only track feature bits by
  following the organization of kvm_cpu_caps[], handle non-feature
  bits separately. (Sean)
- Use two versions of macros (TDX_CFG_F() VS. TDX_CFG_EXTRA_F()) to
  distinguish whether a supported TDX configurable CPUID bit should be
  checked against KVM's common cpu capabilities.
- Add AMX_COMPLEX since it has been defined in the CPUID virtualization doc.
---
 arch/x86/kvm/vmx/tdx.c | 145 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 145 insertions(+)

diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index b272c20586a7..d4a3a42cfd9d 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -52,6 +52,149 @@
 	__TDX_BUG_ON(__err, #__fn, __kvm, ", " #a1 " 0x%llx, " #a2 ", 0x%llx, " #a3 " 0x%llx", \
 		     a1, a2, a3)
 
+static u32 tdx_cpu_cfg_caps[NR_KVM_CPU_CAPS] __ro_after_init;
+static_assert(ARRAY_SIZE(tdx_cpu_cfg_caps) == ARRAY_SIZE(kvm_cpu_caps));
+
+#define TDX_VALIDATE_CPU_CAP_USAGE(name)			\
+	BUILD_BUG_ON(__feature_leaf(X86_FEATURE_##name) !=	\
+		     tdx_cpu_cap_init_in_progress)
+
+/* For feature bit that KVM advertised through kvm_cpu_caps[]. */
+#define TDX_CFG_F(name)					\
+({							\
+	TDX_VALIDATE_CPU_CAP_USAGE(name);		\
+	tdx_cfg_caps |= feature_bit(name);		\
+})
+
+/*
+ * For feature bit KVM allows for TDX guests even though it is not advertised
+ * through kvm_cpu_caps[], e.g. MWAIT.
+ */
+#define TDX_CFG_EXTRA_F(name)				\
+({							\
+	TDX_VALIDATE_CPU_CAP_USAGE(name);		\
+	tdx_cfg_extra_caps |= feature_bit(name);	\
+})
+
+#define tdx_cpu_cfg_cap_init(leaf, feature_initializers...)		\
+do {									\
+	const u32 __maybe_unused tdx_cpu_cap_init_in_progress = leaf;	\
+	u32 tdx_cfg_extra_caps = 0;					\
+	u32 tdx_cfg_caps = 0;						\
+									\
+	feature_initializers						\
+	tdx_cpu_cfg_caps[leaf] = (tdx_cfg_caps & kvm_cpu_caps[leaf]) |	\
+				 tdx_cfg_extra_caps;			\
+} while (0)
+
+/*
+ * Track only CPUID feature bits that are directly configurable by userspace.
+ * Features controlled by XFAM or ATTRIBUTES are excluded; userspace cannot
+ * enable them until KVM adds support for the corresponding control.
+ */
+static void __init tdx_initialize_cpu_cfg_caps(void)
+{
+	tdx_cpu_cfg_cap_init(CPUID_1_ECX,
+		TDX_CFG_EXTRA_F(MWAIT),
+		TDX_CFG_F(TSC_DEADLINE_TIMER),
+		TDX_CFG_F(AVX),
+		TDX_CFG_F(F16C),
+	);
+
+	tdx_cpu_cfg_cap_init(CPUID_1_EDX,
+		TDX_CFG_F(MCE),
+		TDX_CFG_F(MTRR),
+		TDX_CFG_F(MCA),
+		TDX_CFG_F(SELFSNOOP),
+	);
+
+	tdx_cpu_cfg_cap_init(CPUID_7_0_EBX,
+		TDX_CFG_F(BMI1),
+		/* HLE */
+		TDX_CFG_F(BMI2),
+		TDX_CFG_F(ERMS),
+		/* RTM */
+		TDX_CFG_F(AVX512F),
+		TDX_CFG_F(AVX512DQ),
+		TDX_CFG_F(ADX),
+		TDX_CFG_F(AVX512IFMA),
+		TDX_CFG_F(AVX512PF),
+		TDX_CFG_F(AVX512ER),
+		TDX_CFG_F(AVX512CD),
+		TDX_CFG_F(AVX512BW),
+		TDX_CFG_F(AVX512VL),
+	);
+
+	tdx_cpu_cfg_cap_init(CPUID_7_ECX,
+		TDX_CFG_F(UMIP),
+		/* WAITPKG */
+		TDX_CFG_F(AVX512_VBMI2),
+		TDX_CFG_F(GFNI),
+		TDX_CFG_F(VAES),
+		TDX_CFG_F(VPCLMULQDQ),
+		TDX_CFG_F(AVX512_VNNI),
+		TDX_CFG_F(AVX512_BITALG),
+		TDX_CFG_F(AVX512_VPOPCNTDQ),
+		TDX_CFG_F(LA57),
+		TDX_CFG_F(RDPID),
+		TDX_CFG_F(CLDEMOTE),
+	);
+
+	tdx_cpu_cfg_cap_init(CPUID_7_EDX,
+		TDX_CFG_F(AVX512_4VNNIW),
+		TDX_CFG_F(AVX512_4FMAPS),
+		TDX_CFG_F(FSRM),
+		TDX_CFG_F(AVX512_VP2INTERSECT),
+		TDX_CFG_F(SERIALIZE),
+		TDX_CFG_F(TSXLDTRK),
+	);
+
+	tdx_cpu_cfg_cap_init(CPUID_7_1_EAX,
+		TDX_CFG_F(SHA512),
+		TDX_CFG_F(SM3),
+		TDX_CFG_F(SM4),
+		TDX_CFG_F(AVX_VNNI),
+		TDX_CFG_F(AVX512_BF16),
+		TDX_CFG_F(CMPCCXADD),
+		TDX_CFG_F(FZRM),
+		TDX_CFG_F(FSRS),
+		TDX_CFG_F(FSRC),
+		TDX_CFG_F(LKGS),
+		TDX_CFG_F(WRMSRNS),
+		TDX_CFG_F(AMX_FP16),
+		TDX_CFG_F(AVX_IFMA),
+		TDX_CFG_F(LAM),
+		TDX_CFG_F(MOVRS),
+	);
+
+	tdx_cpu_cfg_cap_init(CPUID_7_1_EDX,
+		TDX_CFG_F(AVX_VNNI_INT8),
+		TDX_CFG_F(AVX_NE_CONVERT),
+		TDX_CFG_F(AMX_COMPLEX),
+		TDX_CFG_F(AVX_VNNI_INT16),
+		TDX_CFG_F(PREFETCHITI),
+		TDX_CFG_F(AVX10),
+	);
+
+	tdx_cpu_cfg_cap_init(CPUID_7_2_EDX,
+		TDX_CFG_F(DDPD_U),
+		TDX_CFG_F(MCDT_NO),
+	);
+
+	tdx_cpu_cfg_cap_init(CPUID_1E_1_EAX,
+		TDX_CFG_F(AMX_FP8),
+		TDX_CFG_F(AMX_TF32),
+		TDX_CFG_F(AMX_AVX512),
+		TDX_CFG_F(AMX_MOVRS),
+	);
+
+	tdx_cpu_cfg_cap_init(CPUID_8000_0008_EBX,
+		TDX_CFG_F(WBNOINVD),
+	);
+}
+
+#undef TDX_CFG_F
+#undef TDX_CFG_EXTRA_F
 
 bool enable_tdx __ro_after_init;
 module_param_named(tdx, enable_tdx, bool, 0444);
@@ -3481,6 +3624,8 @@ int __init tdx_hardware_setup(void)
 		return r;
 	}
 
+	tdx_initialize_cpu_cfg_caps();
+
 	KVM_SANITY_CHECK_VM_STRUCT_SIZE(kvm_tdx);
 
 	vt_x86_ops.vm_size = max_t(unsigned int, vt_x86_ops.vm_size, sizeof(struct kvm_tdx));
-- 
2.46.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v3 2/4] KVM: TDX: Report CORE_CAPABILITIES as configurable
  2026-08-27  3:18 [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Binbin Wu
  2026-08-27  3:18 ` [PATCH v3 1/4] KVM: TDX: Track configurable CPUID bits allowed by KVM Binbin Wu
@ 2026-08-27  3:18 ` Binbin Wu
  2026-08-27  3:18 ` [PATCH v3 3/4] KVM: TDX: Filter configurable CPUID bits Binbin Wu
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 10+ messages in thread
From: Binbin Wu @ 2026-08-27  3:18 UTC (permalink / raw)
  To: linux-kernel, kvm
  Cc: seanjc, pbonzini, dave.hansen, andrew.cooper3, nik.borisov, kas,
	rick.p.edgecombe, xiaoyao.li, chao.gao, binbin.wu

Add CORE_CAPABILITIES (CPUID.0x7.0.EDX[30]) to KVM's allowlist of TDX
directly configurable CPUID feature bits, even though KVM doesn't support
MSR_IA32_CORE_CAPS for TDX guests, to accommodate legacy TDX module
behavior.

Older TDX specs define the CORE_CAPABILITIES CPUID bit as fixed-1, so
userspace may expect the bit to be enabled for TDs.  If the bit becomes
directly configurable in a newer TDX module but is not reported as such to
userspace, userspace can no longer enable it once KVM starts validating
the CPUID configuration input.

Reporting CORE_CAPABILITIES as configurable keeps userspace able to enable
the bit across the fixed-1 => configurable transition, and lets userspace
infer that the bit is no longer fixed-1 so it can adjust its expectations.

Keep MSR_IA32_CORE_CAPS unsupported for TDX guests, as existing TDX users
have not needed guest access to the MSR, and advertising the CPUID bit as
configurable is enough for userspace to handle the legacy-module
compatibility case.

Signed-off-by: Binbin Wu <binbin.wu@linux.intel.com>
---
v3:
- Move this patch earlier in the series to avoid breaking userspace during
  bisection. (Xiaoyao)
- Drop the code for MSR_IA32_CORE_CAPS access.
---
 arch/x86/kvm/vmx/tdx.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index d4a3a42cfd9d..b020518717ac 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -147,6 +147,12 @@ static void __init tdx_initialize_cpu_cfg_caps(void)
 		TDX_CFG_F(AVX512_VP2INTERSECT),
 		TDX_CFG_F(SERIALIZE),
 		TDX_CFG_F(TSXLDTRK),
+		/*
+		 * KVM does not support MSR_IA32_CORE_CAPS, but older TDX specs
+		 * define this bit as fixed-1.  Report it as configurable so
+		 * userspace can know the feature is no longer a fixed-1 bit.
+		 */
+		TDX_CFG_EXTRA_F(CORE_CAPABILITIES),
 	);
 
 	tdx_cpu_cfg_cap_init(CPUID_7_1_EAX,
-- 
2.46.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v3 3/4] KVM: TDX: Filter configurable CPUID bits
  2026-08-27  3:18 [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Binbin Wu
  2026-08-27  3:18 ` [PATCH v3 1/4] KVM: TDX: Track configurable CPUID bits allowed by KVM Binbin Wu
  2026-08-27  3:18 ` [PATCH v3 2/4] KVM: TDX: Report CORE_CAPABILITIES as configurable Binbin Wu
@ 2026-08-27  3:18 ` Binbin Wu
  2026-08-27  3:18 ` [PATCH v3 4/4] KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM Binbin Wu
  2026-08-27 19:33 ` [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Edgecombe, Rick P
  4 siblings, 0 replies; 10+ messages in thread
From: Binbin Wu @ 2026-08-27  3:18 UTC (permalink / raw)
  To: linux-kernel, kvm
  Cc: seanjc, pbonzini, dave.hansen, andrew.cooper3, nik.borisov, kas,
	rick.p.edgecombe, xiaoyao.li, chao.gao, binbin.wu

Filter the directly configurable CPUID bits reported through
KVM_TDX_CAPABILITIES against KVM's TDX allowlist, and drop the hardcoded
denylist based filtering.

The TDX module reports all directly configurable CPUID bits that it
supports for a TD, but KVM must not expose bits that it doesn't support,
as blindly exposing a host state clobbering feature can lead to host state
corruption.  The existing denylist, which clears only TSX and WAITPKG, is
not fail-safe.

Add tdx_get_allowed_cfg_cpuid_mask() to get the mask of directly
configurable bits allowed by KVM for a given CPUID register, covering both
feature bits, which come from tdx_cpu_cfg_caps[], and non-feature bits,
which are enumerated at runtime.  Apply the mask to every CPUID register
reported through KVM_TDX_CAPABILITIES.

With the allowlist in place, newly introduced TDX directly configurable
CPUID bits stay hidden from userspace until KVM explicitly opts in.

Signed-off-by: Binbin Wu <binbin.wu@linux.intel.com>
---
v3:
- Handle non-feature leafs at runtime. (Sean)
- Add CPUID.0x24.0.EBX[7:0] into allow list. There is a mismatch of the
  description about CPUID.0x24.0.EBX[7:0], which is listed as
  "XFAM & CPUID_Enabled & Native" but should be "XFAM & CPUID_Enabled &
  Configured & Native".
---
 arch/x86/kvm/vmx/tdx.c | 84 +++++++++++++++++++++++++++++++++---------
 1 file changed, 66 insertions(+), 18 deletions(-)

diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index b020518717ac..e8951353de73 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -277,34 +277,76 @@ static bool has_tsx(const struct kvm_cpuid_entry2 *entry)
 	       (entry->ebx & TDX_FEATURE_TSX);
 }
 
-static void clear_tsx(struct kvm_cpuid_entry2 *entry)
-{
-	entry->ebx &= ~TDX_FEATURE_TSX;
-}
-
 static bool has_waitpkg(const struct kvm_cpuid_entry2 *entry)
 {
 	return entry->function == 7 && entry->index == 0 &&
 	       (entry->ecx & __feature_bit(X86_FEATURE_WAITPKG));
 }
 
-static void clear_waitpkg(struct kvm_cpuid_entry2 *entry)
+static bool tdx_unsupported_cpuid(const struct kvm_cpuid_entry2 *entry)
 {
-	entry->ecx &= ~__feature_bit(X86_FEATURE_WAITPKG);
+	return has_tsx(entry) || has_waitpkg(entry);
 }
 
-static void tdx_clear_unsupported_cpuid(struct kvm_cpuid_entry2 *entry)
+#define TDX_CPUID_ALL_ALLOWED_MASK	GENMASK_U32(31, 0)
+
+static u32 tdx_cfg_non_feature_mask(u32 function, u32 index, int reg)
 {
-	if (has_tsx(entry))
-		clear_tsx(entry);
+	/*
+	 * For a leaf/subleaf/register that will never be repurposed to hold
+	 * feature bits, it's safe to return TDX_CPUID_ALL_ALLOWED_MASK, i.e.
+	 * leave the TDX module's CPUID config mask intact.
+	 */
+	switch (function) {
+	case 1:
+		if (reg == CPUID_EAX || reg == CPUID_EBX)
+			return TDX_CPUID_ALL_ALLOWED_MASK;
+		return 0;
+	case 4:
+	case 0x18:
+	case 0x1f:
+		return TDX_CPUID_ALL_ALLOWED_MASK;
+	case 0x24:
+		if (index == 0 && reg == CPUID_EBX)
+			return GENMASK_U32(7, 0);
+		return 0;
+	case 0x80000008:
+		if (reg == CPUID_EAX)
+			return TDX_CPUID_ALL_ALLOWED_MASK;
+		return 0;
+	default:
+		return 0;
+	}
+}
 
-	if (has_waitpkg(entry))
-		clear_waitpkg(entry);
+static u32 tdx_cfg_feature_mask(u32 function, u32 index, int reg)
+{
+	for (int i = 0; i < NR_KVM_CPU_CAPS; i++) {
+		const struct cpuid_reg *cpuid = &reverse_cpuid[i];
+
+		if (!cpuid->function)
+			continue;
+
+		if (cpuid->function == function && cpuid->index == index &&
+		    cpuid->reg == reg)
+			return tdx_cpu_cfg_caps[i];
+	}
+
+	return 0;
 }
 
-static bool tdx_unsupported_cpuid(const struct kvm_cpuid_entry2 *entry)
+static u32 tdx_get_allowed_cfg_cpuid_mask(u32 function, u32 index, int reg)
 {
-	return has_tsx(entry) || has_waitpkg(entry);
+	u32 non_feature_mask = tdx_cfg_non_feature_mask(function, index, reg);
+
+	if (non_feature_mask == TDX_CPUID_ALL_ALLOWED_MASK)
+		return TDX_CPUID_ALL_ALLOWED_MASK;
+
+	/*
+	 * It's possible that a CPUID register contains both feature and
+	 * non-feature bits.
+	 */
+	return non_feature_mask | tdx_cfg_feature_mask(function, index, reg);
 }
 
 #define KVM_TDX_CPUID_NO_SUBLEAF	((__u32)-1)
@@ -330,8 +372,6 @@ static void td_init_cpuid_entry2(struct kvm_cpuid_entry2 *entry, unsigned char i
 	 */
 	if (entry->function == 0x80000008)
 		entry->eax = tdx_set_guest_phys_addr_bits(entry->eax, 0xff);
-
-	tdx_clear_unsupported_cpuid(entry);
 }
 
 #define TDVMCALLINFO_SETUP_EVENT_NOTIFY_INTERRUPT	BIT(1)
@@ -354,8 +394,16 @@ static int init_kvm_tdx_caps(const struct tdx_sys_info_td_conf *td_conf,
 	caps->user_tdvmcallinfo_1_r11 =
 		TDVMCALLINFO_SETUP_EVENT_NOTIFY_INTERRUPT;
 
-	for (i = 0; i < td_conf->num_cpuid_config; i++)
-		td_init_cpuid_entry2(&caps->cpuid.entries[i], i);
+	for (i = 0; i < td_conf->num_cpuid_config; i++) {
+		struct kvm_cpuid_entry2 *e = &caps->cpuid.entries[i];
+
+		td_init_cpuid_entry2(e, i);
+		/* Only report the configurable bits allowed by KVM. */
+		e->eax &= tdx_get_allowed_cfg_cpuid_mask(e->function, e->index, CPUID_EAX);
+		e->ebx &= tdx_get_allowed_cfg_cpuid_mask(e->function, e->index, CPUID_EBX);
+		e->ecx &= tdx_get_allowed_cfg_cpuid_mask(e->function, e->index, CPUID_ECX);
+		e->edx &= tdx_get_allowed_cfg_cpuid_mask(e->function, e->index, CPUID_EDX);
+	}
 
 	return 0;
 }
-- 
2.46.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v3 4/4] KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM
  2026-08-27  3:18 [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Binbin Wu
                   ` (2 preceding siblings ...)
  2026-08-27  3:18 ` [PATCH v3 3/4] KVM: TDX: Filter configurable CPUID bits Binbin Wu
@ 2026-08-27  3:18 ` Binbin Wu
  2026-08-27  3:24   ` sashiko-bot
  2026-08-27 19:33 ` [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Edgecombe, Rick P
  4 siblings, 1 reply; 10+ messages in thread
From: Binbin Wu @ 2026-08-27  3:18 UTC (permalink / raw)
  To: linux-kernel, kvm
  Cc: seanjc, pbonzini, dave.hansen, andrew.cooper3, nik.borisov, kas,
	rick.p.edgecombe, xiaoyao.li, chao.gao, binbin.wu

Validate the CPUID configuration provided by userspace through
KVM_TDX_INIT_VM against KVM's TDX allowlist, and drop the hardcoded
denylist based check.

The TDX module lets the VMM configure certain CPUID features for a TD at
initialization time, but KVM must strictly govern which of them userspace
can actually enable, otherwise a host state clobbering feature could be
enabled behind KVM's back.  The existing check only rejects TSX and
WAITPKG, i.e. it is not fail-safe, as any bit that a future TDX module
makes configurable would be accepted even if KVM has no idea about the
feature.

Add tdx_has_unsupported_cfg_cpuid_bit() and reject KVM_TDX_INIT_VM if
userspace sets any bit outside the mask returned by
tdx_get_allowed_cfg_cpuid_mask().  There is no need to first mask the
userspace input with the bits the TDX module reports as directly
configurable, as anything outside that set is rejected by the TDX module
itself.

Also reject CPUID entries whose index differs from the value expected by
the TDX module, as kvm_find_cpuid_entry2() ignores the index when
KVM_CPUID_FLAG_SIGNIFCANT_INDEX is cleared, and so a mismatching entry
could otherwise be applied to the wrong subleaf.

Signed-off-by: Binbin Wu <binbin.wu@linux.intel.com>
---
v3:
- Check CPUID entry index mismatch b/t userspace input and TDX sysinfo
  configuration. (Sashiko)
- No need to mask the userspace input with TDX module reported directly
  configurable bits first, since the userspace input should be subset of
  directly configurable bits. Otherwise, the input will be rejected by
  the TDX module.
---
 arch/x86/kvm/vmx/tdx.c | 41 +++++++++++++++++++++--------------------
 1 file changed, 21 insertions(+), 20 deletions(-)

diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index e8951353de73..12dea8775fd4 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -269,25 +269,6 @@ static u32 tdx_set_guest_phys_addr_bits(const u32 eax, int addr_bits)
 	return (eax & ~GENMASK(23, 16)) | (addr_bits & 0xff) << 16;
 }
 
-#define TDX_FEATURE_TSX (__feature_bit(X86_FEATURE_HLE) | __feature_bit(X86_FEATURE_RTM))
-
-static bool has_tsx(const struct kvm_cpuid_entry2 *entry)
-{
-	return entry->function == 7 && entry->index == 0 &&
-	       (entry->ebx & TDX_FEATURE_TSX);
-}
-
-static bool has_waitpkg(const struct kvm_cpuid_entry2 *entry)
-{
-	return entry->function == 7 && entry->index == 0 &&
-	       (entry->ecx & __feature_bit(X86_FEATURE_WAITPKG));
-}
-
-static bool tdx_unsupported_cpuid(const struct kvm_cpuid_entry2 *entry)
-{
-	return has_tsx(entry) || has_waitpkg(entry);
-}
-
 #define TDX_CPUID_ALL_ALLOWED_MASK	GENMASK_U32(31, 0)
 
 static u32 tdx_cfg_non_feature_mask(u32 function, u32 index, int reg)
@@ -2533,6 +2514,17 @@ static int setup_tdparams_eptp_controls(struct kvm_cpuid2 *cpuid,
 	return 0;
 }
 
+static bool tdx_has_unsupported_cfg_cpuid_bit(const struct kvm_cpuid_entry2 *entry)
+{
+	u32 function = entry->function;
+	u32 index = entry->index;
+
+	return (entry->eax & ~tdx_get_allowed_cfg_cpuid_mask(function, index, CPUID_EAX)) ||
+	       (entry->ebx & ~tdx_get_allowed_cfg_cpuid_mask(function, index, CPUID_EBX)) ||
+	       (entry->ecx & ~tdx_get_allowed_cfg_cpuid_mask(function, index, CPUID_ECX)) ||
+	       (entry->edx & ~tdx_get_allowed_cfg_cpuid_mask(function, index, CPUID_EDX));
+}
+
 static int setup_tdparams_cpuids(struct kvm_cpuid2 *cpuid,
 				 struct td_params *td_params)
 {
@@ -2556,7 +2548,16 @@ static int setup_tdparams_cpuids(struct kvm_cpuid2 *cpuid,
 		if (!entry)
 			continue;
 
-		if (tdx_unsupported_cpuid(entry))
+		/*
+		 * Reject entries whose index does not match the expected one.
+		 * This catches userspace passing a CPUID entry with the
+		 * KVM_CPUID_FLAG_SIGNIFCANT_INDEX flag cleared when the index
+		 * is significant.
+		 */
+		if (entry->index != tmp.index)
+			return -EINVAL;
+
+		if (tdx_has_unsupported_cfg_cpuid_bit(entry))
 			return -EINVAL;
 
 		copy_cnt++;
-- 
2.46.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 4/4] KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM
  2026-08-27  3:18 ` [PATCH v3 4/4] KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM Binbin Wu
@ 2026-08-27  3:24   ` sashiko-bot
  2026-08-27  7:25     ` Binbin Wu
  0 siblings, 1 reply; 10+ messages in thread
From: sashiko-bot @ 2026-08-27  3:24 UTC (permalink / raw)
  To: Binbin Wu; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Missing bounds check on the hardware-reported `num_cpuid_config` leads to an out-of-bounds heap write when initializing TD parameters if the TDX module reports more than 48 CPUID configuration leaves.
--

commit 491ef15b15a75d61b2ded06ddb90ff6562206292
Author: Binbin Wu <binbin.wu@linux.intel.com>

KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM

This commit validates the CPUID configuration provided by userspace
through KVM_TDX_INIT_VM against KVM's TDX allowlist. It drops the
hardcoded denylist-based check and instead rejects entries matching
unsupported CPUID bits or entries with mismatching subleaf indices.

> diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
> index e8951353de73c..12dea8775fd4f 100644
> --- a/arch/x86/kvm/vmx/tdx.c
> +++ b/arch/x86/kvm/vmx/tdx.c
[ ... ]
> @@ -2556,7 +2548,16 @@ static int setup_tdparams_cpuids(struct kvm_cpuid2 *cpuid,
>  		if (!entry)
>  			continue;
>  
> -		if (tdx_unsupported_cpuid(entry))
> +		/*
> +		 * Reject entries whose index does not match the expected one.
> +		 * This catches userspace passing a CPUID entry with the
> +		 * KVM_CPUID_FLAG_SIGNIFCANT_INDEX flag cleared when the index
> +		 * is significant.
> +		 */
> +		if (entry->index != tmp.index)
> +			return -EINVAL;
> +
> +		if (tdx_has_unsupported_cfg_cpuid_bit(entry))
>  			return -EINVAL;
>  
>  		copy_cnt++;

[Severity: High]
This is a pre-existing issue, but does this loop in setup_tdparams_cpuids()
lack a bounds check on the hardware-reported td_conf->num_cpuid_config?

Looking at the loop just below this diff in arch/x86/kvm/vmx/tdx.c:

    for (i = 0; i < td_conf->num_cpuid_config; i++) {
        ...
        value = &td_params->cpuid_values[i];
        value->eax = entry->eax;
        ...

The target td_params is allocated with a strict 1024-byte size bound
in tdx_td_init():

    td_params = kzalloc_obj(struct td_params);

However, the hardware-reported num_cpuid_config can be up to 128. Because
struct td_params contains preceding fields and is bounded to 1024 bytes,
the cpuid_values flex array can safely hold a maximum of 48 entries.

If a TDX module supports more than 48 CPUID configuration leaves, this
iteration will blindly index past the 48th element, resulting in an
out-of-bounds heap write with user-controlled CPUID values.

Should num_cpuid_config be validated against the maximum capacity of
the td_params allocation?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827031837.2863609-1-binbin.wu@linux.intel.com?part=4

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 4/4] KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM
  2026-08-27  3:24   ` sashiko-bot
@ 2026-08-27  7:25     ` Binbin Wu
  0 siblings, 0 replies; 10+ messages in thread
From: Binbin Wu @ 2026-08-27  7:25 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: kvm



On 8/27/2026 11:24 AM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> 
> Pre-existing issues:
> - [High] Missing bounds check on the hardware-reported `num_cpuid_config` leads to an out-of-bounds heap write when initializing TD parameters if the TDX module reports more than 48 CPUID configuration leaves.
> --
> 
> commit 491ef15b15a75d61b2ded06ddb90ff6562206292
> Author: Binbin Wu <binbin.wu@linux.intel.com>
> 
> KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM
> 
> This commit validates the CPUID configuration provided by userspace
> through KVM_TDX_INIT_VM against KVM's TDX allowlist. It drops the
> hardcoded denylist-based check and instead rejects entries matching
> unsupported CPUID bits or entries with mismatching subleaf indices.
> 
>> diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
>> index e8951353de73c..12dea8775fd4f 100644
>> --- a/arch/x86/kvm/vmx/tdx.c
>> +++ b/arch/x86/kvm/vmx/tdx.c
> [ ... ]
>> @@ -2556,7 +2548,16 @@ static int setup_tdparams_cpuids(struct kvm_cpuid2 *cpuid,
>>  		if (!entry)
>>  			continue;
>>  
>> -		if (tdx_unsupported_cpuid(entry))
>> +		/*
>> +		 * Reject entries whose index does not match the expected one.
>> +		 * This catches userspace passing a CPUID entry with the
>> +		 * KVM_CPUID_FLAG_SIGNIFCANT_INDEX flag cleared when the index
>> +		 * is significant.
>> +		 */
>> +		if (entry->index != tmp.index)
>> +			return -EINVAL;
>> +
>> +		if (tdx_has_unsupported_cfg_cpuid_bit(entry))
>>  			return -EINVAL;
>>  
>>  		copy_cnt++;
> 
> [Severity: High]
> This is a pre-existing issue, but does this loop in setup_tdparams_cpuids()
> lack a bounds check on the hardware-reported td_conf->num_cpuid_config?
> 
> Looking at the loop just below this diff in arch/x86/kvm/vmx/tdx.c:
> 
>     for (i = 0; i < td_conf->num_cpuid_config; i++) {
>         ...
>         value = &td_params->cpuid_values[i];
>         value->eax = entry->eax;
>         ...
> 
> The target td_params is allocated with a strict 1024-byte size bound
> in tdx_td_init():
> 
>     td_params = kzalloc_obj(struct td_params);
> 
> However, the hardware-reported num_cpuid_config can be up to 128. Because
> struct td_params contains preceding fields and is bounded to 1024 bytes,
> the cpuid_values flex array can safely hold a maximum of 48 entries.
> 
> If a TDX module supports more than 48 CPUID configuration leaves, this
> iteration will blindly index past the 48th element, resulting in an
> out-of-bounds heap write with user-controlled CPUID values.
> 
> Should num_cpuid_config be validated against the maximum capacity of
> the td_params allocation?
> 

td_conf->num_cpuid_config is reported by the TDX module and gives the
number of leaves with directly configurable CPUIDs. struct td_params is
defined per the TDX spec with a fixed size of 1024 bytes, which is large
enough to hold every directly configurable CPUID leaf, i.e. exactly
num_cpuid_config entries. So the TDX module should never report a number
larger than td_params can accommodate.

So I think the finding is not a real vulnerability, but a cheap one-time
sanity check is still defensible.

I will send it as a separate hardening patch if there is no objection.


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits
  2026-08-27  3:18 [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Binbin Wu
                   ` (3 preceding siblings ...)
  2026-08-27  3:18 ` [PATCH v3 4/4] KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM Binbin Wu
@ 2026-08-27 19:33 ` Edgecombe, Rick P
  2026-08-28  3:19   ` Binbin Wu
  4 siblings, 1 reply; 10+ messages in thread
From: Edgecombe, Rick P @ 2026-08-27 19:33 UTC (permalink / raw)
  To: kvm@vger.kernel.org, linux-kernel@vger.kernel.org,
	binbin.wu@linux.intel.com
  Cc: Gao, Chao, seanjc@google.com, dave.hansen@linux.intel.com,
	kas@kernel.org, Li, Xiaoyao, pbonzini@redhat.com,
	andrew.cooper3@citrix.com, nik.borisov@suse.com

On Thu, 2026-08-27 at 11:18 +0800, Binbin Wu wrote:
> Hi,
> 
> The purpose of this patch series is to prevent userspace from enabling
> host state clobbering features that KVM does not support for TDX.  A host
> state clobbering feature exposed on a new TDX module/platform can corrupt
> host state if KVM does not explicitly save and restore the related MSR(s)
> across host/guest transitions.  If such a feature is blindly exposed to
> and used by a TD, the host will behave unexpectedly.
> 
> Except for a few fixed-1 bits required for basic TDX support, host state
> clobbering features are either directly configurable or gated by TD
> ATTRIBUTES/XFAM.  So an allowlist covering only the directly configurable
> CPUID bits, plus the corresponding filtering and validation, is sufficient
> to serve the purpose while keeping the code footprint small.
> 
> Open question for Sean/Paolo
> ============================
> This revision comes long after Sean confirmed the overall direction on
> RFC v2 [1], due to internal discussions about a potential backward
> compatibility issue: a CPUID field can change virtualization type to
> directly configurable.  The TDX module can maintain backward
> compatibility in most cases; the problematic case is a single bit that
> was effectively "1" and non-configurable becoming directly configurable.
> The only scenario we can envision for that is an x86 feature deprecation.
> 
> In that case the TDX module has to decide how to handle deprecation,
> especially with respect to TD migration compatibility.  That
> pre-existing VMs with the deprecated feature enabled cannot migrate from
> an old platform to a new one is a problem shared by TDX and VMX, but the
> TDX module additionally has to decide:
> - Whether to allow TDs newly created on old platforms to migrate to new
>   platforms.
> - Whether to allow TDs created on new platforms to migrate to old
>   platforms.
> 
> There are two options:
> 1. Do not offer the VMM the ability to configure the bit, i.e. simply
>    report it as zero on platforms that no longer support the feature.
>    This disallows both migration cases above.
> 2. Make the bit directly configurable so the VMM can clear it even on
>    platforms that still support the feature, allowing both migration
>    cases above.  However, since the bit is not in KVM's allowlist,
>    userspace would no longer be able to enable it on old platforms after
>    a TDX module update.
> 
> Adding all supported CPUID bits to the allowlist, regardless of their
> virtualization type in the TDX module, would avoid the compatibility
> issue for option 2, at the cost of a significantly larger series.

I think we could only do this if no new fixed-1 bits are added. If they were,
then a future fixes-1 bit could be added then deprecated. An old kernel would
not know about it.

But we will not see new fixed-1 bits?

> 
> Given that x86 feature deprecation is expected to be very rare, and that
> Intel will discuss such cases with the community beforehand, does the
> approach taken by this series look acceptable?
> 

In the event a feature bit is turned configurable to support creating TDs that
can be migrated to new platforms without the feature, then the desired value for
the bit on the old platform will be 0. Which is what it would end up being set
as, if the bit was not in the KVM side allow list. So everything will work as
desired for that use?

But a user on the old platform that didn't care about migration and was using
the feature, would suddenly lose it after TDX module upgrade that turned the bit
configurable

But it isn't a kernel bug, so I'd think to punt on this and let TDX module
figure out a solution if/when the time comes. Probably various opt-in knobs to
allow for working around it. If they are not worried, we don't need to worry and
bloat the kernel to prepare for it.

>   If so, the goal for this
> revision is to collect detailed feedback and, where appropriate,
> Reviewed-by tags.
> 
> Expected host state clobbering behavior for TDX
> ===============================================
> We also want to call for discussions about the expected host state
> clobbering behavior for TDX here for future features.
> 
> For a normal VMX guest, VM entry/exit behavior for a given piece of CPU
> state is architecturally defined: state is either switched by hardware via
> VMCS host/guest fields, or left as the guest value on VM exit and managed
> by KVM in software.
> 
> For TDs, the host/guest transition goes through TDH.VP.ENTER, and what the
> TDX module does with a given piece of host state is defined by the TDX
> module ABI rather than by the x86 architecture.
> 
> What we would like to align on is the expected baseline behavior of
> TDH.VP.ENTER for future features.  The proposal is to have TDX simply
> match VMX behavior, i.e. on return from TDH.VP.ENTER, state that VMX would
> restore from the VMCS host fields is restored, and state that VMX would
> leave as the guest value is clobbered.  That keeps a single model for VMM,
> and means enabling a new feature for TDs requires the same work flow as
> enabling it for VMX.

Ideally the TDX save/restore would share code with normal VMs. On the other hand
if we don't share enter/exit paths sufficiently, we may need to duplicate some
save/restore in tdx code. Depending on how much of which category we have, it
could be better for the kernel to have either one.

And if TDX always saved/restored all state across VP.ENTER, then we don't need
bit filtering? What are the problems then with exposing everything to userspace
as we currently do?

> 
> FRED is a useful concrete example.  Under VMX, the FRED host state in
> IA32_FRED_CONFIG, IA32_FRED_STKLVLS, IA32_FRED_RSP1-3 and
> IA32_FRED_SSP1-3 is covered by the VMCS host-state area, so the TDX module
> is expected to restore these MSRs on TDH.VP.ENTER return.  IA32_FRED_RSP0
> and IA32_PL0_SSP (a.k.a. IA32_FRED_SSP0) are handled by software, so the
> TDX module is expected to clobber them on TDH.VP.ENTER return.

And so the ability to have a design like this for the feature depends on CPUID
bit filtering to be in place, otherwise new modules on old kernels can clobber
host state unexpectedly. Future features that follow this approach will need
some VMM opt-in to say "VMM is filtering CPUID bit config", so TDX can safely
expose new clobbering bits. Otherwise, features like FRED will be problems on
old kernels. Do we want it? Where would it get plugged in?

> 
> The series
> ==========
> 
> Starting with v2 [2], the series takes a simpler, TDX-contained approach
> instead of the comprehensive CPUID paranoid verification framework across
> VMX, SVM, and TDX proposed in v1 [3].  It validates only the TDX directly
> configurable CPUID bits, which are reported by the TDX module in
> CPUID_CONFIG fields that the VMM can configure for a TD.  All filtering
> and validation logic is isolated within TDX code.  This is sufficient to
> address the host clobbering issue because:
> - The TDX module will not introduce new host state clobbering features
>   that are fixed-1.
> - Non-directly configurable feature bits (i.e. features controlled by XFAM
>   or ATTRIBUTES) cannot be enabled by userspace without KVM's support.
> 
> Specifically, this series builds a KVM-side allowlist of supported TDX
> directly configurable CPUID bits to:
>  - Filter KVM_TDX_CAPABILITIES
>    Replace the hardcoded denylist to only report configurable bits that
>    KVM explicitly supports.
>  - Validate KVM_TDX_INIT_VM
>    Reject any configurable bit that the TDX module allows but KVM does
>    not support, as well as CPUID entries with an unexpected subleaf.
> 
> With this allowlist, newly added TDX configurable CPUID bits will not be
> exposed to userspace until KVM explicitly opts in after fulfilling the
> necessary virtualization requirements.
> 
> The allowlist consists of two parts:
> - Feature CPUID bits, which are tracked in tdx_cpu_cfg_caps[] following
>   the organization of kvm_cpu_caps[].  It holds KVM's supported masks for
>   the TDX configurable CPUID feature bits.
> - Non-feature multi-bit fields, which are handled at runtime for the CPUID
>   registers holding such fields.
> 
> For compatibility with older TDX modules that report CORE_CAPABILITIES as
> fixed-1, report CORE_CAPABILITIES as configurable even though KVM does not
> support guest access to MSR_IA32_CORE_CAPS.  This allows userspace to keep
> enabling CORE_CAPABILITIES when the bit changes from fixed-1 to directly
> configurable, and allows userspace to infer that the bit is no longer
> fixed-1 so it can adjust its expectations.
> 
> Known limitation:
> - The series does not check KVM_SET_CPUID2 input for consistency with the
>   CPUID configuration supplied through KVM_TDX_INIT_VM.  This is not
>   required for host safety today because KVM does not use its vCPU CPUID
>   model to decide whether to manage host-clobbering state for TDs.  A
>   consistency check should be added if KVM ever starts relying on the vCPU
>   CPUID model for such decisions.


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits
  2026-08-27 19:33 ` [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Edgecombe, Rick P
@ 2026-08-28  3:19   ` Binbin Wu
  2026-08-28 16:58     ` Edgecombe, Rick P
  0 siblings, 1 reply; 10+ messages in thread
From: Binbin Wu @ 2026-08-28  3:19 UTC (permalink / raw)
  To: Edgecombe, Rick P, kvm@vger.kernel.org,
	linux-kernel@vger.kernel.org
  Cc: Gao, Chao, seanjc@google.com, dave.hansen@linux.intel.com,
	kas@kernel.org, Li, Xiaoyao, pbonzini@redhat.com,
	andrew.cooper3@citrix.com, nik.borisov@suse.com

On 8/28/2026 3:33 AM, Edgecombe, Rick P wrote:
> On Thu, 2026-08-27 at 11:18 +0800, Binbin Wu wrote:
>> Hi,
>>
>> The purpose of this patch series is to prevent userspace from enabling
>> host state clobbering features that KVM does not support for TDX.  A host
>> state clobbering feature exposed on a new TDX module/platform can corrupt
>> host state if KVM does not explicitly save and restore the related MSR(s)
>> across host/guest transitions.  If such a feature is blindly exposed to
>> and used by a TD, the host will behave unexpectedly.
>>
>> Except for a few fixed-1 bits required for basic TDX support, host state
>> clobbering features are either directly configurable or gated by TD
>> ATTRIBUTES/XFAM.  So an allowlist covering only the directly configurable
>> CPUID bits, plus the corresponding filtering and validation, is sufficient
>> to serve the purpose while keeping the code footprint small.
>>
>> Open question for Sean/Paolo
>> ============================
>> This revision comes long after Sean confirmed the overall direction on
>> RFC v2 [1], due to internal discussions about a potential backward
>> compatibility issue: a CPUID field can change virtualization type to
>> directly configurable.  The TDX module can maintain backward
>> compatibility in most cases; the problematic case is a single bit that
>> was effectively "1" and non-configurable becoming directly configurable.
>> The only scenario we can envision for that is an x86 feature deprecation.
>>
>> In that case the TDX module has to decide how to handle deprecation,
>> especially with respect to TD migration compatibility.  That
>> pre-existing VMs with the deprecated feature enabled cannot migrate from
>> an old platform to a new one is a problem shared by TDX and VMX, but the
>> TDX module additionally has to decide:
>> - Whether to allow TDs newly created on old platforms to migrate to new
>>   platforms.
>> - Whether to allow TDs created on new platforms to migrate to old
>>   platforms.
>>
>> There are two options:
>> 1. Do not offer the VMM the ability to configure the bit, i.e. simply
>>    report it as zero on platforms that no longer support the feature.
>>    This disallows both migration cases above.
>> 2. Make the bit directly configurable so the VMM can clear it even on
>>    platforms that still support the feature, allowing both migration
>>    cases above.  However, since the bit is not in KVM's allowlist,
>>    userspace would no longer be able to enable it on old platforms after
>>    a TDX module update.
>>
>> Adding all supported CPUID bits to the allowlist, regardless of their
>> virtualization type in the TDX module, would avoid the compatibility
>> issue for option 2, at the cost of a significantly larger series.
> 
> I think we could only do this if no new fixed-1 bits are added. If they were,
> then a future fixes-1 bit could be added then deprecated. An old kernel would
> not know about it.
> 
> But we will not see new fixed-1 bits?

The expectation is that no new fixed-1 bits will be added after the initial TDX
basic support. A new fixed-1 bit means the x86 architecture itself leaves the
TDX module no choice, so the TDX module will not add one unless some very
unusual future architectural change forces it to (and it's hard to see such a
design passing the design review in practice). If that ever does happen, it
would imply a significant architectural change, and we can figure out how to
handle it then.

> 
>>
>> Given that x86 feature deprecation is expected to be very rare, and that
>> Intel will discuss such cases with the community beforehand, does the
>> approach taken by this series look acceptable?
>>
> 
> In the event a feature bit is turned configurable to support creating TDs that
> can be migrated to new platforms without the feature, then the desired value for
> the bit on the old platform will be 0. Which is what it would end up being set
> as, if the bit was not in the KVM side allow list. So everything will work as
> desired for that use?

Yes, it would work for migration cases.

> 
> But a user on the old platform that didn't care about migration and was using
> the feature, would suddenly lose it after TDX module upgrade that turned the bit
> configurable

Yes. This is exactly the backward compatibility issue for option 2.
Though I am not sure how important it is when losing a feature that is being
deprecated.

> 
> But it isn't a kernel bug, so I'd think to punt on this and let TDX module
> figure out a solution if/when the time comes. Probably various opt-in knobs to
> allow for working around it.

Do you mean on the old platforms, the according TDX module versions add opt-in
knobs to allow the "effective 1 -> directly configurable" change?


> If they are not worried, we don't need to worry and
> bloat the kernel to prepare for it.
> 
>>   If so, the goal for this
>> revision is to collect detailed feedback and, where appropriate,
>> Reviewed-by tags.
>>
>> Expected host state clobbering behavior for TDX
>> ===============================================
>> We also want to call for discussions about the expected host state
>> clobbering behavior for TDX here for future features.
>>
>> For a normal VMX guest, VM entry/exit behavior for a given piece of CPU
>> state is architecturally defined: state is either switched by hardware via
>> VMCS host/guest fields, or left as the guest value on VM exit and managed
>> by KVM in software.
>>
>> For TDs, the host/guest transition goes through TDH.VP.ENTER, and what the
>> TDX module does with a given piece of host state is defined by the TDX
>> module ABI rather than by the x86 architecture.
>>
>> What we would like to align on is the expected baseline behavior of
>> TDH.VP.ENTER for future features.  The proposal is to have TDX simply
>> match VMX behavior, i.e. on return from TDH.VP.ENTER, state that VMX would
>> restore from the VMCS host fields is restored, and state that VMX would
>> leave as the guest value is clobbered.  That keeps a single model for VMM,
>> and means enabling a new feature for TDs requires the same work flow as
>> enabling it for VMX.
> 
> Ideally the TDX save/restore would share code with normal VMs. On the other hand
> if we don't share enter/exit paths sufficiently, we may need to duplicate some
> save/restore in tdx code.

It probably needs some TDX specific handling, since the TDX module clobbers the
MSRs (setting them to either their INIT values or some default values), whereas
in the VMX case the guest values are left.

 Depending on how much of which category we have, it
> could be better for the kernel to have either one.
> 
> And if TDX always saved/restored all state across VP.ENTER, then we don't need
> bit filtering? What are the problems then with exposing everything to userspace
> as we currently do?

One consideration is performance.

- When The TDX module clobbers an MSR, there are 1 RDMSR + 2 WRMSR
  TDX WRMSR to restore guest value
  ---
  TDX RDMSR to save guest value
  TDX WRMSR to set to default value
- When The TDX module save/restore an MSR, there are 2 RDMSR + 2 WRMSR
  TDX RDMSR to save host value
  TDX WRMSR to restore guest value
  ---
  TDX RDMSR to save guest value
  TDX WRMSR to restore host value

In most cases where the TDX module chooses to clobber an MSR rather than restore
the host value, that MSR isn't consumed in ring 0, so the Linux kernel/KVM
simply restores the value before returning to userspace.

That means for most TD entry/exit paths there is one extra MSR read per MSR.

Previously in the PUCK meeting, we proposed that the TDX module preserve host
state by default for any new feature, and provide an interface for the host VMM
to opt in to "don't preserve" as an optimization. That proposal wasn't pursued,
since Sean suggested letting KVM do the CPUID validation for TDX instead.

> 
>>
>> FRED is a useful concrete example.  Under VMX, the FRED host state in
>> IA32_FRED_CONFIG, IA32_FRED_STKLVLS, IA32_FRED_RSP1-3 and
>> IA32_FRED_SSP1-3 is covered by the VMCS host-state area, so the TDX module
>> is expected to restore these MSRs on TDH.VP.ENTER return.  IA32_FRED_RSP0
>> and IA32_PL0_SSP (a.k.a. IA32_FRED_SSP0) are handled by software, so the
>> TDX module is expected to clobber them on TDH.VP.ENTER return.
> 
> And so the ability to have a design like this for the feature depends on CPUID
> bit filtering to be in place, otherwise new modules on old kernels can clobber
> host state unexpectedly. 

Yes.

> Future features that follow this approach will need
> some VMM opt-in to say "VMM is filtering CPUID bit config", so TDX can safely
> expose new clobbering bits. Otherwise, features like FRED will be problems on
> old kernels.

If FRED support lands in KVM for non-TDX VMs before this filtering/invalidation
is in place, adding FRED to the hardcoded denylist would work as a temporary
solution.

But that only holds for a well-behaved userspace VMM. QEMU, for example, only
enables features supported by both KVM and TDX, so if FRED isn't supported in
KVM for non-TDX VMs, QEMU won't enable it for TDX either.

A malicious userspace VMM, however, can set a feature regardless of KVM's
reported CPU capabilities, and could still cause trouble.

> Do we want it? Where would it get plugged in?

So if the approach in this series is taken, I think we still need such an opt-in
interface to tell the TDX module that the VMM is now filtering the CPUID bits so
that the TDX module knows that it's safe to report new host state clobbering
features.



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits
  2026-08-28  3:19   ` Binbin Wu
@ 2026-08-28 16:58     ` Edgecombe, Rick P
  0 siblings, 0 replies; 10+ messages in thread
From: Edgecombe, Rick P @ 2026-08-28 16:58 UTC (permalink / raw)
  To: kvm@vger.kernel.org, linux-kernel@vger.kernel.org,
	binbin.wu@linux.intel.com
  Cc: Gao, Chao, seanjc@google.com, dave.hansen@linux.intel.com,
	kas@kernel.org, Li, Xiaoyao, pbonzini@redhat.com,
	andrew.cooper3@citrix.com, nik.borisov@suse.com

On Fri, 2026-08-28 at 11:19 +0800, Binbin Wu wrote:
> > 
> > But it isn't a kernel bug, so I'd think to punt on this and let TDX module
> > figure out a solution if/when the time comes. Probably various opt-in knobs
> > to allow for working around it.
> 
> Do you mean on the old platforms, the according TDX module versions add opt-in
> knobs to allow the "effective 1 -> directly configurable" change?

Yea. Or the other way. Forcing the old fixed-1 bits to 1 automatically. Could be
like a quirk like config thing. User/admin can decide if they want migration
flexible design, or backwards compatibility for existing TDs. BUT, we could work
out the details later if we think we have options.

I think I've convinced myself we have options and can close this one. Agreed?

<snip>
> 
> 
> If FRED support lands in KVM for non-TDX VMs before this
> filtering/invalidation is in place, adding FRED to the hardcoded denylist
> would work as a temporary solution.

This could easily happen.

> 
> But that only holds for a well-behaved userspace VMM. QEMU, for example, only
> enables features supported by both KVM and TDX, so if FRED isn't supported in
> KVM for non-TDX VMs, QEMU won't enable it for TDX either.
> 
> A malicious userspace VMM, however, can set a feature regardless of KVM's
> reported CPU capabilities, and could still cause trouble.

Right. It the problem I thought we would need an opt-in to avoid.

> 
> > Do we want it? Where would it get plugged in?
> 
> So if the approach in this series is taken, I think we still need such an opt-
> in interface to tell the TDX module that the VMM is now filtering the CPUID
> bits so that the TDX module knows that it's safe to report new host state
> clobbering features.

Yep. Can we think about what it would look like? Easiest would be a bit passed
in TDH_SYS_CONFIG. But then arch/x86 is saying how KVM will behave. Ok to me,
for the simplicity. Could come with a nice comment.


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-08-28 16:58 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27  3:18 [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Binbin Wu
2026-08-27  3:18 ` [PATCH v3 1/4] KVM: TDX: Track configurable CPUID bits allowed by KVM Binbin Wu
2026-08-27  3:18 ` [PATCH v3 2/4] KVM: TDX: Report CORE_CAPABILITIES as configurable Binbin Wu
2026-08-27  3:18 ` [PATCH v3 3/4] KVM: TDX: Filter configurable CPUID bits Binbin Wu
2026-08-27  3:18 ` [PATCH v3 4/4] KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM Binbin Wu
2026-08-27  3:24   ` sashiko-bot
2026-08-27  7:25     ` Binbin Wu
2026-08-27 19:33 ` [PATCH v3 0/4] KVM: TDX: Validate directly configurable CPUID bits Edgecombe, Rick P
2026-08-28  3:19   ` Binbin Wu
2026-08-28 16:58     ` Edgecombe, Rick P

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox