mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-04-28 11:44:38 +00:00
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:
parent
523399c329
commit
b0a3293d53
71
src/libs/Cargo.lock
generated
71
src/libs/Cargo.lock
generated
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -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";
|
||||
|
33
src/runtime-rs/Cargo.lock
generated
33
src/runtime-rs/Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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
@ -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)]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user