diff --git a/src/libs/Cargo.lock b/src/libs/Cargo.lock index 0c63e3b217..c7114e373f 100644 --- a/src/libs/Cargo.lock +++ b/src/libs/Cargo.lock @@ -172,6 +172,40 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "derive-new" version = "0.5.9" @@ -448,6 +482,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "1.8.1" @@ -543,6 +583,7 @@ dependencies = [ "regex", "safe-path", "serde", + "serde-enum-str", "serde_json", "slog", "slog-scope", @@ -1072,6 +1113,36 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-attributes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eb8ec7724e4e524b2492b510e66957fe1a2c76c26a6975ec80823f2439da685" +dependencies = [ + "darling_core", + "serde-rename-rule", + "syn", +] + +[[package]] +name = "serde-enum-str" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26416dc95fcd46b0e4b12a3758043a229a6914050aaec2e8191949753ed4e9aa" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde-attributes", + "syn", +] + +[[package]] +name = "serde-rename-rule" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794e44574226fc701e3be5c651feb7939038fc67fb73f6f4dd5c4ba90fd3be70" + [[package]] name = "serde_derive" version = "1.0.136" diff --git a/src/libs/kata-sys-util/src/protection.rs b/src/libs/kata-sys-util/src/protection.rs index 75e6dbf1d4..06ebae364e 100644 --- a/src/libs/kata-sys-util/src/protection.rs +++ b/src/libs/kata-sys-util/src/protection.rs @@ -17,8 +17,9 @@ use nix::unistd::Uid; use std::fs; #[allow(dead_code)] -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub enum GuestProtection { + #[default] NoProtection, Tdx, Sev, diff --git a/src/libs/kata-types/src/config/hypervisor/mod.rs b/src/libs/kata-types/src/config/hypervisor/mod.rs index 19fb83d61a..a7a5c94bd7 100644 --- a/src/libs/kata-types/src/config/hypervisor/mod.rs +++ b/src/libs/kata-types/src/config/hypervisor/mod.rs @@ -44,8 +44,12 @@ pub use self::qemu::{QemuConfig, HYPERVISOR_NAME_QEMU}; mod ch; pub use self::ch::{CloudHypervisorConfig, HYPERVISOR_NAME_CH}; -const VIRTIO_BLK_PCI: &str = "virtio-blk-pci"; -const VIRTIO_BLK_MMIO: &str = "virtio-blk-mmio"; +/// Virtual PCI block device driver. +pub const VIRTIO_BLK_PCI: &str = "virtio-blk-pci"; + +/// Virtual MMIO block device driver. +pub const VIRTIO_BLK_MMIO: &str = "virtio-blk-mmio"; + const VIRTIO_BLK_CCW: &str = "virtio-blk-ccw"; const VIRTIO_SCSI: &str = "virtio-scsi"; const VIRTIO_PMEM: &str = "virtio-pmem"; diff --git a/src/runtime-rs/Cargo.lock b/src/runtime-rs/Cargo.lock index 64c63efa11..cfd3830675 100644 --- a/src/runtime-rs/Cargo.lock +++ b/src/runtime-rs/Cargo.lock @@ -454,6 +454,7 @@ version = "0.1.0" dependencies = [ "anyhow", "api_client", + "kata-sys-util", "kata-types", "nix 0.26.2", "serde", @@ -1402,6 +1403,7 @@ dependencies = [ "dragonball", "futures 0.3.28", "go-flag", + "hypervisor", "kata-sys-util", "kata-types", "lazy_static", @@ -1416,9 +1418,11 @@ dependencies = [ "seccompiler", "serde", "serde_json", + "serial_test 2.0.0", "shim-interface", "slog", "slog-scope", + "test-utils", "tests_utils", "thiserror", "tokio", @@ -3049,7 +3053,21 @@ checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d" dependencies = [ "lazy_static", "parking_lot 0.11.2", - "serial_test_derive", + "serial_test_derive 0.5.1", +] + +[[package]] +name = "serial_test" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +dependencies = [ + "dashmap", + "futures 0.3.28", + "lazy_static", + "log", + "parking_lot 0.12.1", + "serial_test_derive 2.0.0", ] [[package]] @@ -3063,6 +3081,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serial_test_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", +] + [[package]] name = "service" version = "0.1.0" @@ -3144,7 +3173,7 @@ dependencies = [ "protobuf 3.2.0", "rand 0.8.5", "runtimes", - "serial_test", + "serial_test 0.5.1", "service", "sha2 0.9.3", "slog", diff --git a/src/runtime-rs/crates/hypervisor/Cargo.toml b/src/runtime-rs/crates/hypervisor/Cargo.toml index f8e5497f5d..27c68a321b 100644 --- a/src/runtime-rs/crates/hypervisor/Cargo.toml +++ b/src/runtime-rs/crates/hypervisor/Cargo.toml @@ -23,7 +23,7 @@ serde_json = ">=1.0.9" slog = "2.5.2" slog-scope = "4.4.0" thiserror = "1.0" -tokio = { version = "1.28.1", features = ["sync", "fs"] } +tokio = { version = "1.28.1", features = ["sync", "fs", "process", "io-util"] } vmm-sys-util = "0.11.0" rand = "0.8.4" path-clean = "1.0.1" @@ -50,3 +50,12 @@ default = [] # Feature is not yet complete, so not enabled by default. # See https://github.com/kata-containers/kata-containers/issues/6264. cloud-hypervisor = ["ch-config"] + +[dev-dependencies] +# Force the CH tests to run, even when the feature is not enabled for +# a normal build. +hypervisor = { path = ".", features = ["cloud-hypervisor"] } + +test-utils = { path = "../../../libs/test-utils" } + +serial_test = "2.0.0" diff --git a/src/runtime-rs/crates/hypervisor/ch-config/Cargo.toml b/src/runtime-rs/crates/hypervisor/ch-config/Cargo.toml index 0e0b45e594..85014294fa 100644 --- a/src/runtime-rs/crates/hypervisor/ch-config/Cargo.toml +++ b/src/runtime-rs/crates/hypervisor/ch-config/Cargo.toml @@ -22,5 +22,6 @@ tokio = { version = "1.28.1", features = ["sync", "rt"] } api_client = { git = "https://github.com/cloud-hypervisor/cloud-hypervisor", crate = "api_client", tag = "v27.0" } kata-types = { path = "../../../../libs/kata-types"} +kata-sys-util = { path = "../../../../libs/kata-sys-util"} nix = "0.26.2" thiserror = "1.0.38" 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 314dcbe929..1ba8b0928f 100644 --- a/src/runtime-rs/crates/hypervisor/ch-config/src/convert.rs +++ b/src/runtime-rs/crates/hypervisor/ch-config/src/convert.rs @@ -6,12 +6,17 @@ use crate::net_util::MAC_ADDR_LEN; use crate::NamedHypervisorConfig; use crate::VmConfig; use crate::{ - ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpuTopology, CpusConfig, DiskConfig, MacAddr, - MemoryConfig, PayloadConfig, PlatformConfig, PmemConfig, RngConfig, VsockConfig, + guest_protection_is_tdx, ConsoleConfig, ConsoleOutputMode, CpuFeatures, CpuTopology, + CpusConfig, DiskConfig, MacAddr, MemoryConfig, PayloadConfig, PlatformConfig, PmemConfig, + RngConfig, VsockConfig, }; use anyhow::{anyhow, Context, Result}; +use kata_sys_util::protection::GuestProtection; use kata_types::config::default::DEFAULT_CH_ENTROPY_SOURCE; -use kata_types::config::hypervisor::{CpuInfo, MachineInfo, MemoryInfo}; +use kata_types::config::hypervisor::Hypervisor as HypervisorConfig; +use kata_types::config::hypervisor::{ + CpuInfo, MachineInfo, MemoryInfo, VIRTIO_BLK_MMIO, VIRTIO_BLK_PCI, +}; use kata_types::config::BootInfo; use std::convert::TryFrom; use std::fmt::Display; @@ -33,9 +38,54 @@ pub const DEFAULT_NUM_PCI_SEGMENTS: u16 = 1; pub const DEFAULT_DISK_QUEUES: usize = 1; pub const DEFAULT_DISK_QUEUE_SIZE: u16 = 128; +// TDX requires all rootfs's be mounted using a block device. This test +// ensures that the user has a correct set of values for the following Kata +// Containers configuration "hypervisor" section variables: +// +// - block_device_driver= +// - vm_rootfs_driver= +// +fn check_tdx_rootfs_settings( + cfg: &HypervisorConfig, + guest_protection_to_use: &GuestProtection, +) -> Result<(), VmConfigError> { + if guest_protection_is_tdx(guest_protection_to_use.clone()) { + let block_drivers = [VIRTIO_BLK_MMIO, VIRTIO_BLK_PCI]; + + let using_image = !cfg.boot_info.image.is_empty(); + + if !using_image { + return Err(VmConfigError::TDXDisallowsInitrd); + } + + // Check the hypervisor rootfs configuration variables + // for validity. + let block_device_driver = cfg.blockdev_info.block_device_driver.clone(); + let vm_rootfs_driver = cfg.boot_info.vm_rootfs_driver.clone(); + + if !block_drivers.contains(&block_device_driver.as_str()) { + return Err(VmConfigError::TDXContainerRootfsNotVirtioBlk); + } + + // It doesn't matter what the VM rootfs driver is when using an initrd + // as this is not passed as a block device (it's handled with a + // PayloadConfig). + if using_image && !block_drivers.contains(&vm_rootfs_driver.as_str()) { + return Err(VmConfigError::TDXVMRootfsNotVirtioBlk); + } + } + + Ok(()) +} + impl TryFrom for VmConfig { type Error = VmConfigError; + // XXX: Note that this function assumes that if + // NamedHypervisorConfig.guest_protection_to_use is set, that a protected guest + // should be created. In other words, the check to ensure that suitable + // hardware guest protection is available should already have been + // confirmed at this point! fn try_from(n: NamedHypervisorConfig) -> Result { let kernel_params = if n.kernel_params.is_empty() { None @@ -46,9 +96,10 @@ impl TryFrom for VmConfig { let cfg = n.cfg; let debug = cfg.debug_info.enable_debug; - let confidential_guest = cfg.security_info.confidential_guest; - let tdx_enabled = n.tdx_enabled; + let guest_protection_to_use = n.guest_protection_to_use; + + check_tdx_rootfs_settings(&cfg, &guest_protection_to_use)?; let vsock_socket_path = if n.vsock_socket_path.is_empty() { return Err(VmConfigError::EmptyVsockSocketPath); @@ -65,7 +116,8 @@ impl TryFrom for VmConfig { let fs = n.shared_fs_devices; let net = n.network_devices; - let cpus = CpusConfig::try_from(cfg.cpu_info).map_err(VmConfigError::CPUError)?; + let cpus = CpusConfig::try_from((cfg.cpu_info, guest_protection_to_use.clone())) + .map_err(VmConfigError::CPUError)?; let rng = RngConfig::from(cfg.machine_info); @@ -93,7 +145,7 @@ impl TryFrom for VmConfig { return Err(VmConfigError::NoBootFile); } - let pmem = if use_initrd || confidential_guest { + let pmem = if use_initrd || guest_protection_is_tdx(guest_protection_to_use.clone()) { None } else { let pmem = PmemConfig::try_from(&boot_info).map_err(VmConfigError::PmemError)?; @@ -102,22 +154,28 @@ impl TryFrom for VmConfig { }; let payload = Some( - PayloadConfig::try_from((boot_info.clone(), kernel_params, tdx_enabled)) - .map_err(VmConfigError::PayloadError)?, + PayloadConfig::try_from(( + boot_info.clone(), + kernel_params, + guest_protection_to_use.clone(), + )) + .map_err(VmConfigError::PayloadError)?, ); - let disks = if confidential_guest && use_image { + let mut disks: Vec = vec![]; + + if use_image && guest_protection_is_tdx(guest_protection_to_use.clone()) { let disk = DiskConfig::try_from(boot_info).map_err(VmConfigError::DiskError)?; - Some(vec![disk]) - } else { - None + disks.push(disk); }; - let serial = get_serial_cfg(debug, confidential_guest); - let console = get_console_cfg(debug, confidential_guest); + let disks = if !disks.is_empty() { Some(disks) } else { None }; - let memory = MemoryConfig::try_from((cfg.memory_info, confidential_guest)) + let serial = get_serial_cfg(debug, guest_protection_to_use.clone()); + let console = get_console_cfg(debug, guest_protection_to_use.clone()); + + let memory = MemoryConfig::try_from((cfg.memory_info, guest_protection_to_use.clone())) .map_err(VmConfigError::MemoryError)?; std::fs::create_dir_all(sandbox_path.clone()) @@ -126,7 +184,7 @@ impl TryFrom for VmConfig { let vsock = VsockConfig::try_from((vsock_socket_path, DEFAULT_VSOCK_CID)) .map_err(VmConfigError::VsockError)?; - let platform = get_platform_cfg(tdx_enabled); + let platform = get_platform_cfg(guest_protection_to_use); let cfg = VmConfig { cpus, @@ -173,12 +231,12 @@ impl TryFrom<(String, u64)> for VsockConfig { } } -impl TryFrom<(MemoryInfo, bool)> for MemoryConfig { +impl TryFrom<(MemoryInfo, GuestProtection)> for MemoryConfig { type Error = MemoryConfigError; - fn try_from(args: (MemoryInfo, bool)) -> Result { + fn try_from(args: (MemoryInfo, GuestProtection)) -> Result { let mem = args.0; - let confidential_guest = args.1; + let guest_protection_to_use = args.1; if mem.default_memory == 0 { return Err(MemoryConfigError::NoDefaultMemory); @@ -197,7 +255,7 @@ impl TryFrom<(MemoryInfo, bool)> for MemoryConfig { return Err(MemoryConfigError::DefaultMemSizeTooBig); } - let hotplug_size = if confidential_guest { + let hotplug_size = if guest_protection_is_tdx(guest_protection_to_use) { None } else { // The amount of memory that can be hot-plugged is the total less the @@ -244,16 +302,44 @@ fn checked_next_multiple_of(value: u64, multiple: u64) -> Option { } } -impl TryFrom for CpusConfig { +impl TryFrom<(CpuInfo, GuestProtection)> for CpusConfig { type Error = CpusConfigError; - fn try_from(cpu: CpuInfo) -> Result { - let boot_vcpus = + fn try_from(args: (CpuInfo, GuestProtection)) -> Result { + let cpu = args.0; + + let guest_protection_to_use = args.1; + + // This can only happen if runtime-rs fails to set default values. + if cpu.default_vcpus <= 0 { + return Err(CpusConfigError::BootVCPUsTooSmall); + } + + let default_vcpus = u8::try_from(cpu.default_vcpus).map_err(CpusConfigError::BootVCPUsTooBig)?; - let max_vcpus = + // This can only happen if runtime-rs fails to set default values. + if cpu.default_maxvcpus == 0 { + return Err(CpusConfigError::MaxVCPUsTooSmall); + } + + let default_max_vcpus = u8::try_from(cpu.default_maxvcpus).map_err(CpusConfigError::MaxVCPUsTooBig)?; + let boot_vcpus = default_vcpus; + + let max_vcpus = if guest_protection_is_tdx(guest_protection_to_use.clone()) { + // Hotplug is not available with TDX so limit to number of boot + // cpus. + default_vcpus + } else { + default_max_vcpus + }; + + if boot_vcpus > max_vcpus { + return Err(CpusConfigError::BootVPUsGtThanMaxVCPUs); + } + let topology = CpuTopology { cores_per_die: max_vcpus, threads_per_core: 1, @@ -302,13 +388,13 @@ impl From for CpuFeatures { // // - The 3rd tuple element determines if TDX is enabled. // -impl TryFrom<(BootInfo, Option, bool)> for PayloadConfig { +impl TryFrom<(BootInfo, Option, GuestProtection)> for PayloadConfig { type Error = PayloadConfigError; - fn try_from(args: (BootInfo, Option, bool)) -> Result { + fn try_from(args: (BootInfo, Option, GuestProtection)) -> Result { let boot_info = args.0; let cmdline = args.1; - let tdx_enabled = args.2; + let guest_protection_to_use = args.2; // The kernel is always specified here, // not in the top level VmConfig.kernel. @@ -324,7 +410,7 @@ impl TryFrom<(BootInfo, Option, bool)> for PayloadConfig { Some(PathBuf::from(boot_info.initrd)) }; - let firmware = if tdx_enabled { + let firmware = if guest_protection_is_tdx(guest_protection_to_use) { if boot_info.firmware.is_empty() { return Err(PayloadConfigError::TDXFirmwareMissing); } else { @@ -407,8 +493,8 @@ impl TryFrom<&BootInfo> for PmemConfig { } } -fn get_serial_cfg(debug: bool, confidential_guest: bool) -> ConsoleConfig { - let mode = if confidential_guest { +fn get_serial_cfg(debug: bool, guest_protection_to_use: GuestProtection) -> ConsoleConfig { + let mode = if guest_protection_is_tdx(guest_protection_to_use) { ConsoleOutputMode::Off } else if debug { ConsoleOutputMode::Tty @@ -423,8 +509,8 @@ fn get_serial_cfg(debug: bool, confidential_guest: bool) -> ConsoleConfig { } } -fn get_console_cfg(debug: bool, confidential_guest: bool) -> ConsoleConfig { - let mode = if confidential_guest { +fn get_console_cfg(debug: bool, guest_protection_to_use: GuestProtection) -> ConsoleConfig { + let mode = if guest_protection_is_tdx(guest_protection_to_use) { if debug { ConsoleOutputMode::Tty } else { @@ -441,8 +527,8 @@ fn get_console_cfg(debug: bool, confidential_guest: bool) -> ConsoleConfig { } } -fn get_platform_cfg(tdx_enabled: bool) -> Option { - if tdx_enabled { +fn get_platform_cfg(guest_protection_to_use: GuestProtection) -> Option { + if guest_protection_is_tdx(guest_protection_to_use) { let platform = PlatformConfig { tdx: true, num_pci_segments: DEFAULT_NUM_PCI_SEGMENTS, @@ -494,7 +580,9 @@ where #[cfg(test)] mod tests { use super::*; - use kata_types::config::hypervisor::{Hypervisor as HypervisorConfig, SecurityInfo}; + use kata_types::config::hypervisor::{ + BlockDeviceInfo, Hypervisor as HypervisorConfig, SecurityInfo, + }; // Generate a valid generic memory info object and a valid CH specific // memory config object. @@ -541,19 +629,31 @@ mod tests { } } - fn make_cpu_objects(cpu_default: u8, cpu_max: u8) -> (CpuInfo, CpusConfig) { + fn make_cpu_objects(cpu_default: u8, cpu_max: u8, tdx: bool) -> (CpuInfo, CpusConfig) { + let default_maxvcpus = if tdx { + cpu_default as u32 + } else { + cpu_max as u32 + }; + let cpu_info = CpuInfo { default_vcpus: cpu_default as i32, - default_maxvcpus: cpu_max as u32, + default_maxvcpus, ..Default::default() }; + let max_vcpus = if tdx { + cpu_default + } else { + default_maxvcpus as u8 + }; + let cpus_config = CpusConfig { boot_vcpus: cpu_default, - max_vcpus: cpu_max, + max_vcpus, topology: Some(CpuTopology { - cores_per_die: cpu_max, + cores_per_die: max_vcpus, ..make_bare_topology() }), @@ -658,14 +758,14 @@ mod tests { #[derive(Debug)] struct TestData { debug: bool, - confidential_guest: bool, + guest_protection: GuestProtection, result: ConsoleConfig, } let tests = &[ TestData { debug: false, - confidential_guest: false, + guest_protection: GuestProtection::NoProtection, result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Off, @@ -674,7 +774,7 @@ mod tests { }, TestData { debug: true, - confidential_guest: false, + guest_protection: GuestProtection::NoProtection, result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Tty, @@ -683,7 +783,7 @@ mod tests { }, TestData { debug: false, - confidential_guest: true, + guest_protection: GuestProtection::Tdx, result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Off, @@ -692,19 +792,37 @@ mod tests { }, TestData { debug: true, - confidential_guest: true, + guest_protection: GuestProtection::Tdx, result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Off, iommu: false, }, }, + TestData { + debug: false, + guest_protection: GuestProtection::Pef, + result: ConsoleConfig { + file: None, + mode: ConsoleOutputMode::Off, + iommu: false, + }, + }, + TestData { + debug: true, + guest_protection: GuestProtection::Pef, + result: ConsoleConfig { + file: None, + mode: ConsoleOutputMode::Tty, + iommu: false, + }, + }, ]; for (i, d) in tests.iter().enumerate() { let msg = format!("test[{}]: {:?}", i, d); - let result = get_serial_cfg(d.debug, d.confidential_guest); + let result = get_serial_cfg(d.debug, d.guest_protection.clone()); let msg = format!("{}: actual result: {:?}", msg, result); @@ -723,14 +841,14 @@ mod tests { #[derive(Debug)] struct TestData { debug: bool, - confidential_guest: bool, + guest_protection: GuestProtection, result: ConsoleConfig, } let tests = &[ TestData { debug: false, - confidential_guest: false, + guest_protection: GuestProtection::NoProtection, result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Off, @@ -739,7 +857,7 @@ mod tests { }, TestData { debug: true, - confidential_guest: false, + guest_protection: GuestProtection::NoProtection, result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Off, @@ -748,7 +866,7 @@ mod tests { }, TestData { debug: false, - confidential_guest: true, + guest_protection: GuestProtection::Tdx, result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Off, @@ -757,19 +875,37 @@ mod tests { }, TestData { debug: true, - confidential_guest: true, + guest_protection: GuestProtection::Tdx, result: ConsoleConfig { file: None, mode: ConsoleOutputMode::Tty, iommu: false, }, }, + TestData { + debug: false, + guest_protection: GuestProtection::Pef, + result: ConsoleConfig { + file: None, + mode: ConsoleOutputMode::Off, + iommu: false, + }, + }, + TestData { + debug: true, + guest_protection: GuestProtection::Pef, + result: ConsoleConfig { + file: None, + mode: ConsoleOutputMode::Off, + iommu: false, + }, + }, ]; for (i, d) in tests.iter().enumerate() { let msg = format!("test[{}]: {:?}", i, d); - let result = get_console_cfg(d.debug, d.confidential_guest); + let result = get_console_cfg(d.debug, d.guest_protection.clone()); let msg = format!("{}: actual result: {:?}", msg, result); @@ -785,17 +921,17 @@ mod tests { fn test_get_platform_cfg() { #[derive(Debug)] struct TestData { - tdx_enabled: bool, + guest_protection: GuestProtection, result: Option, } let tests = &[ TestData { - tdx_enabled: false, + guest_protection: GuestProtection::NoProtection, result: None, }, TestData { - tdx_enabled: true, + guest_protection: GuestProtection::Tdx, result: Some(PlatformConfig { tdx: true, num_pci_segments: DEFAULT_NUM_PCI_SEGMENTS, @@ -803,12 +939,16 @@ mod tests { ..Default::default() }), }, + TestData { + guest_protection: GuestProtection::Pef, + result: None, + }, ]; for (i, d) in tests.iter().enumerate() { let msg = format!("test[{}]: {:?}", i, d); - let result = get_platform_cfg(d.tdx_enabled); + let result = get_platform_cfg(d.guest_protection.clone()); let msg = format!("{}: actual result: {:?}", msg, result); @@ -1031,23 +1171,59 @@ mod tests { #[derive(Debug)] struct TestData { cpu_info: CpuInfo, + guest_protection: GuestProtection, result: Result, } let topology = make_bare_topology(); - let u8_max = std::u8::MAX; - - let (cpu_info, cpus_config) = make_cpu_objects(7, u8_max); - let tests = &[ TestData { cpu_info: CpuInfo::default(), + guest_protection: GuestProtection::NoProtection, + result: Err(CpusConfigError::BootVCPUsTooSmall), + }, + TestData { + cpu_info: CpuInfo { + default_vcpus: -1, + + ..Default::default() + }, + guest_protection: GuestProtection::NoProtection, + result: Err(CpusConfigError::BootVCPUsTooSmall), + }, + TestData { + cpu_info: CpuInfo { + default_vcpus: 1, + default_maxvcpus: 0, + + ..Default::default() + }, + guest_protection: GuestProtection::NoProtection, + result: Err(CpusConfigError::MaxVCPUsTooSmall), + }, + TestData { + cpu_info: CpuInfo { + default_vcpus: 9, + default_maxvcpus: 7, + + ..Default::default() + }, + guest_protection: GuestProtection::NoProtection, + result: Err(CpusConfigError::BootVPUsGtThanMaxVCPUs), + }, + TestData { + cpu_info: CpuInfo { + default_vcpus: 1, + default_maxvcpus: 1, + ..Default::default() + }, + guest_protection: GuestProtection::NoProtection, result: Ok(CpusConfig { - boot_vcpus: 0, - max_vcpus: 0, + boot_vcpus: 1, + max_vcpus: 1, topology: Some(CpuTopology { - cores_per_die: 0, + cores_per_die: 1, ..topology }), @@ -1058,51 +1234,16 @@ mod tests { }, TestData { cpu_info: CpuInfo { - default_vcpus: u8_max as i32, - + default_vcpus: 1, + default_maxvcpus: 3, ..Default::default() }, + guest_protection: GuestProtection::NoProtection, result: Ok(CpusConfig { - boot_vcpus: u8_max, - max_vcpus: 0, - topology: Some(topology.clone()), - max_phys_bits: DEFAULT_CH_MAX_PHYS_BITS, - - ..Default::default() - }), - }, - TestData { - cpu_info: CpuInfo { - default_vcpus: u8_max as i32 + 1, - - ..Default::default() - }, - result: Err(CpusConfigError::BootVCPUsTooBig( - u8::try_from(u8_max as i32 + 1).unwrap_err(), - )), - }, - TestData { - cpu_info: CpuInfo { - default_maxvcpus: u8_max as u32 + 1, - - ..Default::default() - }, - result: Err(CpusConfigError::MaxVCPUsTooBig( - u8::try_from(u8_max as u32 + 1).unwrap_err(), - )), - }, - TestData { - cpu_info: CpuInfo { - default_vcpus: u8_max as i32, - default_maxvcpus: u8_max as u32, - - ..Default::default() - }, - result: Ok(CpusConfig { - boot_vcpus: u8_max, - max_vcpus: u8_max, + boot_vcpus: 1, + max_vcpus: 3, topology: Some(CpuTopology { - cores_per_die: u8_max, + cores_per_die: 3, ..topology }), @@ -1113,16 +1254,16 @@ mod tests { }, TestData { cpu_info: CpuInfo { - default_vcpus: (u8_max - 1) as i32, - default_maxvcpus: u8_max as u32, - + default_vcpus: 1, + default_maxvcpus: 13, ..Default::default() }, + guest_protection: GuestProtection::Tdx, result: Ok(CpusConfig { - boot_vcpus: (u8_max - 1), - max_vcpus: u8_max, + boot_vcpus: 1, + max_vcpus: 1, topology: Some(CpuTopology { - cores_per_die: u8_max, + cores_per_die: 1, ..topology }), @@ -1131,16 +1272,12 @@ mod tests { ..Default::default() }), }, - TestData { - cpu_info, - result: Ok(cpus_config), - }, ]; for (i, d) in tests.iter().enumerate() { let msg = format!("test[{}]: {:?}", i, d); - let result = CpusConfig::try_from(d.cpu_info.clone()); + let result = CpusConfig::try_from((d.cpu_info.clone(), d.guest_protection.clone())); let msg = format!("{}: actual result: {:?}", msg, result); @@ -1171,7 +1308,7 @@ mod tests { struct TestData { boot_info: BootInfo, cmdline: Option, - tdx: bool, + guest_protection: GuestProtection, result: Result, } @@ -1207,7 +1344,7 @@ mod tests { TestData { boot_info: BootInfo::default(), cmdline: None, - tdx: false, + guest_protection: GuestProtection::NoProtection, result: Err(PayloadConfigError::NoKernel), }, TestData { @@ -1219,7 +1356,7 @@ mod tests { ..Default::default() }, cmdline: None, - tdx: false, + guest_protection: GuestProtection::NoProtection, result: Ok(PayloadConfig { kernel: Some(PathBuf::from(kernel)), cmdline: None, @@ -1228,6 +1365,24 @@ mod tests { ..Default::default() }), }, + TestData { + boot_info: BootInfo { + kernel: kernel.into(), + kernel_params: String::new(), + initrd: initramfs.into(), + firmware: firmware.into(), + + ..Default::default() + }, + cmdline: None, + guest_protection: GuestProtection::NoProtection, + result: Ok(PayloadConfig { + kernel: Some(PathBuf::from(kernel)), + cmdline: None, + initramfs: Some(PathBuf::from(initramfs)), + firmware: Some(PathBuf::from(firmware)), + }), + }, TestData { boot_info: BootInfo { kernel: kernel.into(), @@ -1237,7 +1392,7 @@ mod tests { ..Default::default() }, cmdline: Some(cmdline.to_string()), - tdx: false, + guest_protection: GuestProtection::NoProtection, result: Ok(PayloadConfig { kernel: Some(PathBuf::from(kernel)), initramfs: Some(PathBuf::from(initramfs)), @@ -1254,19 +1409,19 @@ mod tests { ..Default::default() }, cmdline: None, - tdx: true, + guest_protection: GuestProtection::Tdx, result: Err(PayloadConfigError::TDXFirmwareMissing), }, TestData { boot_info: boot_info_with_initrd, cmdline: Some(cmdline.to_string()), - tdx: true, + guest_protection: GuestProtection::Tdx, result: Ok(payload_config_with_initrd), }, TestData { boot_info: boot_info_without_initrd, cmdline: Some(cmdline.to_string()), - tdx: true, + guest_protection: GuestProtection::Tdx, result: Ok(payload_config_without_initrd), }, ]; @@ -1274,7 +1429,11 @@ mod tests { for (i, d) in tests.iter().enumerate() { let msg = format!("test[{}]: {:?}", i, d); - let result = PayloadConfig::try_from((d.boot_info.clone(), d.cmdline.clone(), d.tdx)); + let result = PayloadConfig::try_from(( + d.boot_info.clone(), + d.cmdline.clone(), + d.guest_protection.clone(), + )); let msg = format!("{}: actual result: {:?}", msg, result); @@ -1304,7 +1463,7 @@ mod tests { #[derive(Debug)] struct TestData { mem_info: MemoryInfo, - confidential_guest: bool, + guest_protection: GuestProtection, result: Result, } @@ -1326,7 +1485,7 @@ mod tests { let tests = &[ TestData { mem_info: MemoryInfo::default(), - confidential_guest: false, + guest_protection: GuestProtection::NoProtection, result: Err(MemoryConfigError::NoDefaultMemory), }, TestData { @@ -1335,7 +1494,7 @@ mod tests { ..Default::default() }, - confidential_guest: true, + guest_protection: GuestProtection::Tdx, result: Ok(MemoryConfig { size: (17 * MIB), shared: true, @@ -1350,7 +1509,7 @@ mod tests { ..Default::default() }, - confidential_guest: true, + guest_protection: GuestProtection::Tdx, result: Ok(MemoryConfig { size: usable_max_mem_bytes, shared: true, @@ -1365,7 +1524,7 @@ mod tests { ..Default::default() }, - confidential_guest: true, + guest_protection: GuestProtection::Tdx, result: Err(MemoryConfigError::DefaultMemSizeTooBig), }, TestData { @@ -1374,7 +1533,7 @@ mod tests { ..Default::default() }, - confidential_guest: false, + guest_protection: GuestProtection::NoProtection, result: Ok(MemoryConfig { size: 1024_u64 * MIB, shared: true, @@ -1388,12 +1547,12 @@ mod tests { }, TestData { mem_info: mem_info_std, - confidential_guest: false, + guest_protection: GuestProtection::NoProtection, result: Ok(mem_cfg_std), }, TestData { mem_info: mem_info_confidential_guest, - confidential_guest: true, + guest_protection: GuestProtection::Tdx, result: Ok(mem_cfg_confidential_guest), }, ]; @@ -1401,7 +1560,7 @@ mod tests { for (i, d) in tests.iter().enumerate() { let msg = format!("test[{}]: {:?}", i, d); - let result = MemoryConfig::try_from((d.mem_info.clone(), d.confidential_guest)); + let result = MemoryConfig::try_from((d.mem_info.clone(), d.guest_protection.clone())); let msg = format!("{}: actual result: {:?}", msg, result); @@ -1506,6 +1665,8 @@ mod tests { let kernel = "kernel"; let firmware = "firmware"; + let kernel_params = "foo bar baz=true wibble=1234 a=b:c:d:e moo=0xf00f hello=world quoted_string='a list of stuff' comma-list=a,b,c,d,e"; + let entropy_source = "entropy_source"; let sandbox_path = "sandbox_path"; let vsock_socket_path = "vsock_socket_path"; @@ -1513,7 +1674,8 @@ mod tests { let valid_vsock = VsockConfig::try_from((vsock_socket_path.to_string(), DEFAULT_VSOCK_CID)).unwrap(); - let (cpu_info, cpus_config) = make_cpu_objects(7, u8_max); + let (cpu_info, cpus_config) = make_cpu_objects(7, u8_max, false); + let (cpu_info_tdx, cpus_config_tdx) = make_cpu_objects(7, u8_max, true); let (memory_info_std, mem_config_std) = make_memory_objects(79, usable_max_mem_bytes, false); @@ -1529,20 +1691,13 @@ mod tests { let (boot_info_with_initrd, payload_config_with_initrd) = make_bootinfo_payloadconfig_objects(kernel, initramfs, payload_firmware, None); - let (boot_info_confidential_guest_image, disk_config_confidential_guest_image) = - make_bootinfo_diskconfig_objects(image); - - let boot_info_confidential_guest_initrd = BootInfo { - kernel: kernel.to_string(), - initrd: initramfs.to_string(), - - ..Default::default() - }; + let (_, disk_config_confidential_guest_image) = make_bootinfo_diskconfig_objects(image); let boot_info_tdx_image = BootInfo { kernel: kernel.to_string(), image: image.to_string(), firmware: firmware.to_string(), + vm_rootfs_driver: VIRTIO_BLK_PCI.to_string(), ..Default::default() }; @@ -1555,13 +1710,6 @@ mod tests { ..Default::default() }; - let payload_config_confidential_guest_initrd = PayloadConfig { - kernel: Some(PathBuf::from(kernel)), - initramfs: Some(PathBuf::from(initramfs)), - - ..Default::default() - }; - // XXX: Note that the image is defined in a DiskConfig! let payload_config_tdx_for_image = PayloadConfig { firmware: Some(PathBuf::from(firmware)), @@ -1570,14 +1718,6 @@ mod tests { ..Default::default() }; - let payload_config_tdx_initrd = PayloadConfig { - firmware: Some(PathBuf::from(firmware)), - initramfs: Some(PathBuf::from(initramfs)), - kernel: Some(PathBuf::from(kernel)), - - ..Default::default() - }; - //------------------------------ let hypervisor_cfg_with_image_and_kernel = HypervisorConfig { @@ -1586,6 +1726,7 @@ mod tests { boot_info: BootInfo { image: image.to_string(), kernel: kernel.to_string(), + kernel_params: kernel_params.to_string(), ..Default::default() }, @@ -1609,46 +1750,32 @@ mod tests { ..Default::default() }; - let hypervisor_cfg_confidential_guest_image = HypervisorConfig { - cpu_info: cpu_info.clone(), - memory_info: memory_info_confidential_guest.clone(), - boot_info: BootInfo { - kernel: kernel.to_string(), - - ..boot_info_confidential_guest_image - }, - machine_info: machine_info.clone(), - security_info: security_info_confidential_guest.clone(), - - ..Default::default() - }; - - let hypervisor_cfg_confidential_guest_initrd = HypervisorConfig { - cpu_info: cpu_info.clone(), - memory_info: memory_info_confidential_guest.clone(), - boot_info: boot_info_confidential_guest_initrd, - machine_info: machine_info.clone(), - security_info: security_info_confidential_guest.clone(), - - ..Default::default() - }; - let hypervisor_cfg_tdx_image = HypervisorConfig { - cpu_info: cpu_info.clone(), + cpu_info: cpu_info_tdx.clone(), memory_info: memory_info_confidential_guest.clone(), boot_info: boot_info_tdx_image, machine_info: machine_info.clone(), security_info: security_info_confidential_guest.clone(), + blockdev_info: BlockDeviceInfo { + block_device_driver: VIRTIO_BLK_PCI.to_string(), + + ..Default::default() + }, ..Default::default() }; let hypervisor_cfg_tdx_initrd = HypervisorConfig { - cpu_info, + cpu_info: cpu_info_tdx.clone(), memory_info: memory_info_confidential_guest, boot_info: boot_info_tdx_initrd, machine_info, security_info: security_info_confidential_guest, + blockdev_info: BlockDeviceInfo { + block_device_driver: VIRTIO_BLK_PCI.to_string(), + + ..Default::default() + }, ..Default::default() }; @@ -1666,6 +1793,7 @@ mod tests { payload: Some(PayloadConfig { kernel: Some(PathBuf::from(kernel)), + cmdline: Some(kernel_params.to_string()), ..Default::default() }), @@ -1685,40 +1813,10 @@ mod tests { ..Default::default() }; - let vmconfig_confidential_guest_image = VmConfig { - cpus: cpus_config.clone(), - memory: mem_config_confidential_guest.clone(), - rng: rng_config.clone(), - vsock: Some(valid_vsock.clone()), - - // Confidential guest image specific - disks: Some(vec![disk_config_confidential_guest_image.clone()]), - - payload: Some(PayloadConfig { - kernel: Some(PathBuf::from(kernel)), - - ..Default::default() - }), - - ..Default::default() - }; - - let vmconfig_confidential_guest_initrd = VmConfig { - cpus: cpus_config.clone(), - memory: mem_config_confidential_guest.clone(), - rng: rng_config.clone(), - vsock: Some(valid_vsock.clone()), - - // Confidential guest initrd specific - payload: Some(payload_config_confidential_guest_initrd), - - ..Default::default() - }; - - let platform_config_tdx = get_platform_cfg(true); + let platform_config_tdx = get_platform_cfg(GuestProtection::Tdx); let vmconfig_tdx_image = VmConfig { - cpus: cpus_config.clone(), + cpus: cpus_config_tdx.clone(), memory: mem_config_confidential_guest.clone(), rng: rng_config.clone(), vsock: Some(valid_vsock.clone()), @@ -1733,26 +1831,79 @@ mod tests { ..Default::default() }; - let vmconfig_tdx_initrd = VmConfig { - cpus: cpus_config, - memory: mem_config_confidential_guest, - rng: rng_config, - vsock: Some(valid_vsock), - platform: platform_config_tdx, + //------------------------------ - // Confidential guest + TDX specific - payload: Some(payload_config_tdx_initrd), + let named_hypervisor_cfg_with_image_and_kernel = NamedHypervisorConfig { + kernel_params: kernel_params.to_string(), + sandbox_path: sandbox_path.into(), + vsock_socket_path: vsock_socket_path.into(), + + cfg: hypervisor_cfg_with_image_and_kernel.clone(), ..Default::default() }; - //------------------------------ - - let named_hypervisor_cfg_with_image_and_kernel = NamedHypervisorConfig { + let named_hypervisor_cfg_with_image_and_kernel_bad_cpu = NamedHypervisorConfig { + kernel_params: kernel_params.to_string(), sandbox_path: sandbox_path.into(), vsock_socket_path: vsock_socket_path.into(), - cfg: hypervisor_cfg_with_image_and_kernel, + cfg: HypervisorConfig { + cpu_info: CpuInfo { + default_vcpus: 0, + + ..cpu_info.clone() + }, + + ..hypervisor_cfg_with_image_and_kernel.clone() + }, + + ..Default::default() + }; + + let named_hypervisor_cfg_with_image_and_kernel_bad_payload = NamedHypervisorConfig { + kernel_params: kernel_params.to_string(), + sandbox_path: sandbox_path.into(), + vsock_socket_path: vsock_socket_path.into(), + + cfg: HypervisorConfig { + boot_info: BootInfo { + kernel: String::new(), + image: image.to_string(), + + ..Default::default() + }, + + ..hypervisor_cfg_with_image_and_kernel.clone() + }, + + ..Default::default() + }; + + let named_hypervisor_cfg_with_image_and_kernel_bad_memory = NamedHypervisorConfig { + kernel_params: kernel_params.to_string(), + sandbox_path: sandbox_path.into(), + vsock_socket_path: vsock_socket_path.into(), + + cfg: HypervisorConfig { + memory_info: MemoryInfo { + default_memory: 0, + + ..Default::default() + }, + + ..hypervisor_cfg_with_image_and_kernel.clone() + }, + + ..Default::default() + }; + + let named_hypervisor_cfg_with_image_and_kernel_bad_vsock = NamedHypervisorConfig { + kernel_params: kernel_params.to_string(), + sandbox_path: sandbox_path.into(), + vsock_socket_path: String::new(), + + cfg: hypervisor_cfg_with_image_and_kernel.clone(), ..Default::default() }; @@ -1766,31 +1917,12 @@ mod tests { ..Default::default() }; - let named_hypervisor_cfg_confidential_guest_image = NamedHypervisorConfig { - sandbox_path: sandbox_path.into(), - vsock_socket_path: vsock_socket_path.into(), - - cfg: hypervisor_cfg_confidential_guest_image, - - ..Default::default() - }; - - let named_hypervisor_cfg_confidential_guest_initrd = NamedHypervisorConfig { - sandbox_path: sandbox_path.into(), - vsock_socket_path: vsock_socket_path.into(), - - cfg: hypervisor_cfg_confidential_guest_initrd, - - ..Default::default() - }; - let named_hypervisor_cfg_tdx_image = NamedHypervisorConfig { sandbox_path: sandbox_path.into(), vsock_socket_path: vsock_socket_path.into(), cfg: hypervisor_cfg_tdx_image, - - tdx_enabled: true, + guest_protection_to_use: GuestProtection::Tdx, ..Default::default() }; @@ -1800,8 +1932,7 @@ mod tests { vsock_socket_path: vsock_socket_path.into(), cfg: hypervisor_cfg_tdx_initrd, - - tdx_enabled: true, + guest_protection_to_use: GuestProtection::Tdx, ..Default::default() }; @@ -1833,7 +1964,15 @@ mod tests { cfg: NamedHypervisorConfig { sandbox_path: "sandbox_path".into(), vsock_socket_path: "vsock_socket_path".into(), - cfg: HypervisorConfig::default(), + cfg: HypervisorConfig { + cpu_info: CpuInfo { + default_vcpus: 1, + default_maxvcpus: 1, + + ..Default::default() + }, + ..Default::default() + }, ..Default::default() }, @@ -1850,6 +1989,12 @@ mod tests { ..Default::default() }, + cpu_info: CpuInfo { + default_vcpus: 1, + default_maxvcpus: 1, + + ..Default::default() + }, ..Default::default() }, @@ -1858,6 +2003,24 @@ mod tests { }, result: Err(VmConfigError::MultipleBootFiles), }, + TestData { + cfg: named_hypervisor_cfg_with_image_and_kernel_bad_cpu, + result: Err(VmConfigError::CPUError(CpusConfigError::BootVCPUsTooSmall)), + }, + TestData { + cfg: named_hypervisor_cfg_with_image_and_kernel_bad_payload, + result: Err(VmConfigError::PayloadError(PayloadConfigError::NoKernel)), + }, + TestData { + cfg: named_hypervisor_cfg_with_image_and_kernel_bad_memory, + result: Err(VmConfigError::MemoryError( + MemoryConfigError::NoDefaultMemory, + )), + }, + TestData { + cfg: named_hypervisor_cfg_with_image_and_kernel_bad_vsock, + result: Err(VmConfigError::EmptyVsockSocketPath), + }, TestData { cfg: named_hypervisor_cfg_with_image_and_kernel, result: Ok(vmconfig_with_image_and_kernel), @@ -1866,21 +2029,13 @@ mod tests { cfg: named_hypervisor_cfg_with_initrd, result: Ok(vmconfig_with_initrd), }, - TestData { - cfg: named_hypervisor_cfg_confidential_guest_image, - result: Ok(vmconfig_confidential_guest_image), - }, - TestData { - cfg: named_hypervisor_cfg_confidential_guest_initrd, - result: Ok(vmconfig_confidential_guest_initrd), - }, TestData { cfg: named_hypervisor_cfg_tdx_image, result: Ok(vmconfig_tdx_image), }, TestData { cfg: named_hypervisor_cfg_tdx_initrd, - result: Ok(vmconfig_tdx_initrd), + result: Err(VmConfigError::TDXDisallowsInitrd), }, ]; @@ -1911,4 +2066,291 @@ mod tests { assert_eq!(&result.unwrap(), d.result.as_ref().unwrap(), "{}", msg); } } + + #[test] + fn test_checked_next_multiple() { + #[derive(Debug)] + struct TestData { + value: u64, + multiple: u64, + result: Option, + } + + let tests = &[ + TestData { + value: 0, + multiple: 0, + result: Some(0), + }, + TestData { + value: 1, + multiple: 8, + result: Some(8), + }, + TestData { + value: 0, + multiple: 1, + result: Some(1), + }, + TestData { + value: 1, + multiple: 0, + result: Some(1), + }, + TestData { + value: 2, + multiple: 8, + result: Some(8), + }, + TestData { + value: 7, + multiple: 8, + result: Some(8), + }, + TestData { + value: 8, + multiple: 8, + result: Some(16), + }, + TestData { + value: 9, + multiple: 8, + result: Some(16), + }, + // Test odd multiples + TestData { + value: 1, + multiple: 3, + result: Some(3), + }, + TestData { + value: 2, + multiple: 3, + result: Some(3), + }, + TestData { + value: 3, + multiple: 3, + result: Some(6), + }, + // Test very large values + TestData { + value: u64::MAX - 2, + multiple: 2, + result: Some(18_446_744_073_709_551_614), + }, + // Test values that are too big + TestData { + value: u64::MAX - 1, + multiple: 2, + result: None, + }, + TestData { + value: u64::MAX, + multiple: 2, + result: None, + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + + let result = checked_next_multiple_of(d.value, d.multiple); + + let msg = format!("{}: actual result: {:?}", msg, result); + + if std::env::var("DEBUG").is_ok() { + eprintln!("DEBUG: {}", msg); + } + + assert_eq!(result, d.result, "{}", msg); + } + } + + #[test] + fn test_check_tdx_rootfs_settings() { + #[derive(Debug)] + struct TestData<'a> { + use_image: bool, + container_rootfs_driver: &'a str, + vm_rootfs_driver: &'a str, + guest_protection_to_use: GuestProtection, + result: Result<(), VmConfigError>, + } + + let tests = &[ + // n/a as no TDX + TestData { + use_image: true, + container_rootfs_driver: "container", + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::NoProtection, + result: Ok(()), + }, + TestData { + use_image: true, + container_rootfs_driver: "container", + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Sev, + result: Ok(()), + }, + TestData { + use_image: true, + container_rootfs_driver: "container", + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Snp, + result: Ok(()), + }, + TestData { + use_image: true, + container_rootfs_driver: "container", + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Pef, + result: Ok(()), + }, + TestData { + use_image: true, + container_rootfs_driver: "container", + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Se, + result: Ok(()), + }, + // Incorrect + TestData { + use_image: true, + container_rootfs_driver: "container", + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Tdx, + result: Err(VmConfigError::TDXContainerRootfsNotVirtioBlk), + }, + // Partially correct + TestData { + use_image: true, + container_rootfs_driver: VIRTIO_BLK_PCI, + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Tdx, + result: Err(VmConfigError::TDXVMRootfsNotVirtioBlk), + }, + TestData { + use_image: true, + container_rootfs_driver: VIRTIO_BLK_MMIO, + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Tdx, + result: Err(VmConfigError::TDXVMRootfsNotVirtioBlk), + }, + TestData { + use_image: true, + container_rootfs_driver: "container", + vm_rootfs_driver: VIRTIO_BLK_PCI, + guest_protection_to_use: GuestProtection::Tdx, + result: Err(VmConfigError::TDXContainerRootfsNotVirtioBlk), + }, + TestData { + use_image: true, + container_rootfs_driver: "container", + vm_rootfs_driver: VIRTIO_BLK_MMIO, + guest_protection_to_use: GuestProtection::Tdx, + result: Err(VmConfigError::TDXContainerRootfsNotVirtioBlk), + }, + // Same types + TestData { + use_image: true, + container_rootfs_driver: VIRTIO_BLK_MMIO, + vm_rootfs_driver: VIRTIO_BLK_MMIO, + guest_protection_to_use: GuestProtection::Tdx, + result: Ok(()), + }, + TestData { + use_image: true, + container_rootfs_driver: VIRTIO_BLK_PCI, + vm_rootfs_driver: VIRTIO_BLK_PCI, + guest_protection_to_use: GuestProtection::Tdx, + result: Ok(()), + }, + // Alternate types + TestData { + use_image: true, + container_rootfs_driver: VIRTIO_BLK_MMIO, + vm_rootfs_driver: VIRTIO_BLK_PCI, + guest_protection_to_use: GuestProtection::Tdx, + result: Ok(()), + }, + TestData { + use_image: true, + container_rootfs_driver: VIRTIO_BLK_PCI, + vm_rootfs_driver: VIRTIO_BLK_MMIO, + guest_protection_to_use: GuestProtection::Tdx, + result: Ok(()), + }, + // Using an initrd (not currently supported) + TestData { + use_image: false, + container_rootfs_driver: VIRTIO_BLK_PCI, + vm_rootfs_driver: VIRTIO_BLK_PCI, + guest_protection_to_use: GuestProtection::Tdx, + result: Err(VmConfigError::TDXDisallowsInitrd), + }, + TestData { + use_image: false, + container_rootfs_driver: "container", + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Tdx, + result: Err(VmConfigError::TDXDisallowsInitrd), + }, + TestData { + use_image: false, + container_rootfs_driver: VIRTIO_BLK_PCI, + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Tdx, + result: Err(VmConfigError::TDXDisallowsInitrd), + }, + TestData { + use_image: false, + container_rootfs_driver: VIRTIO_BLK_MMIO, + vm_rootfs_driver: "vm", + guest_protection_to_use: GuestProtection::Tdx, + result: Err(VmConfigError::TDXDisallowsInitrd), + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + + let image = if d.use_image { + "image".to_string() + } else { + "".to_string() + }; + + let boot_info = BootInfo { + vm_rootfs_driver: d.vm_rootfs_driver.into(), + image, + + ..Default::default() + }; + + let blockdev_info = BlockDeviceInfo { + block_device_driver: d.container_rootfs_driver.into(), + + ..Default::default() + }; + + let cfg = HypervisorConfig { + boot_info, + blockdev_info, + + ..Default::default() + }; + + let result = check_tdx_rootfs_settings(&cfg, &d.guest_protection_to_use); + + let msg = format!("{}: actual result: {:?}", msg, result); + + if std::env::var("DEBUG").is_ok() { + eprintln!("DEBUG: {}", msg); + } + + assert_eq!(result, d.result, "{}", msg); + } + } } diff --git a/src/runtime-rs/crates/hypervisor/ch-config/src/errors.rs b/src/runtime-rs/crates/hypervisor/ch-config/src/errors.rs index 7e062f5e6e..422d177d37 100644 --- a/src/runtime-rs/crates/hypervisor/ch-config/src/errors.rs +++ b/src/runtime-rs/crates/hypervisor/ch-config/src/errors.rs @@ -41,6 +41,16 @@ pub enum VmConfigError { #[error("VSOCK config error: {0}")] VsockError(VsockConfigError), + + #[error("TDX requires virtio-blk VM rootfs driver")] + TDXVMRootfsNotVirtioBlk, + + #[error("TDX requires virtio-blk container rootfs block device driver")] + TDXContainerRootfsNotVirtioBlk, + + // LIMITATION: Current CH TDX limitation. + #[error("TDX requires an image=, not an initrd=")] + TDXDisallowsInitrd, } #[derive(Error, Debug, PartialEq)] @@ -60,11 +70,20 @@ pub enum DiskConfigError { #[derive(Error, Debug, PartialEq)] pub enum CpusConfigError { + #[error("Boot vCPUs cannot be zero or negative")] + BootVCPUsTooSmall, + #[error("Too many boot vCPUs specified: {0}")] BootVCPUsTooBig(>::Error), + #[error("Max vCPUs cannot be zero or negative")] + MaxVCPUsTooSmall, + #[error("Too many max vCPUs specified: {0}")] MaxVCPUsTooBig(>::Error), + + #[error("Boot vCPUs cannot be larger than max vCPUs")] + BootVPUsGtThanMaxVCPUs, } #[derive(Error, Debug, PartialEq)] 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 fd5b228f50..a352792807 100644 --- a/src/runtime-rs/crates/hypervisor/ch-config/src/lib.rs +++ b/src/runtime-rs/crates/hypervisor/ch-config/src/lib.rs @@ -12,6 +12,7 @@ pub mod net_util; mod virtio_devices; use crate::virtio_devices::RateLimiterConfig; +use kata_sys_util::protection::GuestProtection; use kata_types::config::hypervisor::Hypervisor as HypervisorConfig; pub use net_util::MacAddr; @@ -489,8 +490,78 @@ pub struct NamedHypervisorConfig { pub sandbox_path: String, pub vsock_socket_path: String, pub cfg: HypervisorConfig, - pub tdx_enabled: bool, pub shared_fs_devices: Option>, pub network_devices: Option>, + + // Set to the available guest protection *iff* BOTH of the following + // conditions are true: + // + // - The hardware supports guest protection. + // - The user has requested that guest protection be used. + pub guest_protection_to_use: GuestProtection, +} + +// 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) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_guest_protection_is_tdx() { + #[derive(Debug)] + struct TestData { + protection: GuestProtection, + result: bool, + } + + let tests = &[ + TestData { + protection: GuestProtection::NoProtection, + result: false, + }, + TestData { + protection: GuestProtection::Pef, + result: false, + }, + TestData { + protection: GuestProtection::Se, + result: false, + }, + TestData { + protection: GuestProtection::Sev, + result: false, + }, + TestData { + protection: GuestProtection::Snp, + result: false, + }, + TestData { + protection: GuestProtection::Tdx, + result: true, + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + + let result = guest_protection_is_tdx(d.protection.clone()); + + let msg = format!("{}: actual result: {:?}", msg, result); + + if std::env::var("DEBUG").is_ok() { + eprintln!("DEBUG: {}", msg); + } + + if d.result { + assert!(result, "{}", msg); + } else { + assert!(!result, "{}", msg); + } + } + } } diff --git a/src/runtime-rs/crates/hypervisor/src/ch/inner.rs b/src/runtime-rs/crates/hypervisor/src/ch/inner.rs index 6be9df2826..2a08306118 100644 --- a/src/runtime-rs/crates/hypervisor/src/ch/inner.rs +++ b/src/runtime-rs/crates/hypervisor/src/ch/inner.rs @@ -8,6 +8,7 @@ use crate::device::DeviceType; use crate::VmmState; use anyhow::Result; use async_trait::async_trait; +use kata_sys_util::protection::GuestProtection; use kata_types::capabilities::{Capabilities, CapabilityBits}; use kata_types::config::hypervisor::Hypervisor as HypervisorConfig; use kata_types::config::hypervisor::HYPERVISOR_NAME_CH; @@ -51,6 +52,13 @@ pub struct CloudHypervisorInner { pub(crate) shutdown_tx: Option>, pub(crate) shutdown_rx: Option>, pub(crate) tasks: Option>>>, + + // Set if the hardware supports creating a protected guest *AND* if the + // user has requested creating a protected guest. + // + // For example, on Intel TDX capable systems with `confidential_guest=true`, + // this will be set to "tdx". + pub(crate) guest_protection_to_use: GuestProtection, } const CH_DEFAULT_TIMEOUT_SECS: u32 = 10; @@ -86,6 +94,7 @@ impl CloudHypervisorInner { shutdown_tx: Some(tx), shutdown_rx: Some(rx), tasks: None, + guest_protection_to_use: GuestProtection::NoProtection, } } 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 bd34fd0be4..8f961b2375 100644 --- a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs +++ b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs @@ -8,24 +8,27 @@ use crate::ch::utils::get_api_socket_path; use crate::ch::utils::get_vsock_path; use crate::kernel_param::KernelParams; use crate::utils::{get_jailer_root, get_sandbox_path}; -use crate::VM_ROOTFS_DRIVER_PMEM; use crate::{VcpuThreadIds, VmmState}; +use crate::{VM_ROOTFS_DRIVER_BLK, VM_ROOTFS_DRIVER_PMEM}; use anyhow::{anyhow, Context, Result}; use ch_config::ch_api::{ cloud_hypervisor_vm_create, cloud_hypervisor_vm_start, cloud_hypervisor_vmm_ping, cloud_hypervisor_vmm_shutdown, }; -use ch_config::{NamedHypervisorConfig, VmConfig}; +use ch_config::{guest_protection_is_tdx, NamedHypervisorConfig, VmConfig}; use core::future::poll_fn; use futures::executor::block_on; use futures::future::join_all; +use kata_sys_util::protection::{available_guest_protection, GuestProtection}; use kata_types::capabilities::{Capabilities, CapabilityBits}; use kata_types::config::default::DEFAULT_CH_ROOTFS_TYPE; +use lazy_static::lazy_static; use std::convert::TryFrom; use std::fs::create_dir_all; use std::os::unix::net::UnixStream; use std::path::Path; use std::process::Stdio; +use std::sync::{Arc, RwLock}; use tokio::io::AsyncBufReadExt; use tokio::io::BufReader; use tokio::process::{Child, Command}; @@ -39,6 +42,21 @@ const CH_NAME: &str = "cloud-hypervisor"; /// Number of milliseconds to wait before retrying a CH operation. const CH_POLL_TIME_MS: u64 = 50; +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum GuestProtectionError { + #[error("guest protection requested but no guest protection available")] + NoProtectionAvailable, + + // LIMITATION: Current CH TDX limitation. + // + // When built to support TDX, if Cloud Hypervisor determines the host + // system supports TDX, it can only create TD's (as opposed to VMs). + // Hence, on a TDX capable system, confidential_guest *MUST* be set to + // "true". + #[error("TDX guest protection available and must be used with Cloud Hypervisor (set 'confidential_guest=true')")] + TDXProtectionMustBeUsedWithCH, +} + impl CloudHypervisorInner { async fn start_hypervisor(&mut self, timeout_secs: i32) -> Result<()> { self.cloud_hypervisor_launch(timeout_secs) @@ -70,7 +88,12 @@ impl CloudHypervisorInner { let confidential_guest = cfg.security_info.confidential_guest; // Note that the configuration option hypervisor.block_device_driver is not used. - let rootfs_driver = VM_ROOTFS_DRIVER_PMEM; + let rootfs_driver = if confidential_guest { + // PMEM is not available with TDX. + VM_ROOTFS_DRIVER_BLK + } else { + VM_ROOTFS_DRIVER_PMEM + }; let rootfs_type = match cfg.boot_info.rootfs_type.is_empty() { true => DEFAULT_CH_ROOTFS_TYPE, @@ -82,7 +105,7 @@ impl CloudHypervisorInner { let mut rootfs_param = KernelParams::new_rootfs_kernel_params(rootfs_driver, rootfs_type)?; - let mut extra_params = if enable_debug { + let mut console_params = if enable_debug { if confidential_guest { KernelParams::from_string("console=hvc0") } else { @@ -92,11 +115,21 @@ impl CloudHypervisorInner { KernelParams::from_string("quiet") }; - params.append(&mut extra_params); + params.append(&mut console_params); // Add the rootfs device params.append(&mut rootfs_param); + // Now add some additional options required for CH + let extra_options = [ + "no_timer_check", // Do not Check broken timer IRQ resources + "noreplace-smp", // Do not replace SMP instructions + "systemd.log_target=console", // Send logging output to the console + ]; + + let mut extra_params = KernelParams::from_string(&extra_options.join(" ")); + params.append(&mut extra_params); + // Finally, add the user-specified options at the end // (so they will take priority). params.append(&mut KernelParams::from_string(&cfg.boot_info.kernel_params)); @@ -134,25 +167,24 @@ impl CloudHypervisorInner { let kernel_params = self.get_kernel_params().await?; - // FIXME: See: - // - // - https://github.com/kata-containers/kata-containers/issues/6383 - // - https://github.com/kata-containers/kata-containers/pull/6257 - let tdx_enabled = false; - let named_cfg = NamedHypervisorConfig { kernel_params, sandbox_path, vsock_socket_path, cfg: hypervisor_config.clone(), - tdx_enabled, + guest_protection_to_use: self.guest_protection_to_use.clone(), shared_fs_devices, network_devices, }; let cfg = VmConfig::try_from(named_cfg)?; - debug!(sl!(), "CH specific VmConfig configuration: {:?}", cfg); + let serialised = serde_json::to_string(&cfg)?; + + debug!( + sl!(), + "CH specific VmConfig configuration (JSON): {:?}", serialised + ); let response = cloud_hypervisor_vm_create(socket.try_clone().context("failed to clone socket")?, cfg) @@ -256,7 +288,7 @@ impl CloudHypervisorInner { let debug = cfg.debug_info.enable_debug; - let disable_seccomp = true; + let disable_seccomp = cfg.security_info.disable_seccomp; let api_socket_path = get_api_socket_path(&self.id)?; @@ -289,6 +321,9 @@ impl CloudHypervisorInner { } if debug { + // Note that with TDX enabled, this results in a lot of additional + // CH output, particularly if the user adds "earlyprintk" to the + // guest kernel command line (by modifying "kernel_params="). cmd.arg("-v"); } @@ -296,6 +331,8 @@ impl CloudHypervisorInner { cmd.args(["--seccomp", "false"]); } + debug!(sl!(), "launching {} as: {:?}", CH_NAME, cmd); + let child = cmd.spawn().context(format!("{} spawn failed", CH_NAME))?; // Save process PID @@ -415,11 +452,55 @@ impl CloudHypervisorInner { self.setup_environment().await?; + self.handle_guest_protection().await?; + self.netns = netns; Ok(()) } + // Check if guest protection is available and also check if the user + // actually wants to use it. + // + // Note: This method must be called as early as possible since after this + // call, if confidential_guest is set, a confidential + // guest will be created. + async fn handle_guest_protection(&mut self) -> Result<()> { + let cfg = self + .config + .as_ref() + .ok_or("missing hypervisor config") + .map_err(|e| anyhow!(e))?; + + let confidential_guest = cfg.security_info.confidential_guest; + + if confidential_guest { + info!(sl!(), "confidential guest requested"); + } + + let protection = + task::spawn_blocking(|| -> Result { get_guest_protection() }) + .await??; + + if protection == GuestProtection::NoProtection { + if confidential_guest { + return Err(anyhow!(GuestProtectionError::NoProtectionAvailable)); + } else { + debug!(sl!(), "no guest protection available"); + } + } else if confidential_guest { + self.guest_protection_to_use = protection.clone(); + + info!(sl!(), "guest protection available and requested"; "guest-protection" => protection.to_string()); + } else if protection == GuestProtection::Tdx { + return Err(anyhow!(GuestProtectionError::TDXProtectionMustBeUsedWithCH)); + } else { + info!(sl!(), "guest protection available but not requested"; "guest-protection" => protection.to_string()); + } + + Ok(()) + } + async fn setup_environment(&mut self) -> Result<()> { // run_dir and vm_path are the same (shared) self.run_dir = get_sandbox_path(&self.id); @@ -524,7 +605,18 @@ impl CloudHypervisorInner { pub(crate) async fn capabilities(&self) -> Result { let mut caps = Capabilities::default(); - caps.set(CapabilityBits::FsSharingSupport); + + let flags = if guest_protection_is_tdx(self.guest_protection_to_use.clone()) { + // TDX does not permit the use of virtio-fs. + CapabilityBits::BlockDeviceSupport | CapabilityBits::BlockDeviceHotplugSupport + } else { + CapabilityBits::BlockDeviceSupport + | CapabilityBits::BlockDeviceHotplugSupport + | CapabilityBits::FsSharingSupport + }; + + caps.set(flags); + Ok(caps) } @@ -583,3 +675,331 @@ async fn cloud_hypervisor_log_output(mut child: Child, mut shutdown: Receiver>> = + Arc::new(RwLock::new(Some(GuestProtection::NoProtection))); +} + +// Return the _fake_ GuestProtection value set by set_guest_protection(). +fn get_fake_guest_protection() -> Result { + let existing_ref = FAKE_GUEST_PROTECTION.clone(); + + let existing = existing_ref.read().unwrap(); + + let real_protection = available_guest_protection()?; + + let protection = if let Some(ref protection) = *existing { + protection + } else { + // XXX: If no fake value is set, fall back to the real function. + &real_protection + }; + + Ok(protection.clone()) +} + +// Return available hardware protection, or GuestProtection::NoProtection +// if none available. +// +// XXX: Note that this function wraps the low-level function to determine +// guest protection. It does this to allow us to force a particular guest +// protection type in the unit tests. +fn get_guest_protection() -> Result { + let guest_protection = if cfg!(test) { + get_fake_guest_protection() + } else { + available_guest_protection().map_err(|e| anyhow!(e.to_string())) + }?; + + Ok(guest_protection) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(target_arch = "x86_64")] + use kata_sys_util::protection::TDX_SYS_FIRMWARE_DIR; + + use kata_types::config::hypervisor::{Hypervisor as HypervisorConfig, SecurityInfo}; + use serial_test::serial; + use std::path::PathBuf; + use test_utils::{assert_result, skip_if_not_root}; + + fn set_fake_guest_protection(protection: Option) { + let existing_ref = FAKE_GUEST_PROTECTION.clone(); + + let mut existing = existing_ref.write().unwrap(); + + // Modify the lazy static global config structure + *existing = protection; + } + + #[serial] + #[actix_rt::test] + async fn test_get_guest_protection() { + // available_guest_protection() requires super user privs. + skip_if_not_root!(); + + #[derive(Debug)] + struct TestData { + value: Option, + result: Result, + } + + let tests = &[ + TestData { + value: Some(GuestProtection::NoProtection), + result: Ok(GuestProtection::NoProtection), + }, + TestData { + value: Some(GuestProtection::Pef), + result: Ok(GuestProtection::Pef), + }, + TestData { + value: Some(GuestProtection::Se), + result: Ok(GuestProtection::Se), + }, + TestData { + value: Some(GuestProtection::Sev), + result: Ok(GuestProtection::Sev), + }, + TestData { + value: Some(GuestProtection::Snp), + result: Ok(GuestProtection::Snp), + }, + TestData { + value: Some(GuestProtection::Tdx), + result: Ok(GuestProtection::Tdx), + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + + set_fake_guest_protection(d.value.clone()); + + let result = + task::spawn_blocking(|| -> Result { get_guest_protection() }) + .await + .unwrap(); + + let msg = format!("{}: actual result: {:?}", msg, result); + + if std::env::var("DEBUG").is_ok() { + eprintln!("DEBUG: {}", msg); + } + + assert_result!(d.result, result, msg); + } + + // Reset + set_fake_guest_protection(None); + } + + #[cfg(target_arch = "x86_64")] + #[serial] + #[actix_rt::test] + async fn test_get_guest_protection_tdx() { + // available_guest_protection() requires super user privs. + skip_if_not_root!(); + + // Use the hosts protection, not a fake one. + set_fake_guest_protection(None); + + let tdx_fw_path = PathBuf::from(TDX_SYS_FIRMWARE_DIR); + + // Simple test for Intel TDX + let have_tdx = if tdx_fw_path.exists() { + if let Ok(metadata) = std::fs::metadata(tdx_fw_path.clone()) { + metadata.is_dir() + } else { + false + } + } else { + false + }; + + let protection = + task::spawn_blocking(|| -> Result { get_guest_protection() }) + .await + .unwrap() + .unwrap(); + + if std::env::var("DEBUG").is_ok() { + let msg = format!( + "tdx_fw_path: {:?}, have_tdx: {:?}, protection: {:?}", + tdx_fw_path, have_tdx, protection + ); + + eprintln!("DEBUG: {}", msg); + } + + if have_tdx { + assert_eq!(protection, GuestProtection::Tdx); + } else { + assert_eq!(protection, GuestProtection::NoProtection); + } + } + + #[serial] + #[actix_rt::test] + async fn test_handle_guest_protection() { + // available_guest_protection() requires super user privs. + skip_if_not_root!(); + + #[derive(Debug)] + struct TestData { + confidential_guest: bool, + available_protection: Option, + + result: Result<()>, + + // The expected result (internal state) + guest_protection_to_use: GuestProtection, + } + + let tests = &[ + TestData { + confidential_guest: false, + available_protection: Some(GuestProtection::NoProtection), + result: Ok(()), + guest_protection_to_use: GuestProtection::NoProtection, + }, + TestData { + confidential_guest: true, + available_protection: Some(GuestProtection::NoProtection), + result: Err(anyhow!(GuestProtectionError::NoProtectionAvailable)), + guest_protection_to_use: GuestProtection::NoProtection, + }, + TestData { + confidential_guest: false, + available_protection: Some(GuestProtection::Tdx), + result: Err(anyhow!(GuestProtectionError::TDXProtectionMustBeUsedWithCH)), + guest_protection_to_use: GuestProtection::NoProtection, + }, + TestData { + confidential_guest: true, + available_protection: Some(GuestProtection::Tdx), + result: Ok(()), + guest_protection_to_use: GuestProtection::Tdx, + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + + set_fake_guest_protection(d.available_protection.clone()); + + let mut ch = CloudHypervisorInner::default(); + + let cfg = HypervisorConfig { + security_info: SecurityInfo { + confidential_guest: d.confidential_guest, + + ..Default::default() + }, + + ..Default::default() + }; + + ch.set_hypervisor_config(cfg); + + let result = ch.handle_guest_protection().await; + + let msg = format!("{}: actual result: {:?}", msg, result); + + if std::env::var("DEBUG").is_ok() { + eprintln!("DEBUG: {}", msg); + } + + if d.result.is_ok() && result.is_ok() { + continue; + } + + assert_result!(d.result, result, msg); + + assert_eq!( + ch.guest_protection_to_use, d.guest_protection_to_use, + "{}", + msg + ); + } + + // Reset + set_fake_guest_protection(None); + } + + #[actix_rt::test] + async fn test_get_kernel_params() { + #[derive(Debug)] + struct TestData<'a> { + cfg: Option, + confidential_guest: bool, + debug: bool, + fails: bool, + contains: Vec<&'a str>, + } + + let tests = &[ + TestData { + cfg: None, + confidential_guest: false, + debug: false, + fails: true, // No hypervisor config + contains: vec![], + }, + TestData { + cfg: Some(HypervisorConfig::default()), + confidential_guest: false, + debug: false, + fails: false, + contains: vec![], + }, + ]; + + for (i, d) in tests.iter().enumerate() { + let msg = format!("test[{}]: {:?}", i, d); + + let mut ch = CloudHypervisorInner::default(); + + if let Some(ref mut cfg) = d.cfg.clone() { + if d.debug { + cfg.debug_info.enable_debug = true; + } + + if d.confidential_guest { + cfg.security_info.confidential_guest = true; + } + + ch.set_hypervisor_config(cfg.clone()); + + let result = ch.get_kernel_params().await; + + let msg = format!("{}: actual result: {:?}", msg, result); + + if std::env::var("DEBUG").is_ok() { + eprintln!("DEBUG: {}", msg); + } + + if d.fails { + assert!(result.is_err(), "{}", msg); + continue; + } + + let result = result.unwrap(); + + for token in d.contains.clone() { + assert!(result.contains(token), "{}", msg); + } + } + } + } +}