From aac86634cd8e3ce85e521f7b63646a4847336b49 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Thu, 29 Feb 2024 08:20:30 -0500 Subject: [PATCH 1/2] Implement KVM_TDX_INIT_VM and KVM_TDX_INIT_VCPU ioctl Implement the KVM_TDX_INIT_VM and KVM_TDX_INIT_VCPU ioctls. Additionally move some device agnostic types into a shared `src/linux/` folder. Exposes the vm filedescriptor in the `TdxVm` type. Implement a conversion from `i32` to `TdxError` Signed-off-by: Jake Correnti --- Cargo.toml | 2 + src/lib.rs | 3 ++ src/linux/mod.rs | 63 +++++++++++++++++++++++ src/vcpu/linux/mod.rs | 2 + src/vcpu/mod.rs | 35 +++++++++++++ src/vm/linux/ioctl.rs | 44 +++++----------- src/vm/linux/types.rs | 54 ++++++++++++++++++++ src/vm/mod.rs | 116 +++++++++++++++++++++++++++++------------- tests/launch.rs | 8 ++- 9 files changed, 260 insertions(+), 67 deletions(-) create mode 100644 src/linux/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 18bab8d..c0ce766 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,6 @@ rust-version = "1.71" [dependencies] bitflags = "2.4.2" +kvm-bindings = "0.7.0" kvm-ioctls = "0.16.0" +vmm-sys-util = "0.12.1" diff --git a/src/lib.rs b/src/lib.rs index 0280808..9aaf65a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,3 +2,6 @@ pub mod vcpu; pub mod vm; + +#[cfg(target_os = "linux")] +pub mod linux; diff --git a/src/linux/mod.rs b/src/linux/mod.rs new file mode 100644 index 0000000..e1f3120 --- /dev/null +++ b/src/linux/mod.rs @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 + +/// Trust Domain eXtensions sub-ioctl() commands +#[repr(u32)] +pub enum CmdId { + GetCapabilities = 0, + InitVm = 1, + InitVcpu = 2, +} + +/// Contains information for the sub-ioctl() command to be run. This is +/// equivalent to `struct kvm_tdx_cmd` in the kernel. +#[derive(Default)] +#[repr(C)] +pub struct Cmd { + /// TDX command identifier + pub id: u32, + + /// Flags for sub-command. If sub-command doesn't use it, set to zero. + pub flags: u32, + + /// A u64 representing a generic pointer to the respective ioctl input. + /// This data is read differently according to the TDX ioctl identifier. + pub data: u64, + + /// Auxiliary error code. The sub-command may return TDX SEAMCALL status + /// code in addition to -Exxx. + pub error: u64, + + /// Reserved. + pub _unused: u64, +} + +#[derive(Debug)] +pub struct TdxError { + pub code: i32, + pub message: String, +} + +impl From for TdxError { + fn from(kvm_err: kvm_ioctls::Error) -> Self { + TdxError::from(kvm_err.errno()) + } +} + +impl From for TdxError { + fn from(errno: i32) -> Self { + match errno { + 7 => TdxError { + code: 7, + message: String::from("Invalid value for NR_CPUID_CONFIGS"), + }, + 25 => TdxError { + code: 25, + message: String::from("Inappropriate ioctl for device. Ensure the proper VM type is being used for the ioctl"), + }, + _ => TdxError { + code: errno, + message: format!("errno: {}", errno), + }, + } + } +} diff --git a/src/vcpu/linux/mod.rs b/src/vcpu/linux/mod.rs index cdc649a..7557906 100644 --- a/src/vcpu/linux/mod.rs +++ b/src/vcpu/linux/mod.rs @@ -1 +1,3 @@ // SPDX-License-Identifier: Apache-2.0 + +pub mod ioctl; diff --git a/src/vcpu/mod.rs b/src/vcpu/mod.rs index cdc649a..523c098 100644 --- a/src/vcpu/mod.rs +++ b/src/vcpu/mod.rs @@ -1 +1,36 @@ // SPDX-License-Identifier: Apache-2.0 + +mod linux; + +use crate::linux::{Cmd, CmdId, TdxError}; +use kvm_bindings::*; +use vmm_sys_util::*; + +vmm_sys_util::ioctl_iowr_nr!(KVM_MEMORY_ENCRYPT_OP, KVMIO, 0xba, std::os::raw::c_ulong); + +pub struct TdxVcpu { + pub fd: kvm_ioctls::VcpuFd, +} + +impl TdxVcpu { + pub fn new(vm: &crate::vm::TdxVm, id: u64) -> Result { + let vcpufd = vm.fd.create_vcpu(id)?; + Ok(Self { fd: vcpufd }) + } + + /// TDX specific VCPU initialization using a TDVF HOB address + pub fn init_vcpu(&self, hob_addr: u64) -> Result<(), TdxError> { + let mut cmd = Cmd { + id: CmdId::InitVcpu as u32, + flags: 0, + data: hob_addr as *const u64 as _, + error: 0, + _unused: 0, + }; + let ret = unsafe { ioctl::ioctl_with_mut_ptr(&self.fd, KVM_MEMORY_ENCRYPT_OP(), &mut cmd) }; + if ret < 0 { + return Err(TdxError::from(ret)); + } + Ok(()) + } +} diff --git a/src/vm/linux/ioctl.rs b/src/vm/linux/ioctl.rs index 66e6845..46e2c3d 100644 --- a/src/vm/linux/ioctl.rs +++ b/src/vm/linux/ioctl.rs @@ -1,35 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -use crate::vm::linux::types::Capabilities; - -/// Trust Domain eXtensions sub-ioctl() commands -#[repr(u32)] -pub enum CmdId { - GetCapabilities = 0, -} - -/// Contains information for the sub-ioctl() command to be run. This is -/// equivalent to `struct kvm_tdx_cmd` in the kernel. -#[derive(Default)] -#[repr(C)] -pub struct Cmd { - /// TDX command identifier - pub id: u32, - - /// Flags for sub-command. If sub-command doesn't use it, set to zero. - pub flags: u32, - - /// A u64 representing a generic pointer to the respective ioctl input. - /// This data is read differently according to the TDX ioctl identifier. - pub data: u64, - - /// Auxiliary error code. The sub-command may return TDX SEAMCALL status - /// code in addition to -Exxx. - pub error: u64, - - /// Reserved. - pub _unused: u64, -} +use crate::linux::{Cmd, CmdId}; +use crate::vm::linux::types::{Capabilities, InitVm}; impl From<&Capabilities> for Cmd { fn from(caps: &Capabilities) -> Self { @@ -42,3 +14,15 @@ impl From<&Capabilities> for Cmd { } } } + +impl From<&InitVm> for Cmd { + fn from(init_vm: &InitVm) -> Self { + Self { + id: CmdId::InitVm as u32, + flags: 0, + data: init_vm as *const InitVm as _, + error: 0, + _unused: 0, + } + } +} diff --git a/src/vm/linux/types.rs b/src/vm/linux/types.rs index 3a3d0eb..b99a784 100644 --- a/src/vm/linux/types.rs +++ b/src/vm/linux/types.rs @@ -96,3 +96,57 @@ impl Default for Capabilities { } } } + +/// TDX specific VM initialization information +#[derive(Debug)] +#[repr(C)] +pub struct InitVm { + /// attributes specifies various guest TD attributes + pub attributes: u64, + + /// mrconfigid is a software-defined ID for non-owner-defined configuration of the guest TD + /// (runtime or OS configuration) + pub mrconfigid: [u64; 6], + + /// mrowner is a software-defined ID for the guest TD’s owner + pub mrowner: [u64; 6], + + /// mrownerconfig is a software-defined ID for owner-defined configuration of the guest TD + /// (specific to the workload) + pub mrownerconfig: [u64; 6], + + /// reserved for future extensibility + reserved: [u64; 1004], + + /// direct configuration of CPUID leaves/subleaves virtualization + pub cpuid_nent: u32, + cpuid_padding: u32, + pub cpuid_entries: [kvm_bindings::kvm_cpuid_entry2; 256], +} + +impl InitVm { + pub fn new(cpuid_entries: &Vec) -> Self { + Self { + cpuid_nent: cpuid_entries.len() as u32, + cpuid_entries: cpuid_entries.as_slice().try_into().unwrap(), + ..Default::default() + } + } +} + +impl Default for InitVm { + fn default() -> Self { + Self { + // set the SEPT_VE_DISABLE bit by default to prevent an Extended Page Table + // (EPT) violation to #VE caused by guest TD access of PENDING pages + attributes: crate::vm::AttributesFlags::SEPT_VE_DISABLE.bits(), + mrconfigid: [0; 6], + mrowner: [0; 6], + mrownerconfig: [0; 6], + reserved: [0; 1004], + cpuid_nent: 0, + cpuid_padding: 0, + cpuid_entries: [Default::default(); 256], + } + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 7b6d6a1..73327db 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -2,24 +2,25 @@ mod linux; -use crate::vm::linux::{ - ioctl::Cmd, - types::{Capabilities, CpuidConfig}, -}; +use crate::linux::{Cmd, TdxError}; +use crate::vm::linux::types::{Capabilities, CpuidConfig, InitVm}; use bitflags::bitflags; use kvm_ioctls::{Kvm, VmFd}; +use std::arch::x86_64; // Defined in linux/arch/x86/include/uapi/asm/kvm.h const KVM_X86_TDX_VM: u64 = 2; /// Handle to the TDX VM file descriptor -pub struct TdxVm(VmFd); +pub struct TdxVm { + pub fd: VmFd, +} impl TdxVm { /// Create a new TDX VM with KVM - pub fn new(kvm_fd: Kvm) -> Result { + pub fn new(kvm_fd: &Kvm) -> Result { let vm_fd = kvm_fd.create_vm_with_type(KVM_X86_TDX_VM)?; - Ok(Self(vm_fd)) + Ok(Self { fd: vm_fd }) } /// Retrieve information about the Intel TDX module @@ -28,9 +29,7 @@ impl TdxVm { let mut cmd: Cmd = Cmd::from(&caps); unsafe { - if let Err(e) = self.0.encrypt_op(&mut cmd) { - return Err(TdxError::from(e)); - } + self.fd.encrypt_op(&mut cmd)?; } Ok(TdxCapabilities { @@ -46,6 +45,78 @@ impl TdxVm { cpuid_configs: Vec::from(caps.cpuid_configs), }) } + + /// Do additional VM initialization that is specific to Intel TDX + pub fn init_vm(&self, kvm_fd: &Kvm, caps: &TdxCapabilities) -> Result<(), TdxError> { + let cpuid = kvm_fd + .get_supported_cpuid(kvm_bindings::KVM_MAX_CPUID_ENTRIES) + .unwrap(); + let mut cpuid_entries: Vec = + cpuid.as_slice().iter().map(|e| (*e).into()).collect(); + + // resize to 256 entries to make sure that InitVm is 8KB + cpuid_entries.resize(256, kvm_bindings::kvm_cpuid_entry2::default()); + + // hex for Ob1100000001011111111 based on the XSAVE state-components architecture + let xcr0_mask = 0x602ff; + // hex for 0b11111110100000000 based on the XSAVE state-components architecture + let xss_mask = 0x1FD00; + + let xfam_fixed0 = caps.xfam.fixed0.bits(); + let xfam_fixed1 = caps.xfam.fixed1.bits(); + + // patch cpuid + for entry in cpuid_entries.as_mut_slice() { + // get the configurable cpuid bits (can be set to 0 or 1) reported by TDX Module from + // TdxCapabilities + for cpuid_config in &caps.cpuid_configs { + // 0xffffffff means the cpuid leaf has no subleaf + if cpuid_config.leaf == entry.function + && (cpuid_config.sub_leaf == 0xffffffff || cpuid_config.sub_leaf == entry.index) + { + entry.eax |= cpuid_config.eax; + entry.ebx |= cpuid_config.ebx; + entry.ecx |= cpuid_config.ecx; + entry.edx |= cpuid_config.edx; + } + } + + // mandatory patches for TDX based on XFAM values reported by TdxCapabilities + match entry.index { + // XSAVE features and state-components + 0xD => { + if entry.index == 0 { + // XSAVE XCR0 LO + entry.eax &= (xfam_fixed0 as u32) & (xcr0_mask as u32); + entry.eax |= (xfam_fixed1 as u32) & (xcr0_mask as u32); + // XSAVE XCR0 HI + entry.edx &= ((xfam_fixed0 & xcr0_mask) >> 32) as u32; + entry.edx |= ((xfam_fixed1 & xcr0_mask) >> 32) as u32; + } else if entry.index == 1 { + // XSAVE XCR0 LO + entry.ecx &= (xfam_fixed0 as u32) & (xss_mask as u32); + entry.ecx |= (xfam_fixed1 as u32) & (xss_mask as u32); + // XSAVE XCR0 HI + entry.edx &= ((xfam_fixed0 & xss_mask) >> 32) as u32; + entry.edx |= ((xfam_fixed1 & xss_mask) >> 32) as u32; + } + } + 0x8000_0008 => { + // host physical address bits supported + let phys_bits = unsafe { x86_64::__cpuid(0x8000_0008).eax } & 0xff; + entry.eax = (entry.eax & 0xffff_ff00) | (phys_bits as u32 & 0xff); + } + _ => (), + } + } + + let mut cmd = Cmd::from(&InitVm::new(&cpuid_entries)); + unsafe { + self.fd.encrypt_op(&mut cmd)?; + } + + Ok(()) + } } bitflags! { @@ -193,28 +264,3 @@ pub struct TdxCapabilities { pub cpuid_configs: Vec, } - -#[derive(Debug)] -pub struct TdxError { - pub code: i32, - pub message: String, -} - -impl From for TdxError { - fn from(kvm_err: kvm_ioctls::Error) -> Self { - match kvm_err.errno() { - 7 => TdxError { - code: 7, - message: String::from("Invalid value for NR_CPUID_CONFIGS"), - }, - 25 => TdxError { - code: 25, - message: String::from("Inappropriate ioctl for device. Ensure the proper VM type is being used for the ioctl"), - }, - _ => TdxError { - code: kvm_err.errno(), - message: format!("errno: {}", kvm_err.errno()), - }, - } - } -} diff --git a/tests/launch.rs b/tests/launch.rs index 972c935..f61c5da 100644 --- a/tests/launch.rs +++ b/tests/launch.rs @@ -2,11 +2,15 @@ use kvm_ioctls::Kvm; +use tdx::vcpu::TdxVcpu; use tdx::vm::TdxVm; #[test] fn launch() { let kvm_fd = Kvm::new().unwrap(); - let tdx_vm = TdxVm::new(kvm_fd).unwrap(); - let _caps = tdx_vm.get_capabilities().unwrap(); + let tdx_vm = TdxVm::new(&kvm_fd).unwrap(); + let caps = tdx_vm.get_capabilities().unwrap(); + let _ = tdx_vm.init_vm(&kvm_fd, &caps).unwrap(); + let tdx_vcpu = TdxVcpu::new(&tdx_vm, 0).unwrap(); + let _ = tdx_vcpu.init_vcpu(0).unwrap(); } From 7ad08b6cf43392d4fd33a41e54dd5d2ab602d586 Mon Sep 17 00:00:00 2001 From: Jake Correnti Date: Fri, 22 Mar 2024 13:12:20 -0400 Subject: [PATCH 2/2] Implement KVM_TDX_INIT_MEMORY_REGION and KVM_TDX_FINALIZE_VM Implements the final two ioctls for the TDX VM creation process. Signed-off-by: Jake Correnti --- src/linux/mod.rs | 2 ++ src/vm/linux/ioctl.rs | 14 ++++++++++- src/vm/linux/types.rs | 24 +++++++++++++++++++ src/vm/mod.rs | 54 +++++++++++++++++++++++++++++++++++++++++-- tests/launch.rs | 5 +++- 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/linux/mod.rs b/src/linux/mod.rs index e1f3120..a99b14f 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -6,6 +6,8 @@ pub enum CmdId { GetCapabilities = 0, InitVm = 1, InitVcpu = 2, + InitMemRegion = 3, + FinalizeVm = 4, } /// Contains information for the sub-ioctl() command to be run. This is diff --git a/src/vm/linux/ioctl.rs b/src/vm/linux/ioctl.rs index 46e2c3d..5481fa8 100644 --- a/src/vm/linux/ioctl.rs +++ b/src/vm/linux/ioctl.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::linux::{Cmd, CmdId}; -use crate::vm::linux::types::{Capabilities, InitVm}; +use crate::vm::linux::types::{Capabilities, InitMemRegion, InitVm}; impl From<&Capabilities> for Cmd { fn from(caps: &Capabilities) -> Self { @@ -26,3 +26,15 @@ impl From<&InitVm> for Cmd { } } } + +impl From<&InitMemRegion> for Cmd { + fn from(init_mem_region: &InitMemRegion) -> Self { + Self { + id: CmdId::InitMemRegion as u32, + flags: 0, + data: init_mem_region as *const InitMemRegion as _, + error: 0, + _unused: 0, + } + } +} diff --git a/src/vm/linux/types.rs b/src/vm/linux/types.rs index b99a784..30129f5 100644 --- a/src/vm/linux/types.rs +++ b/src/vm/linux/types.rs @@ -150,3 +150,27 @@ impl Default for InitVm { } } } + +/// Information to encrypt a contiguous memory region +#[derive(Debug)] +#[repr(C)] +pub struct InitMemRegion { + /// private page image + pub source_addr: u64, + + /// guest address to map the private page image to + pub gpa: u64, + + /// number of 4KB private pages + pub nr_pages: u64, +} + +impl From<&crate::vm::TdxInitMemRegion> for InitMemRegion { + fn from(value: &crate::vm::TdxInitMemRegion) -> Self { + Self { + source_addr: value.host_address, + gpa: value.guest_address, + nr_pages: value.nr_pages, + } + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 73327db..d13793e 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -2,8 +2,8 @@ mod linux; -use crate::linux::{Cmd, TdxError}; -use crate::vm::linux::types::{Capabilities, CpuidConfig, InitVm}; +use crate::linux::{Cmd, CmdId, TdxError}; +use crate::vm::linux::types::{Capabilities, CpuidConfig, InitMemRegion, InitVm}; use bitflags::bitflags; use kvm_ioctls::{Kvm, VmFd}; use std::arch::x86_64; @@ -117,6 +117,33 @@ impl TdxVm { Ok(()) } + + /// Add a 4KB private page to a TD, mapped to the specified guest address, + /// filled with the given page image at the host address. If `measure_memory_regions` + /// is `true`, also updates the TD measurement with the page properties. + pub fn init_mem_region( + &self, + measure_memory_regions: bool, + init_mem_region: &TdxInitMemRegion, + ) -> Result<(), TdxError> { + let init_mem_region = &InitMemRegion::from(init_mem_region); + let mut cmd = Cmd::from(init_mem_region); + cmd.flags = measure_memory_regions as u32; + unsafe { + self.fd.encrypt_op(&mut cmd)?; + } + Ok(()) + } + + /// Complete measurement of the initial TD contents and mark it ready to run + pub fn finalize_vm(&self) -> Result<(), TdxError> { + let mut cmd = Cmd::default(); + cmd.id = CmdId::FinalizeVm as u32; + unsafe { + self.fd.encrypt_op(&mut cmd)?; + } + Ok(()) + } } bitflags! { @@ -264,3 +291,26 @@ pub struct TdxCapabilities { pub cpuid_configs: Vec, } + +/// Information to encrypt a contiguous memory region +#[derive(Debug)] +pub struct TdxInitMemRegion { + /// private page image + pub host_address: u64, + + /// guest address to map the private page image to + pub guest_address: u64, + + /// number of 4KB private pages + pub nr_pages: u64, +} + +impl TdxInitMemRegion { + pub fn new(host_address: u64, guest_address: u64, nr_pages: u64) -> Self { + Self { + host_address, + guest_address, + nr_pages, + } + } +} diff --git a/tests/launch.rs b/tests/launch.rs index f61c5da..2b71e81 100644 --- a/tests/launch.rs +++ b/tests/launch.rs @@ -3,7 +3,7 @@ use kvm_ioctls::Kvm; use tdx::vcpu::TdxVcpu; -use tdx::vm::TdxVm; +use tdx::vm::{TdxInitMemRegion, TdxVm}; #[test] fn launch() { @@ -13,4 +13,7 @@ fn launch() { let _ = tdx_vm.init_vm(&kvm_fd, &caps).unwrap(); let tdx_vcpu = TdxVcpu::new(&tdx_vm, 0).unwrap(); let _ = tdx_vcpu.init_vcpu(0).unwrap(); + let init_mem_region = TdxInitMemRegion::new(0, 0, 0); + let _ = tdx_vm.init_mem_region(true, &init_mem_region).unwrap(); + let _ = tdx_vm.finalize_vm().unwrap(); }