runtime-rs: ch: Enable Intel TDX

Allow Cloud Hypervisor to create a confidential guest (a TD or
"Trust Domain") rather than a VM (Virtual Machine) on Intel systems
that provide TDX functionality.

> **Notes:**
>
> - At least currently, when built with the `tdx` feature, Cloud Hypervisor
>   cannot create a standard VM on a TDX capable system: it can only create
>   a TD. This implies that on TDX capable systems, the Kata Configuration
>   option `confidential_guest=` must be set to `true`. If it is not, Kata
>   will detect this and display the following error:
>
>   ```
>   TDX guest protection available and must be used with Cloud Hypervisor (set 'confidential_guest=true')
>   ```
>
> - This change expands the scope of the protection code, changing
>   Intel TDX specific booleans to more generic "available guest protection"
>   code that could be "none" or "TDX", or some other form of guest
>   protection.

Fixes: #6448.

Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
This commit is contained in:
James O. D. Hunt 2023-09-22 17:21:28 +01:00
parent 523399c329
commit b0a3293d53
11 changed files with 1357 additions and 281 deletions

71
src/libs/Cargo.lock generated
View File

@ -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"

View File

@ -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,

View File

@ -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";

View File

@ -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",

View File

@ -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"

View File

@ -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"

File diff suppressed because it is too large Load Diff

View File

@ -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(<u8 as TryFrom<i32>>::Error),
#[error("Max vCPUs cannot be zero or negative")]
MaxVCPUsTooSmall,
#[error("Too many max vCPUs specified: {0}")]
MaxVCPUsTooBig(<u8 as TryFrom<u32>>::Error),
#[error("Boot vCPUs cannot be larger than max vCPUs")]
BootVPUsGtThanMaxVCPUs,
}
#[derive(Error, Debug, PartialEq)]

View File

@ -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<Vec<FsConfig>>,
pub network_devices: Option<Vec<NetConfig>>,
// 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);
}
}
}
}

View File

@ -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<Sender<bool>>,
pub(crate) shutdown_rx: Option<Receiver<bool>>,
pub(crate) tasks: Option<Vec<JoinHandle<Result<()>>>>,
// 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,
}
}

View File

@ -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<GuestProtection> { 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<Capabilities> {
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<bo
Ok(())
}
lazy_static! {
// Store the fake guest protection value used by
// get_fake_guest_protection() and set_fake_guest_protection().
//
// Note that if this variable is set to None, get_fake_guest_protection()
// will fall back to checking the actual guest protection by calling
// get_guest_protection().
static ref FAKE_GUEST_PROTECTION: Arc<RwLock<Option<GuestProtection>>> =
Arc::new(RwLock::new(Some(GuestProtection::NoProtection)));
}
// Return the _fake_ GuestProtection value set by set_guest_protection().
fn get_fake_guest_protection() -> Result<GuestProtection> {
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<GuestProtection> {
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<GuestProtection>) {
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<GuestProtection>,
result: Result<GuestProtection>,
}
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<GuestProtection> { 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<GuestProtection> { 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<GuestProtection>,
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<HypervisorConfig>,
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);
}
}
}
}
}