Fix EINVAL from KVM_TDX_INIT_VCPU by not filtering vCPU CPUID - #17
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
It introduces/retains a Rust FFI unsoundness around using *const for an ioctl-filled buffer (tdx_command/Capabilities path), which should be corrected before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes a TDX vCPU initialization failure (KVM_TDX_INIT_VCPU returning EINVAL) by ensuring guest-visible CPUID leaves that are not “configurable” (per KVM_TDX_CAPABILITIES) are no longer dropped from the CPUID programmed via per-vCPU KVM_SET_CPUID2, while still filtering CPUID appropriately for KVM_TDX_INIT_VM.
Changes:
- Filter only a copy of CPUID for
KVM_TDX_INIT_VM, keeping the shared/per-vCPU CPUID unfiltered for guest-visible leaves. - Improve TDX command ioctl handling by passing a mutable command struct and logging the returned
hw_erroron failure. - Adjust KVM feature masking for TDX builds by clearing additional feature bits.
File summaries
| File | Description |
|---|---|
| hypervisor/src/kvm/mod.rs | Stops filtering the shared CPUID used for per-vCPU programming; filters only the KVM_TDX_INIT_VM CPUID copy and improves TDX ioctl error reporting. |
| arch/src/x86_64/mod.rs | Removes TDX CPUID filtering during common CPUID generation and updates the set of KVM feature bits cleared for TDX. |
Review details
Suppressed comments (1)
hypervisor/src/kvm/mod.rs:1107
tdx_command()takesdata: *const c_voidand stores it incmd.data, but at leastTdxCommand::Capabilitiesexpects the kernel to write results into the pointed-to buffer (seetdx_capabilities()returning the populateddata). Passing a shared reference/*constto memory that is mutated by the kernel is unsound in Rust and can lead to undefined behavior; the API should take a mutable pointer for any command that may write intodata.
fn tdx_command(
fd: RawFd,
command: TdxCommand,
flags: u32,
data: *const libc::c_void,
) -> std::result::Result<(), std::io::Error> {
const TDX_COMMAND_TIMEOUT: Duration = Duration::from_secs(5);
let mut cmd = KvmTdxCmd {
id: command,
flags,
data: data as _,
hw_error: 0,
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
7687d01 to
7ae14e6
Compare
KVM_TDX_CAPABILITIES only reports the CPUID bits the TDX module considers "configurable". Leaves such as 0x8000_0008 (MAXPHYADDR) are guest-visible but not part of that configurable set, so running the shared guest CPUID through tdx_filter_cpuid() before handing it to the per-vCPU KVM_SET_CPUID2 ioctl silently dropped them. Without 0x8000_0008 in KVM's local CPUID cache, vcpu->arch.maxphyaddr stayed 0, which made the kernel's reserved-GPA-bits check reject the x2APIC MSR value tdx_vcpu_init() forces on the vCPU, so KVM_TDX_INIT_VCPU failed with EINVAL before the TDX module SEAMCALL was ever attempted. Stop filtering the CPUID used for KVM_SET_CPUID2 and instead filter only a copy of it when building the KVM_TDX_INIT_VM request. Signed-off-by: liangzhou121 <zhouliang121@126.com>
KVM_TDX_CAPABILITIES only reports the CPUID bits the TDX module considers "configurable". Leaves such as 0x8000_0008 (MAXPHYADDR) are guest-visible but not part of that configurable set, so running the shared guest CPUID through tdx_filter_cpuid() before handing it to the per-vCPU KVM_SET_CPUID2 ioctl silently dropped them.
Without 0x8000_0008 in KVM's local CPUID cache, vcpu->arch.maxphyaddr stayed 0, which made the kernel's reserved-GPA-bits check reject the x2APIC MSR value tdx_vcpu_init() forces on the vCPU, so KVM_TDX_INIT_VCPU failed with EINVAL before the TDX module SEAMCALL was ever attempted.
Stop filtering the CPUID used for KVM_SET_CPUID2 and instead filter only a copy of it when building the KVM_TDX_INIT_VM request.