diff --git a/src/libs/Cargo.lock b/src/libs/Cargo.lock index c7114e373f..4e36020eec 100644 --- a/src/libs/Cargo.lock +++ b/src/libs/Cargo.lock @@ -558,6 +558,7 @@ dependencies = [ "oci", "once_cell", "rand", + "safe-path", "serde_json", "serial_test", "slog", diff --git a/src/libs/kata-sys-util/Cargo.toml b/src/libs/kata-sys-util/Cargo.toml index 0d6cff91fb..0e74c8fdca 100644 --- a/src/libs/kata-sys-util/Cargo.toml +++ b/src/libs/kata-sys-util/Cargo.toml @@ -30,6 +30,7 @@ thiserror = "1.0.30" kata-types = { path = "../kata-types" } oci = { path = "../oci" } +safe-path = { path = "../safe-path" } [dev-dependencies] num_cpus = "1.13.1" diff --git a/src/libs/kata-sys-util/src/protection.rs b/src/libs/kata-sys-util/src/protection.rs index 06ebae364e..e8b16287cd 100644 --- a/src/libs/kata-sys-util/src/protection.rs +++ b/src/libs/kata-sys-util/src/protection.rs @@ -3,11 +3,14 @@ // SPDX-License-Identifier: Apache-2.0 // +#[cfg(target_arch = "x86_64")] +use anyhow::anyhow; #[cfg(any(target_arch = "s390x", target_arch = "x86_64", target_arch = "aarch64"))] use anyhow::Result; use std::fmt; #[cfg(target_arch = "x86_64")] use std::path::Path; +use std::path::PathBuf; use thiserror::Error; #[cfg(any(target_arch = "s390x", target_arch = "x86_64"))] @@ -16,12 +19,18 @@ use nix::unistd::Uid; #[cfg(target_arch = "x86_64")] use std::fs; +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct TDXDetails { + pub major_version: u32, + pub minor_version: u32, +} + #[allow(dead_code)] #[derive(Debug, Clone, PartialEq, Default)] pub enum GuestProtection { #[default] NoProtection, - Tdx, + Tdx(TDXDetails), Sev, Snp, Pef, @@ -31,7 +40,11 @@ pub enum GuestProtection { impl fmt::Display for GuestProtection { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - GuestProtection::Tdx => write!(f, "tdx"), + GuestProtection::Tdx(details) => write!( + f, + "tdx (major_version: {}, minor_version: {}", + details.major_version, details.minor_version + ), GuestProtection::Sev => write!(f, "sev"), GuestProtection::Snp => write!(f, "snp"), GuestProtection::Pef => write!(f, "pef"), @@ -52,6 +65,15 @@ pub enum ProtectionError { #[error("Invalid guest protection value: {0}")] InvalidValue(String), + + #[error("Cannot resolve path {0} below {1}: {2}")] + CannotResolvePath(String, PathBuf, anyhow::Error), + + #[error("Expected file {0} not found: {1}")] + FileMissing(String, std::io::Error), + + #[error("File {0} contains unexpected content: {1}")] + FileInvalid(PathBuf, anyhow::Error), } #[cfg(target_arch = "x86_64")] @@ -61,6 +83,18 @@ pub const SEV_KVM_PARAMETER_PATH: &str = "/sys/module/kvm_amd/parameters/sev"; #[cfg(target_arch = "x86_64")] pub const SNP_KVM_PARAMETER_PATH: &str = "/sys/module/kvm_amd/parameters/sev_snp"; +// Module directory below TDX_SYS_FIRMWARE_DIR. +#[cfg(target_arch = "x86_64")] +const TDX_FW_MODULE_DIR: &str = "tdx_module"; + +// File in TDX_FW_MODULE_DIR that specifies TDX major version number. +#[cfg(target_arch = "x86_64")] +const TDX_MAJOR_FILE: &str = "major_version"; + +// File in TDX_FW_MODULE_DIR that specifies TDX minor version number. +#[cfg(target_arch = "x86_64")] +const TDX_MINOR_FILE: &str = "minor_version"; + #[cfg(target_arch = "x86_64")] pub fn available_guest_protection() -> Result { if !Uid::effective().is_root() { @@ -83,7 +117,59 @@ pub fn arch_guest_protection( let metadata = fs::metadata(tdx_path); if metadata.is_ok() && metadata.unwrap().is_dir() { - return Ok(GuestProtection::Tdx); + let module_dir = safe_path::scoped_join(tdx_path, TDX_FW_MODULE_DIR).map_err(|e| { + ProtectionError::CannotResolvePath( + TDX_FW_MODULE_DIR.to_string(), + PathBuf::from(tdx_path), + anyhow!(e), + ) + })?; + + let major_file = + safe_path::scoped_join(module_dir.clone(), TDX_MAJOR_FILE).map_err(|e| { + ProtectionError::CannotResolvePath( + TDX_MAJOR_FILE.to_string(), + module_dir.clone(), + anyhow!(e), + ) + })?; + + let minor_file = + safe_path::scoped_join(module_dir.clone(), TDX_MINOR_FILE).map_err(|e| { + ProtectionError::CannotResolvePath( + TDX_MINOR_FILE.to_string(), + module_dir, + anyhow!(e), + ) + })?; + + const HEX_BASE: u32 = 16; + const HEX_PREFIX: &str = "0x"; + + let major_version_str = std::fs::read_to_string(major_file.clone()).map_err(|e| { + ProtectionError::FileMissing(major_file.clone().to_string_lossy().into(), e) + })?; + + let major_version_str = major_version_str.trim_start_matches(HEX_PREFIX); + + let major_version = u32::from_str_radix(&major_version_str, HEX_BASE) + .map_err(|e| ProtectionError::FileInvalid(major_file, anyhow!(e)))?; + + let minor_version_str = std::fs::read_to_string(minor_file.clone()).map_err(|e| { + ProtectionError::FileMissing(minor_file.clone().to_string_lossy().into(), e) + })?; + + let minor_version_str = minor_version_str.trim_start_matches(HEX_PREFIX); + + let minor_version = u32::from_str_radix(&minor_version_str, HEX_BASE) + .map_err(|e| ProtectionError::FileInvalid(minor_file, anyhow!(e)))?; + + let details = TDXDetails { + major_version, + minor_version, + }; + + return Ok(GuestProtection::Tdx(details)); } let check_contents = |file_name: &str| -> Result { @@ -248,7 +334,41 @@ mod tests { assert_eq!(actual.unwrap(), GuestProtection::NoProtection); let actual = arch_guest_protection(tdx_path.to_str().unwrap(), invalid_dir, invalid_dir); - assert!(actual.is_ok()); - assert_eq!(actual.unwrap(), GuestProtection::Tdx); + assert!(actual.is_err()); + + let tdx_module = tdx_path.join(TDX_FW_MODULE_DIR); + std::fs::create_dir_all(tdx_module.clone()).unwrap(); + + let major_file = tdx_module.join(TDX_MAJOR_FILE); + std::fs::File::create(&major_file).unwrap(); + + let minor_file = tdx_module.join(TDX_MINOR_FILE); + std::fs::File::create(&minor_file).unwrap(); + + let result = arch_guest_protection(tdx_path.to_str().unwrap(), invalid_dir, invalid_dir); + assert!(result.is_err()); + + std::fs::write(&major_file, b"invalid").unwrap(); + std::fs::write(&minor_file, b"invalid").unwrap(); + + let result = arch_guest_protection(tdx_path.to_str().unwrap(), invalid_dir, invalid_dir); + assert!(result.is_err()); + + // Fake a TDX 1.0 environment + std::fs::write(&major_file, b"0x00000001").unwrap(); + std::fs::write(&minor_file, b"0x00000000").unwrap(); + + let result = arch_guest_protection(tdx_path.to_str().unwrap(), invalid_dir, invalid_dir); + assert!(result.is_ok()); + + let result = result.unwrap(); + + let details = match result { + GuestProtection::Tdx(details) => details, + _ => panic!(), + }; + + assert_eq!(details.major_version, 1); + assert_eq!(details.minor_version, 0); } } diff --git a/src/runtime-rs/Cargo.lock b/src/runtime-rs/Cargo.lock index cfd3830675..45ca239bc7 100644 --- a/src/runtime-rs/Cargo.lock +++ b/src/runtime-rs/Cargo.lock @@ -1574,6 +1574,7 @@ dependencies = [ "oci", "once_cell", "rand 0.8.5", + "safe-path 0.1.0", "serde_json", "slog", "slog-scope", diff --git a/src/runtime-rs/crates/hypervisor/ch-config/src/convert.rs b/src/runtime-rs/crates/hypervisor/ch-config/src/convert.rs index 1ba8b0928f..44e833ac48 100644 --- a/src/runtime-rs/crates/hypervisor/ch-config/src/convert.rs +++ b/src/runtime-rs/crates/hypervisor/ch-config/src/convert.rs @@ -580,6 +580,7 @@ where #[cfg(test)] mod tests { use super::*; + use kata_sys_util::protection::TDXDetails; use kata_types::config::hypervisor::{ BlockDeviceInfo, Hypervisor as HypervisorConfig, SecurityInfo, }; @@ -755,6 +756,11 @@ mod tests { #[test] fn test_get_serial_cfg() { + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData { debug: bool, @@ -783,7 +789,7 @@ mod tests { }, TestData { debug: false, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Off, @@ -792,7 +798,7 @@ mod tests { }, TestData { debug: true, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Off, @@ -838,6 +844,11 @@ mod tests { #[test] fn test_get_console_cfg() { + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData { debug: bool, @@ -866,7 +877,7 @@ mod tests { }, TestData { debug: false, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Off, @@ -875,7 +886,7 @@ mod tests { }, TestData { debug: true, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Tty, @@ -919,6 +930,11 @@ mod tests { #[test] fn test_get_platform_cfg() { + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData { guest_protection: GuestProtection, @@ -931,7 +947,7 @@ mod tests { result: None, }, TestData { - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: Some(PlatformConfig { tdx: true, num_pci_segments: DEFAULT_NUM_PCI_SEGMENTS, @@ -1168,6 +1184,11 @@ mod tests { #[test] fn test_cpuinfo_to_cpusconfig() { + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData { cpu_info: CpuInfo, @@ -1258,7 +1279,7 @@ mod tests { default_maxvcpus: 13, ..Default::default() }, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: Ok(CpusConfig { boot_vcpus: 1, max_vcpus: 1, @@ -1304,6 +1325,11 @@ mod tests { #[test] fn test_bootinfo_to_payloadconfig() { + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData { boot_info: BootInfo, @@ -1409,19 +1435,19 @@ mod tests { ..Default::default() }, cmdline: None, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: Err(PayloadConfigError::TDXFirmwareMissing), }, TestData { boot_info: boot_info_with_initrd, cmdline: Some(cmdline.to_string()), - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: Ok(payload_config_with_initrd), }, TestData { boot_info: boot_info_without_initrd, cmdline: Some(cmdline.to_string()), - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: Ok(payload_config_without_initrd), }, ]; @@ -1460,6 +1486,11 @@ mod tests { #[test] fn test_memoryinfo_to_memoryconfig() { + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData { mem_info: MemoryInfo, @@ -1494,7 +1525,7 @@ mod tests { ..Default::default() }, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: Ok(MemoryConfig { size: (17 * MIB), shared: true, @@ -1509,7 +1540,7 @@ mod tests { ..Default::default() }, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: Ok(MemoryConfig { size: usable_max_mem_bytes, shared: true, @@ -1524,7 +1555,7 @@ mod tests { ..Default::default() }, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: Err(MemoryConfigError::DefaultMemSizeTooBig), }, TestData { @@ -1552,7 +1583,7 @@ mod tests { }, TestData { mem_info: mem_info_confidential_guest, - guest_protection: GuestProtection::Tdx, + guest_protection: GuestProtection::Tdx(tdx_details.clone()), result: Ok(mem_cfg_confidential_guest), }, ]; @@ -1642,6 +1673,11 @@ mod tests { #[test] fn test_named_hypervisor_config_to_vmconfig() { + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData { cfg: NamedHypervisorConfig, @@ -1813,7 +1849,7 @@ mod tests { ..Default::default() }; - let platform_config_tdx = get_platform_cfg(GuestProtection::Tdx); + let platform_config_tdx = get_platform_cfg(GuestProtection::Tdx(tdx_details.clone())); let vmconfig_tdx_image = VmConfig { cpus: cpus_config_tdx.clone(), @@ -1922,7 +1958,7 @@ mod tests { vsock_socket_path: vsock_socket_path.into(), cfg: hypervisor_cfg_tdx_image, - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), ..Default::default() }; @@ -1932,7 +1968,7 @@ mod tests { vsock_socket_path: vsock_socket_path.into(), cfg: hypervisor_cfg_tdx_initrd, - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), ..Default::default() }; @@ -2169,6 +2205,11 @@ mod tests { #[test] fn test_check_tdx_rootfs_settings() { + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData<'a> { use_image: bool, @@ -2220,7 +2261,7 @@ mod tests { use_image: true, container_rootfs_driver: "container", vm_rootfs_driver: "vm", - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Err(VmConfigError::TDXContainerRootfsNotVirtioBlk), }, // Partially correct @@ -2228,28 +2269,28 @@ mod tests { use_image: true, container_rootfs_driver: VIRTIO_BLK_PCI, vm_rootfs_driver: "vm", - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Err(VmConfigError::TDXVMRootfsNotVirtioBlk), }, TestData { use_image: true, container_rootfs_driver: VIRTIO_BLK_MMIO, vm_rootfs_driver: "vm", - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Err(VmConfigError::TDXVMRootfsNotVirtioBlk), }, TestData { use_image: true, container_rootfs_driver: "container", vm_rootfs_driver: VIRTIO_BLK_PCI, - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Err(VmConfigError::TDXContainerRootfsNotVirtioBlk), }, TestData { use_image: true, container_rootfs_driver: "container", vm_rootfs_driver: VIRTIO_BLK_MMIO, - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Err(VmConfigError::TDXContainerRootfsNotVirtioBlk), }, // Same types @@ -2257,14 +2298,14 @@ mod tests { use_image: true, container_rootfs_driver: VIRTIO_BLK_MMIO, vm_rootfs_driver: VIRTIO_BLK_MMIO, - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Ok(()), }, TestData { use_image: true, container_rootfs_driver: VIRTIO_BLK_PCI, vm_rootfs_driver: VIRTIO_BLK_PCI, - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Ok(()), }, // Alternate types @@ -2272,14 +2313,14 @@ mod tests { use_image: true, container_rootfs_driver: VIRTIO_BLK_MMIO, vm_rootfs_driver: VIRTIO_BLK_PCI, - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Ok(()), }, TestData { use_image: true, container_rootfs_driver: VIRTIO_BLK_PCI, vm_rootfs_driver: VIRTIO_BLK_MMIO, - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Ok(()), }, // Using an initrd (not currently supported) @@ -2287,28 +2328,28 @@ mod tests { use_image: false, container_rootfs_driver: VIRTIO_BLK_PCI, vm_rootfs_driver: VIRTIO_BLK_PCI, - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Err(VmConfigError::TDXDisallowsInitrd), }, TestData { use_image: false, container_rootfs_driver: "container", vm_rootfs_driver: "vm", - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Err(VmConfigError::TDXDisallowsInitrd), }, TestData { use_image: false, container_rootfs_driver: VIRTIO_BLK_PCI, vm_rootfs_driver: "vm", - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Err(VmConfigError::TDXDisallowsInitrd), }, TestData { use_image: false, container_rootfs_driver: VIRTIO_BLK_MMIO, vm_rootfs_driver: "vm", - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), result: Err(VmConfigError::TDXDisallowsInitrd), }, ]; diff --git a/src/runtime-rs/crates/hypervisor/ch-config/src/lib.rs b/src/runtime-rs/crates/hypervisor/ch-config/src/lib.rs index a352792807..4df06eca20 100644 --- a/src/runtime-rs/crates/hypervisor/ch-config/src/lib.rs +++ b/src/runtime-rs/crates/hypervisor/ch-config/src/lib.rs @@ -504,15 +504,21 @@ pub struct NamedHypervisorConfig { // Returns true if the enabled guest protection is Intel TDX. pub fn guest_protection_is_tdx(guest_protection_to_use: GuestProtection) -> bool { - matches!(guest_protection_to_use, GuestProtection::Tdx) + matches!(guest_protection_to_use, GuestProtection::Tdx(_)) } #[cfg(test)] mod tests { use super::*; + use kata_sys_util::protection::TDXDetails; #[test] fn test_guest_protection_is_tdx() { + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData { protection: GuestProtection, @@ -541,7 +547,7 @@ mod tests { result: false, }, TestData { - protection: GuestProtection::Tdx, + protection: GuestProtection::Tdx(tdx_details), result: true, }, ]; diff --git a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs index 8f961b2375..8e48040087 100644 --- a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs +++ b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs @@ -492,7 +492,7 @@ impl CloudHypervisorInner { self.guest_protection_to_use = protection.clone(); info!(sl!(), "guest protection available and requested"; "guest-protection" => protection.to_string()); - } else if protection == GuestProtection::Tdx { + } else if let GuestProtection::Tdx(_) = protection { return Err(anyhow!(GuestProtectionError::TDXProtectionMustBeUsedWithCH)); } else { info!(sl!(), "guest protection available but not requested"; "guest-protection" => protection.to_string()); @@ -724,6 +724,7 @@ fn get_guest_protection() -> Result { #[cfg(test)] mod tests { use super::*; + use kata_sys_util::protection::TDXDetails; #[cfg(target_arch = "x86_64")] use kata_sys_util::protection::TDX_SYS_FIRMWARE_DIR; @@ -748,6 +749,11 @@ mod tests { // available_guest_protection() requires super user privs. skip_if_not_root!(); + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + #[derive(Debug)] struct TestData { value: Option, @@ -776,8 +782,8 @@ mod tests { result: Ok(GuestProtection::Snp), }, TestData { - value: Some(GuestProtection::Tdx), - result: Ok(GuestProtection::Tdx), + value: Some(GuestProtection::Tdx(tdx_details.clone())), + result: Ok(GuestProtection::Tdx(tdx_details.clone())), }, ]; @@ -811,6 +817,11 @@ mod tests { // available_guest_protection() requires super user privs. skip_if_not_root!(); + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + // Use the hosts protection, not a fake one. set_fake_guest_protection(None); @@ -843,7 +854,7 @@ mod tests { } if have_tdx { - assert_eq!(protection, GuestProtection::Tdx); + assert_eq!(protection, GuestProtection::Tdx(tdx_details)); } else { assert_eq!(protection, GuestProtection::NoProtection); } @@ -866,6 +877,11 @@ mod tests { guest_protection_to_use: GuestProtection, } + let tdx_details = TDXDetails { + major_version: 1, + minor_version: 0, + }; + let tests = &[ TestData { confidential_guest: false, @@ -881,15 +897,15 @@ mod tests { }, TestData { confidential_guest: false, - available_protection: Some(GuestProtection::Tdx), + available_protection: Some(GuestProtection::Tdx(tdx_details.clone())), result: Err(anyhow!(GuestProtectionError::TDXProtectionMustBeUsedWithCH)), guest_protection_to_use: GuestProtection::NoProtection, }, TestData { confidential_guest: true, - available_protection: Some(GuestProtection::Tdx), + available_protection: Some(GuestProtection::Tdx(tdx_details.clone())), result: Ok(()), - guest_protection_to_use: GuestProtection::Tdx, + guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), }, ];