KVM: TDX: Reject concurrent change to CPUID entry count
Subsystems (5)
PATCH v2 1/1 KVM: TDX: Reject concurrent change to CPUID entry count
Reject KVM_TDX_INIT_VM if userspace changes cpuid.nent between the initial read and the subsequent copy of the initialization data.
tdx_td_init() first reads user_data->cpuid.nent to size the flexible kvm_tdx_init_vm copy. The copied structure also contains cpuid.nent, and that field can differ from the value used to size the allocation if userspace modifies the input concurrently. setup_tdparams_cpuids() later passes init_vm->cpuid.nent to kvm_find_cpuid_entry2(), which uses it as the array bound for the copied entries.
Require the copied count to match the value used to size the allocation so that CPUID parsing cannot access beyond the entries actually copied.
---
v2:
- Reject the request if mismatch instead overwriting the value. (Thorsten, Rick)
- Lump the check into the existing sanity check on the cpuid field. (Sean)
- "KVM: x86: TDX:" -> "KVM: TDX:" in the shortlog.
v1:
- https://lore.kernel.org/kvm/[email protected]/
--- arch/x86/kvm/vmx/tdx.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
arch/x86/kvm/vmx/tdx.c +5 -1
@@ -2797,7 +2797,11 @@ static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd) goto out; } -if (init_vm->cpuid.padding) { +/* +* Reject the request if userspace changes cpuid.nent between the +* initial read and the subsequent copy. +*/ +if (init_vm->cpuid.padding || init_vm->cpuid.nent != nr_user_entries) { ret = -EINVAL ; goto out; }
base-commit: f1e5ada5ab62dbe32350bc161771c9afc6a896de
Signature · 1 line
2.46.0
[email protected]
AI reviewThank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Acquiring cpus_read_lock() while holding kvm->lock and kvm->slots_lock violates the KVM lock ordering hierarchy and creates an AB-BA deadlock risk with CPU hotplug.
- [High] Failing to charge the misc_cg in __tdx_td_init() jumps to a cleanup label that incorrectly uncharges and decrements a global counter.
- [High] Kernel heap buffer overflow when setting up TD params if the TDX module reports a large number of CPUID configurations.
--
--- Patch [1]: [PATCH v2] KVM: TDX: Reject concurrent change to CPUID entry count --- commit 43b81457d393443ba395e2515a2d545aac295cf0
Author: Binbin Wu <[email protected]>
KVM: TDX: Reject concurrent change to CPUID entry count
Reject KVM_TDX_INIT_VM if userspace changes cpuid.nent between the initial read and the subsequent copy of the initialization data.
Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +0
-0@@ -2738,7 +2738,11 @@ static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd) goto out; }
arch/x86/kvm/vmx/tdx.c static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd) · line 2748
Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +5
-1-if (init_vm->cpuid.padding) { +/* +* Reject the request if userspace changes cpuid.nent between the +* initial read and the subsequent copy. +*/ +if (init_vm->cpuid.padding || init_vm->cpuid.nent != nr_user_entries) { ret = -EINVAL ; goto out; }
[Severity: High]
This is a pre-existing issue, but I noticed a potential lock ordering problem in the initialization path called shortly after this check.
In __tdx_td_init(), the code acquires the cpu read lock:
arch/x86/kvm/vmx/tdx.c:__tdx_td_init() {
...
cpus_read_lock();
...
}However, tdx_td_init() is called while the vm state locks are already held higher in the stack:
arch/x86/kvm/vmx/tdx.c:tdx_acquire_vm_state_locks() {
...
mutex_lock(&kvm->lock);
...
}Taking cpus_read_lock() while holding kvm->lock creates a deadlock risk against other subsystems, like cpu hotplug, that hold cpu_hotplug_lock via cpus_write_lock() and subsequently attempt to acquire kvm->lock.
Can this lead to a system-wide deadlock during cpu hotplug operations?
[Severity: High]
This is another pre-existing issue in the same __tdx_td_init() path.
If misc_cg_try_charge() fails, it jumps to the free_hkid label:
arch/x86/kvm/vmx/tdx.c:__tdx_td_init() {
...
ret = misc_cg_try_charge(MISC_CG_RES_TDX, kvm_tdx->misc_cg, 1);
if (ret)
goto free_hkid;
...
}The free_hkid cleanup path executes tdx_hkid_free(), which unconditionally decrements the configured hkid counter and uncharges the cgroup:
arch/x86/kvm/vmx/tdx.c:tdx_hkid_free() {
...
atomic_dec(&nr_configured_hkid);
misc_cg_uncharge(MISC_CG_RES_TDX, kvm_tdx->misc_cg, 1);
...
}Since the initial charge failed and the code jumped past the atomic_inc() for nr_configured_hkid, does this error path underflow the global atomic counter and corrupt the cgroup accounting?
[Severity: High]
This isn't a bug introduced by this patch, but there is a potential out of bounds write later in this function when setting up the td params.
In setup_tdparams_cpuids(), the loop is bounded by num_cpuid_config:
arch/x86/kvm/vmx/tdx.c:setup_tdparams_cpuids() {
...
for (i = 0; i < td_conf->num_cpuid_config; i++) {
...
value = &td_params->cpuid_values[i];
value->eax = entry->eax;
...
}
}The td_params structure is strictly sized to 1024 bytes, which only leaves room for 48 elements in the cpuid_values array.
Since tdx_global_metadata.c permits td_conf->num_cpuid_config to be up to 128, will this loop write past the end of the td_params kernel heap allocation if the module reports a large number of cpuid configurations?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Quoted 56 lines · On 7/10/2026 12:01 PM, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Acquiringcpus_read_lock()while holdingkvm->lockandkvm->slots_lockviolates the KVM lock ordering hierarchy and creates an AB-BA deadlock risk with CPU hotplug.
- [High] Failing to charge themisc_cgin__tdx_td_init()jumps to a cleanup label that incorrectly uncharges and decrements a global counter.
- [High] Kernel heap buffer overflow when setting up TD params if the TDX module reports a large number of CPUID configurations.
----- Patch [1]: [PATCH v2] KVM: TDX: Reject concurrent change to CPUID entry count --- commit
43b81457d393443ba395e2515a2d545aac295cf0
Author: Binbin Wu <[email protected]>KVM: TDX: Reject concurrent change to CPUID entry count
Reject
KVM_TDX_INIT_VMif userspace changes cpuid.nent between the initial read and the subsequent copy of the initialization data.Quoted 7 lines
arch/x86/kvm/vmx/tdx.c +0
-0@@ -2738,7 +2738,11 @@ static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd) goto out; }arch/x86/kvm/vmx/tdx.c static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd) · line 2748
Quoted 10 lines
arch/x86/kvm/vmx/tdx.c +5
-1-if (init_vm->cpuid.padding) { +/* +* Reject the request if userspace changes cpuid.nent between the +* initial read and the subsequent copy. +*/ +if (init_vm->cpuid.padding || init_vm->cpuid.nent != nr_user_entries) { ret = -EINVAL ; goto out; }[Severity: High]
This is a pre-existing issue, but I noticed a potential lock ordering problem in the initialization path called shortly after this check.In
__tdx_td_init(), the code acquires the cpu read lock:arch/x86/kvm/vmx/tdx.c:__tdx_td_init() { ... cpus_read_lock(); ... }However,
tdx_td_init()is called while the vm state locks are already held higher in the stack:arch/x86/kvm/vmx/tdx.c:tdx_acquire_vm_state_locks() { ... mutex_lock(&kvm->lock); ... }
Quoted 5 lines
Taking
cpus_read_lock()while holdingkvm->lockcreates a deadlock risk against other subsystems, like cpu hotplug, that holdcpu_hotplug_lockviacpus_write_lock()and subsequently attempt to acquirekvm->lock.Can this lead to a system-wide deadlock during cpu hotplug operations?
This is considered as a set_nx_huge_pages() problem, not a TDX problem per the discussion.
https://lore.kernel.org/kvm/[email protected]/
Quoted 24 lines
[Severity: High]
This is another pre-existing issue in the same__tdx_td_init()path.If
misc_cg_try_charge()fails, it jumps to thefree_hkidlabel:arch/x86/kvm/vmx/tdx.c:__tdx_td_init() { ... ret = misc_cg_try_charge(MISC_CG_RES_TDX, kvm_tdx->misc_cg, 1); if (ret) goto free_hkid; ... }The
free_hkidcleanup path executestdx_hkid_free(), which unconditionally decrements the configured hkid counter and uncharges the cgroup:arch/x86/kvm/vmx/tdx.c:tdx_hkid_free() { ... atomic_dec(&nr_configured_hkid); misc_cg_uncharge(MISC_CG_RES_TDX, kvm_tdx->misc_cg, 1); ... }
Quoted 3 lines
Since the initial charge failed and the code jumped past the
atomic_inc()fornr_configured_hkid, does this error path underflow the global atomic counter and corrupt the cgroup accounting?
nr_configured_hkid has been removed on kvm-x86's next branch by Sean's patches
https://lore.kernel.org/all/[email protected]/
https://lore.kernel.org/all/[email protected]/
But the issue raised for cgroup accounting is valid. Will cook a patch to fix it.
Quoted 17 lines
[Severity: High]
This isn't a bug introduced by this patch, but there is a potential out of bounds write later in this function when setting up the td params.In
setup_tdparams_cpuids(), the loop is bounded bynum_cpuid_config:arch/x86/kvm/vmx/tdx.c:setup_tdparams_cpuids() { ... for (i = 0; i < td_conf->num_cpuid_config; i++) { ... value = &td_params->cpuid_values[i]; value->eax = entry->eax; ... } }
Quoted 7 lines
The
td_paramsstructure is strictly sized to 1024 bytes, which only leaves room for 48 elements in thecpuid_valuesarray.Since
tdx_global_metadata.cpermitstd_conf->num_cpuid_configto be up to 128, will this loop write past the end of thetd_paramskernel heap allocation if the module reports a large number of cpuid configurations?
The td_params structure is fixed to 1024byte, which is defined by the TDX spec and the value of td_conf->num_cpuid_config is read from the TDX module.
So, this should not happen unless there is a bug in the TDX module. Not sure if KVM should do a preventive check.
Quoted 10 lines · Binbin Wu
Reject
KVM_TDX_INIT_VMif userspace changes cpuid.nent between the initial read and the subsequent copy of the initialization data.
tdx_td_init()first readsuser_data->cpuid.nentto size the flexiblekvm_tdx_init_vmcopy. The copied structure also contains cpuid.nent, and that field can differ from the value used to size the allocation if userspace modifies the input concurrently.setup_tdparams_cpuids()later passesinit_vm->cpuid.nenttokvm_find_cpuid_entry2(), which uses it as the array bound for the copied entries.
Quoted 7 lines
Require the copied count to match the value used to size the allocation so that CPUID parsing cannot access beyond the entries actually copied.
Fixes 0bd0a4a1428b ("KVM: TDX: Replace kmalloc + copy_from_user with memdup_user in tdx_td_init()")Reported-by Sashiko:gemini-3.1-pro-previewCc <[email protected]>Signed-off-by Binbin Wu <[email protected]>
LGTM, and thanks for fixing this!
Quoted 34 lines
---
v2:
- Reject the request if mismatch instead overwriting the value. (Thorsten, Rick)
- Lump the check into the existing sanity check on the cpuid field. (Sean)
- "KVM: x86: TDX:" -> "KVM: TDX:" in the shortlog.v1:
- https://lore.kernel.org/kvm/[email protected]/--- arch/x86/kvm/vmx/tdx.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)arch/x86/kvm/vmx/tdx.c +5
-1@@ -2797,7 +2797,11 @@ static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd) goto out; } -if (init_vm->cpuid.padding) { +/* +* Reject the request if userspace changes cpuid.nent between the +* initial read and the subsequent copy. +*/ +if (init_vm->cpuid.padding || init_vm->cpuid.nent != nr_user_entries) { ret = -EINVAL ; goto out; }base-commit:
f1e5ada5ab62dbe32350bc161771c9afc6a896deSignature · 1 line
2.46.0
Quoted 10 lines · Binbin Wu
Reject
KVM_TDX_INIT_VMif userspace changes cpuid.nent between the initial read and the subsequent copy of the initialization data.
tdx_td_init()first readsuser_data->cpuid.nentto size the flexiblekvm_tdx_init_vmcopy. The copied structure also contains cpuid.nent, and that field can differ from the value used to size the allocation if userspace modifies the input concurrently.setup_tdparams_cpuids()later passesinit_vm->cpuid.nenttokvm_find_cpuid_entry2(), which uses it as the array bound for the copied entries.
Quoted 7 lines
Require the copied count to match the value used to size the allocation so that CPUID parsing cannot access beyond the entries actually copied.
Fixes 0bd0a4a1428b ("KVM: TDX: Replace kmalloc + copy_from_user with memdup_user in tdx_td_init()")Reported-by Sashiko:gemini-3.1-pro-previewCc <[email protected]>Signed-off-by Binbin Wu <[email protected]>
Quoted 31 lines
---
v2:
- Reject the request if mismatch instead overwriting the value. (Thorsten, Rick)
- Lump the check into the existing sanity check on the cpuid field. (Sean)
- "KVM: x86: TDX:" -> "KVM: TDX:" in the shortlog.v1:
- https://lore.kernel.org/kvm/[email protected]/--- arch/x86/kvm/vmx/tdx.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)arch/x86/kvm/vmx/tdx.c +5
-1@@ -2797,7 +2797,11 @@ static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd) goto out; } -if (init_vm->cpuid.padding) { +/* +* Reject the request if userspace changes cpuid.nent between the +* initial read and the subsequent copy. +*/ +if (init_vm->cpuid.padding || init_vm->cpuid.nent != nr_user_entries) { ret = -EINVAL ; goto out; }base-commit:
f1e5ada5ab62dbe32350bc161771c9afc6a896de
Quoted 11 lines · Binbin Wu
Reject
KVM_TDX_INIT_VMif userspace changes cpuid.nent between the initial read and the subsequent copy of the initialization data.
tdx_td_init()first readsuser_data->cpuid.nentto size the flexiblekvm_tdx_init_vmcopy. The copied structure also contains cpuid.nent, and that field can differ from the value used to size the allocation if userspace modifies the input concurrently.setup_tdparams_cpuids()later passesinit_vm->cpuid.nenttokvm_find_cpuid_entry2(), which uses it as the array bound for the copied entries.[...]
Applied to kvm-x86 fixes, thanks!
[1/1] KVM: TDX: Reject concurrent change to CPUID entry count
https://github.com/kvm-x86/linux/commit/cfbebb55e512