[PATCH v3 3/4] KVM: TDX: Filter configurable CPUID bits

From: Binbin Wu

Date: Wed Aug 26 2026 - 23:16:34 EST


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@xxxxxxxxxxxxxxx>
---
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