[PATCH v4 4/4] KVM: TDX: Validate userspace CPUID input for KVM_TDX_INIT_VM
From: Binbin Wu
Date: Thu Sep 17 2026 - 03:32:00 EST
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_cpuid_cfg_bit() and reject KVM_TDX_INIT_VM if
userspace sets any bit outside the mask returned by
tdx_get_cpuid_cfg_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, i.e. a mismatching entry could
otherwise be applied to the wrong subleaf.
Update the comments for KVM_TDX_INIT_VM in the uapi header and the TDX
documentation accordingly.
Signed-off-by: Binbin Wu <binbin.wu@xxxxxxxxxxxxxxx>
Reviewed-by: Tony Lindgren <tony.lindgren@xxxxxxxxxxxxxxx>
---
v4:
- Update the comments/documentation.
- Collect RB tag from Tony.
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.
---
Documentation/virt/kvm/x86/intel-tdx.rst | 2 ++
arch/x86/include/uapi/asm/kvm.h | 2 ++
arch/x86/kvm/vmx/tdx.c | 41 ++++++++++++------------
3 files changed, 25 insertions(+), 20 deletions(-)
diff --git a/Documentation/virt/kvm/x86/intel-tdx.rst b/Documentation/virt/kvm/x86/intel-tdx.rst
index 6beeb89d7f057..36bd0fee2480d 100644
--- a/Documentation/virt/kvm/x86/intel-tdx.rst
+++ b/Documentation/virt/kvm/x86/intel-tdx.rst
@@ -135,6 +135,8 @@ KVM_CREATE_VM and before creating any VCPUs.
/*
* Call KVM_TDX_INIT_VM before vcpu creation, thus before
* KVM_SET_CPUID2.
+ * KVM validates @cpuid, i.e. setting a bit that KVM_TDX_CAPABILITIES
+ * doesn't report as configurable fails the ioctl.
* This configuration supersedes KVM_SET_CPUID2s for VCPUs because the
* TDX module directly virtualizes those CPUIDs without VMM. The user
* space VMM, e.g. qemu, should make KVM_SET_CPUID2 consistent with
diff --git a/arch/x86/include/uapi/asm/kvm.h b/arch/x86/include/uapi/asm/kvm.h
index 1585ec8040666..784cb5cd7f664 100644
--- a/arch/x86/include/uapi/asm/kvm.h
+++ b/arch/x86/include/uapi/asm/kvm.h
@@ -1032,6 +1032,8 @@ struct kvm_tdx_init_vm {
/*
* Call KVM_TDX_INIT_VM before vcpu creation, thus before
* KVM_SET_CPUID2.
+ * KVM validates @cpuid, i.e. setting a bit that KVM_TDX_CAPABILITIES
+ * doesn't report as configurable fails the ioctl.
* This configuration supersedes KVM_SET_CPUID2s for VCPUs because the
* TDX module directly virtualizes those CPUIDs without VMM. The user
* space VMM, e.g. qemu, should make KVM_SET_CPUID2 consistent with
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index ad7d70b36ffe5..5426191905d0d 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -286,25 +286,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_get_cpuid_cfg_non_feature_mask(u32 function, u32 index, int reg)
@@ -2548,6 +2529,17 @@ static int setup_tdparams_eptp_controls(struct kvm_cpuid2 *cpuid,
return 0;
}
+static bool tdx_has_unsupported_cpuid_cfg_bit(const struct kvm_cpuid_entry2 *entry)
+{
+ u32 function = entry->function;
+ u32 index = entry->index;
+
+ return (entry->eax & ~tdx_get_cpuid_cfg_mask(function, index, CPUID_EAX)) ||
+ (entry->ebx & ~tdx_get_cpuid_cfg_mask(function, index, CPUID_EBX)) ||
+ (entry->ecx & ~tdx_get_cpuid_cfg_mask(function, index, CPUID_ECX)) ||
+ (entry->edx & ~tdx_get_cpuid_cfg_mask(function, index, CPUID_EDX));
+}
+
static int setup_tdparams_cpuids(struct kvm_cpuid2 *cpuid,
struct td_params *td_params)
{
@@ -2571,7 +2563,16 @@ static int setup_tdparams_cpuids(struct kvm_cpuid2 *cpuid,
if (!entry)
continue;
- if (tdx_unsupported_cpuid(entry))
+ /*
+ * Reject entries whose index doesn't 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_cpuid_cfg_bit(entry))
return -EINVAL;
copy_cnt++;
--
2.46.0