diff --git a/src/libs/kata-sys-util/src/protection.rs b/src/libs/kata-sys-util/src/protection.rs index 9cb11a1178..8e06117c88 100644 --- a/src/libs/kata-sys-util/src/protection.rs +++ b/src/libs/kata-sys-util/src/protection.rs @@ -3,8 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 // -#[cfg(target_arch = "x86_64")] -use anyhow::anyhow; #[cfg(any(target_arch = "s390x", target_arch = "x86_64", target_arch = "aarch64"))] use anyhow::Result; use serde::{Deserialize, Serialize}; @@ -25,12 +23,6 @@ use nix::unistd::Uid; #[cfg(target_arch = "x86_64")] use std::fs; -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct TDXDetails { - pub major_version: u32, - pub minor_version: u32, -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SevSnpDetails { pub cbitpos: u32, @@ -41,7 +33,7 @@ pub struct SevSnpDetails { pub enum GuestProtection { #[default] NoProtection, - Tdx(TDXDetails), + Tdx, Sev(SevSnpDetails), Snp(SevSnpDetails), Pef, @@ -51,11 +43,7 @@ pub enum GuestProtection { impl fmt::Display for GuestProtection { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - GuestProtection::Tdx(details) => write!( - f, - "tdx (major_version: {}, minor_version: {})", - details.major_version, details.minor_version - ), + GuestProtection::Tdx => write!(f, "tdx"), GuestProtection::Sev(details) => write!(f, "sev (cbitpos: {}", details.cbitpos), GuestProtection::Snp(details) => write!(f, "snp (cbitpos: {}", details.cbitpos), GuestProtection::Pef => write!(f, "pef"), @@ -88,95 +76,29 @@ pub enum ProtectionError { } #[cfg(target_arch = "x86_64")] -pub const TDX_SYS_FIRMWARE_DIR: &str = "/sys/firmware/tdx/"; +pub const TDX_KVM_PARAMETER_PATH: &str = "/sys/module/kvm_intel/parameters/tdx"; #[cfg(target_arch = "x86_64")] pub const SEV_KVM_PARAMETER_PATH: &str = "/sys/module/kvm_amd/parameters/sev"; #[cfg(target_arch = "x86_64")] pub const SNP_KVM_PARAMETER_PATH: &str = "/sys/module/kvm_amd/parameters/sev_snp"; -// Module directory below TDX_SYS_FIRMWARE_DIR. -#[cfg(target_arch = "x86_64")] -const TDX_FW_MODULE_DIR: &str = "tdx_module"; - -// File in TDX_FW_MODULE_DIR that specifies TDX major version number. -#[cfg(target_arch = "x86_64")] -const TDX_MAJOR_FILE: &str = "major_version"; - -// File in TDX_FW_MODULE_DIR that specifies TDX minor version number. -#[cfg(target_arch = "x86_64")] -const TDX_MINOR_FILE: &str = "minor_version"; - #[cfg(target_arch = "x86_64")] pub fn available_guest_protection() -> Result { - arch_guest_protection( - TDX_SYS_FIRMWARE_DIR, - SEV_KVM_PARAMETER_PATH, - SNP_KVM_PARAMETER_PATH, - ) + arch_guest_protection(SEV_KVM_PARAMETER_PATH, SNP_KVM_PARAMETER_PATH) } #[cfg(target_arch = "x86_64")] pub fn arch_guest_protection( - tdx_path: &str, sev_path: &str, snp_path: &str, ) -> Result { - let metadata = fs::metadata(tdx_path); - - if metadata.is_ok() && metadata.unwrap().is_dir() { - let module_dir = safe_path::scoped_join(tdx_path, TDX_FW_MODULE_DIR).map_err(|e| { - ProtectionError::CannotResolvePath( - TDX_FW_MODULE_DIR.to_string(), - PathBuf::from(tdx_path), - anyhow!(e), - ) - })?; - - let major_file = - safe_path::scoped_join(module_dir.clone(), TDX_MAJOR_FILE).map_err(|e| { - ProtectionError::CannotResolvePath( - TDX_MAJOR_FILE.to_string(), - module_dir.clone(), - anyhow!(e), - ) - })?; - - let minor_file = - safe_path::scoped_join(module_dir.clone(), TDX_MINOR_FILE).map_err(|e| { - ProtectionError::CannotResolvePath( - TDX_MINOR_FILE.to_string(), - module_dir, - anyhow!(e), - ) - })?; - - const HEX_BASE: u32 = 16; - const HEX_PREFIX: &str = "0x"; - - let major_version_str = std::fs::read_to_string(major_file.clone()).map_err(|e| { - ProtectionError::FileMissing(major_file.clone().to_string_lossy().into(), e) - })?; - - let major_version_str = major_version_str.trim_start_matches(HEX_PREFIX); - - let major_version = u32::from_str_radix(major_version_str, HEX_BASE) - .map_err(|e| ProtectionError::FileInvalid(major_file, anyhow!(e)))?; - - let minor_version_str = std::fs::read_to_string(minor_file.clone()).map_err(|e| { - ProtectionError::FileMissing(minor_file.clone().to_string_lossy().into(), e) - })?; - - let minor_version_str = minor_version_str.trim_start_matches(HEX_PREFIX); - - let minor_version = u32::from_str_radix(minor_version_str, HEX_BASE) - .map_err(|e| ProtectionError::FileInvalid(minor_file, anyhow!(e)))?; - - let details = TDXDetails { - major_version, - minor_version, - }; - - return Ok(GuestProtection::Tdx(details)); + // Check if /sys/module/kvm_intel/parameters/tdx is set to 'Y' + if Path::new(TDX_KVM_PARAMETER_PATH).exists() { + if let Ok(content) = fs::read(TDX_KVM_PARAMETER_PATH) { + if !content.is_empty() && content[0] == b'Y' { + return Ok(GuestProtection::Tdx); + } + } } let check_contents = |file_name: &str| -> Result { @@ -301,12 +223,12 @@ mod tests { let mut snp_file = fs::File::create(snp_file_path).unwrap(); writeln!(snp_file, "Y").unwrap(); - let actual = arch_guest_protection("/xyz/tmp", "/xyz/tmp", path.to_str().unwrap()); + let actual = arch_guest_protection("/xyz/tmp", path.to_str().unwrap()); assert!(actual.is_ok()); assert_eq!(actual.unwrap(), GuestProtection::Snp); writeln!(snp_file, "N").unwrap(); - let actual = arch_guest_protection("/xyz/tmp", "/xyz/tmp", path.to_str().unwrap()); + let actual = arch_guest_protection("/xyz/tmp", path.to_str().unwrap()); assert!(actual.is_ok()); assert_eq!(actual.unwrap(), GuestProtection::NoProtection); } @@ -320,12 +242,12 @@ mod tests { let mut sev_file = fs::File::create(sev_file_path).unwrap(); writeln!(sev_file, "Y").unwrap(); - let actual = arch_guest_protection("/xyz/tmp", sev_path.to_str().unwrap(), "/xyz/tmp"); + let actual = arch_guest_protection(sev_path.to_str().unwrap(), "/xyz/tmp"); assert!(actual.is_ok()); assert_eq!(actual.unwrap(), GuestProtection::Sev); writeln!(sev_file, "N").unwrap(); - let actual = arch_guest_protection("/xyz/tmp", sev_path.to_str().unwrap(), "/xyz/tmp"); + let actual = arch_guest_protection(sev_path.to_str().unwrap(), "/xyz/tmp"); assert!(actual.is_ok()); assert_eq!(actual.unwrap(), GuestProtection::NoProtection); } @@ -342,49 +264,19 @@ mod tests { std::fs::create_dir_all(tdx_path.clone()).unwrap(); - let actual = arch_guest_protection(invalid_dir, invalid_dir, invalid_dir); + let actual = arch_guest_protection(invalid_dir, invalid_dir); assert!(actual.is_ok()); assert_eq!(actual.unwrap(), GuestProtection::NoProtection); - let actual = arch_guest_protection(tdx_path.to_str().unwrap(), invalid_dir, invalid_dir); + let actual = arch_guest_protection(invalid_dir, invalid_dir); assert!(actual.is_err()); - let tdx_module = tdx_path.join(TDX_FW_MODULE_DIR); - std::fs::create_dir_all(tdx_module.clone()).unwrap(); - - let major_file = tdx_module.join(TDX_MAJOR_FILE); - std::fs::File::create(&major_file).unwrap(); - - let minor_file = tdx_module.join(TDX_MINOR_FILE); - std::fs::File::create(&minor_file).unwrap(); - - let result = arch_guest_protection(tdx_path.to_str().unwrap(), invalid_dir, invalid_dir); - assert!(result.is_err()); - - std::fs::write(&major_file, b"invalid").unwrap(); - std::fs::write(&minor_file, b"invalid").unwrap(); - - let result = arch_guest_protection(tdx_path.to_str().unwrap(), invalid_dir, invalid_dir); - assert!(result.is_err()); - - // Fake a TDX 1.0 environment - std::fs::write(&major_file, b"0x00000001").unwrap(); - std::fs::write(&minor_file, b"0x00000000").unwrap(); - - let result = arch_guest_protection(tdx_path.to_str().unwrap(), invalid_dir, invalid_dir); + let result = arch_guest_protection(invalid_dir, invalid_dir); assert!(result.is_ok()); let result = result.unwrap(); - let details = match &result { - GuestProtection::Tdx(details) => details, - _ => panic!(), - }; - - assert_eq!(details.major_version, 1); - assert_eq!(details.minor_version, 0); - let displayed_value = result.to_string(); - assert_eq!(displayed_value, "tdx (major_version: 1, minor_version: 0)"); + assert_eq!(displayed_value, "tdx"); } } diff --git a/src/libs/kata-types/src/config/hypervisor/mod.rs b/src/libs/kata-types/src/config/hypervisor/mod.rs index b5b0a26251..7c18d69143 100644 --- a/src/libs/kata-types/src/config/hypervisor/mod.rs +++ b/src/libs/kata-types/src/config/hypervisor/mod.rs @@ -890,6 +890,17 @@ pub struct SecurityInfo { /// e.g. "path" for io.katacontainers.config.hypervisor.path" #[serde(default)] pub enable_annotations: Vec, + + /// qgs_port defines Intel Quote Generation Service port exposed from the host + #[serde( + default = "default_qgs_port", + rename = "tdx_quote_generation_service_socket_port" + )] + pub qgs_port: u32, +} + +fn default_qgs_port() -> u32 { + 4050 } impl SecurityInfo { diff --git a/src/runtime-rs/Makefile b/src/runtime-rs/Makefile index 8a0e00e614..cf6a348a84 100644 --- a/src/runtime-rs/Makefile +++ b/src/runtime-rs/Makefile @@ -181,6 +181,7 @@ DEFVFIOMODE := guest-kernel DEFBINDMOUNTS := [] DEFDANCONF := /run/kata-containers/dans DEFFORCEGUESTPULL := false +QEMUTDXQUOTEGENERATIONSERVICESOCKETPORT := 4050 SED = sed CLI_DIR = cmd SHIMV2 = containerd-shim-kata-v2 @@ -510,6 +511,7 @@ USER_VARS += KATA_INSTALL_OWNER USER_VARS += KATA_INSTALL_CFG_PERMS USER_VARS += DEFDANCONF USER_VARS += DEFFORCEGUESTPULL +USER_VARS += QEMUTDXQUOTEGENERATIONSERVICESOCKETPORT SOURCES := \ $(shell find . 2>&1 | grep -E '.*\.rs$$') \ diff --git a/src/runtime-rs/config/configuration-qemu-runtime-rs.toml.in b/src/runtime-rs/config/configuration-qemu-runtime-rs.toml.in index 88d14a7252..d3f3bd2f20 100644 --- a/src/runtime-rs/config/configuration-qemu-runtime-rs.toml.in +++ b/src/runtime-rs/config/configuration-qemu-runtime-rs.toml.in @@ -446,6 +446,18 @@ valid_entropy_sources = @DEFVALIDENTROPYSOURCES@ # Warnings will be logged if any error is encountered while scanning for hooks, # but it will not abort container execution. #guest_hook_path = "/usr/share/oci/hooks" + +# Enable connection to Quote Generation Service (QGS) +# The "tdx_quote_generation_service_socket_port" parameter configures how QEMU connects to the TDX Quote Generation Service (QGS). +# This connection is essential for Trusted Domain (TD) attestation, as QGS signs the TDREPORT sent by QEMU via the GetQuote hypercall. +# By default QGS runs on vsock port 4050, but can be modified by the host admin. For QEMU's tdx-guest object, this connection needs to +# be specified in a JSON format, for example: +# -object '{"qom-type":"tdx-guest","id":"tdx","quote-generation-socket":{"type":"vsock","cid":"2","port":"4050"}}' +# It's important to note that setting "tdx_quote_generation_service_socket_port" to 0 enables communication via Unix Domain Sockets (UDS). +# To activate UDS, the QGS service itself must be launched with the "-port=0" parameter and the UDS will always be located at /var/run/tdx-qgs/qgs.socket. +# -object '{"qom-type":"tdx-guest","id":"tdx","quote-generation-socket":{"type":"unix","path":"/var/run/tdx-qgs/qgs.socket"}}' +# tdx_quote_generation_service_socket_port = @QEMUTDXQUOTEGENERATIONSERVICESOCKETPORT@ + # # Use rx Rate Limiter to control network I/O inbound bandwidth(size in bits/sec for SB/VM). # In Qemu, we use classful qdiscs HTB(Hierarchy Token Bucket) to discipline traffic. 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 ebd56c3a9b..09443323c5 100644 --- a/src/runtime-rs/crates/hypervisor/ch-config/src/convert.rs +++ b/src/runtime-rs/crates/hypervisor/ch-config/src/convert.rs @@ -549,7 +549,7 @@ fn get_platform_cfg(guest_protection_to_use: GuestProtection) -> Option bool { - matches!(guest_protection_to_use, GuestProtection::Tdx(_)) + matches!(guest_protection_to_use, GuestProtection::Tdx) } #[cfg(test)] mod tests { use super::*; - use kata_sys_util::protection::{SevSnpDetails, TDXDetails}; + use kata_sys_util::protection::SevSnpDetails; #[test] fn test_guest_protection_is_tdx() { - let tdx_details = TDXDetails { - major_version: 1, - minor_version: 0, - }; - let sev_snp_details = SevSnpDetails { cbitpos: 42 }; #[derive(Debug)] @@ -547,7 +542,7 @@ mod tests { result: false, }, TestData { - protection: GuestProtection::Tdx(tdx_details), + protection: GuestProtection::Tdx, result: true, }, ]; diff --git a/src/runtime-rs/crates/hypervisor/src/ch/inner.rs b/src/runtime-rs/crates/hypervisor/src/ch/inner.rs index c8f1c2d905..40f3c5d330 100644 --- a/src/runtime-rs/crates/hypervisor/src/ch/inner.rs +++ b/src/runtime-rs/crates/hypervisor/src/ch/inner.rs @@ -197,7 +197,6 @@ impl Persist for CloudHypervisorInner { #[cfg(test)] mod tests { use super::*; - use kata_sys_util::protection::TDXDetails; #[actix_rt::test] async fn test_save_clh() { @@ -209,12 +208,7 @@ mod tests { clh.vm_path = String::from("/opt/kata/bin/cloud-hypervisor"); clh.run_dir = String::from("/var/run/kata-containers/") + &clh.id; - let details = TDXDetails { - major_version: 1, - minor_version: 0, - }; - - clh.guest_protection_to_use = GuestProtection::Tdx(details); + clh.guest_protection_to_use = GuestProtection::Tdx; let state = clh.save().await.unwrap(); assert_eq!(state.id, clh.id); 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 41660ea14e..954ce30343 100644 --- a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs +++ b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs @@ -568,7 +568,7 @@ impl CloudHypervisorInner { if protection == GuestProtection::NoProtection { // User wants protection, but none available. return Err(anyhow!(GuestProtectionError::NoProtectionAvailable)); - } else if let GuestProtection::Tdx(_) = protection { + } else if let GuestProtection::Tdx = protection { info!(sl!(), "guest protection available and requested"; "guest-protection" => protection.to_string()); } else { return Err(anyhow!(GuestProtectionError::ExpectedTDXProtection( @@ -577,7 +577,7 @@ impl CloudHypervisorInner { } } else if protection == GuestProtection::NoProtection { debug!(sl!(), "no guest protection available"); - } else if let GuestProtection::Tdx(_) = protection { + } else if let GuestProtection::Tdx = protection { // CH requires TDX protection to be used. return Err(anyhow!(GuestProtectionError::TDXProtectionMustBeUsedWithCH)); } else { @@ -956,15 +956,13 @@ fn get_ch_vcpu_tids(proc_path: &str) -> Result> { #[cfg(test)] mod tests { use super::*; - use kata_sys_util::protection::{SevSnpDetails, TDXDetails}; + use kata_sys_util::protection::SevSnpDetails; #[cfg(target_arch = "x86_64")] - use kata_sys_util::protection::TDX_SYS_FIRMWARE_DIR; + use kata_sys_util::protection::TDX_KVM_PARAMETER_PATH; use kata_types::config::hypervisor::{Hypervisor as HypervisorConfig, SecurityInfo}; use serial_test::serial; - #[cfg(target_arch = "x86_64")] - use std::path::PathBuf; use test_utils::{assert_result, skip_if_not_root}; use std::fs::File; @@ -985,11 +983,6 @@ mod tests { // available_guest_protection() requires super user privs. skip_if_not_root!(); - let tdx_details = TDXDetails { - major_version: 1, - minor_version: 0, - }; - let sev_snp_details = SevSnpDetails { cbitpos: 42 }; #[derive(Debug)] @@ -1020,8 +1013,8 @@ mod tests { result: Ok(GuestProtection::Snp(sev_snp_details.clone())), }, TestData { - value: Some(GuestProtection::Tdx(tdx_details.clone())), - result: Ok(GuestProtection::Tdx(tdx_details.clone())), + value: Some(GuestProtection::Tdx), + result: Ok(GuestProtection::Tdx), }, ]; @@ -1055,26 +1048,11 @@ mod tests { // available_guest_protection() requires super user privs. skip_if_not_root!(); - let tdx_details = TDXDetails { - major_version: 1, - minor_version: 0, - }; - // Use the hosts protection, not a fake one. set_fake_guest_protection(None); - 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 have_tdx = fs::read(TDX_KVM_PARAMETER_PATH) + .map_or(false, |content| !content.is_empty() && content[0] == b'Y'); let protection = task::spawn_blocking(|| -> Result { get_guest_protection() }) @@ -1083,16 +1061,13 @@ mod tests { .unwrap(); if std::env::var("DEBUG").is_ok() { - let msg = format!( - "tdx_fw_path: {:?}, have_tdx: {:?}, protection: {:?}", - tdx_fw_path, have_tdx, protection - ); + let msg = format!("have_tdx: {:?}, protection: {:?}", have_tdx, protection); eprintln!("DEBUG: {}", msg); } if have_tdx { - assert_eq!(protection, GuestProtection::Tdx(tdx_details)); + assert_eq!(protection, GuestProtection::Tdx); } else { assert_eq!(protection, GuestProtection::NoProtection); } @@ -1115,11 +1090,6 @@ mod tests { guest_protection_to_use: GuestProtection, } - let tdx_details = TDXDetails { - major_version: 1, - minor_version: 0, - }; - let tests = &[ TestData { confidential_guest: false, @@ -1135,15 +1105,15 @@ mod tests { }, TestData { confidential_guest: false, - available_protection: Some(GuestProtection::Tdx(tdx_details.clone())), + available_protection: Some(GuestProtection::Tdx), result: Err(anyhow!(GuestProtectionError::TDXProtectionMustBeUsedWithCH)), - guest_protection_to_use: GuestProtection::Tdx(tdx_details.clone()), + guest_protection_to_use: GuestProtection::Tdx, }, TestData { confidential_guest: true, - available_protection: Some(GuestProtection::Tdx(tdx_details.clone())), + available_protection: Some(GuestProtection::Tdx), result: Ok(()), - guest_protection_to_use: GuestProtection::Tdx(tdx_details), + guest_protection_to_use: GuestProtection::Tdx, }, TestData { confidential_guest: false, diff --git a/src/runtime-rs/crates/hypervisor/src/device/driver/mod.rs b/src/runtime-rs/crates/hypervisor/src/device/driver/mod.rs index e10d66c005..d1069dade7 100644 --- a/src/runtime-rs/crates/hypervisor/src/device/driver/mod.rs +++ b/src/runtime-rs/crates/hypervisor/src/device/driver/mod.rs @@ -16,7 +16,7 @@ mod virtio_net; mod virtio_vsock; pub use port_device::{PCIePortDevice, PortDeviceConfig}; -pub use protection_device::{ProtectionDevice, ProtectionDeviceConfig, SevSnpConfig}; +pub use protection_device::{ProtectionDevice, ProtectionDeviceConfig, SevSnpConfig, TdxConfig}; pub use vfio::{ bind_device_to_host, bind_device_to_vfio, get_vfio_device, HostDevice, VfioBusMode, VfioConfig, VfioDevice, diff --git a/src/runtime-rs/crates/hypervisor/src/device/driver/protection_device.rs b/src/runtime-rs/crates/hypervisor/src/device/driver/protection_device.rs index 33fa82c38b..83d0718967 100644 --- a/src/runtime-rs/crates/hypervisor/src/device/driver/protection_device.rs +++ b/src/runtime-rs/crates/hypervisor/src/device/driver/protection_device.rs @@ -14,6 +14,7 @@ use async_trait::async_trait; pub enum ProtectionDeviceConfig { SevSnp(SevSnpConfig), Se, + Tdx(TdxConfig), } #[derive(Debug, Clone)] @@ -23,6 +24,20 @@ pub struct SevSnpConfig { pub firmware: String, } +#[derive(Debug, Clone)] +pub struct TdxConfig { + // Object ID + pub id: String, + // Firmware path + pub firmware: String, + // Quote Qeneration Socket port + pub qgs_port: u32, + // mrconfigid + pub mrconfigid: Option, + // Debug mode + pub debug: bool, +} + #[derive(Debug, Clone)] pub struct ProtectionDevice { pub device_id: String, diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs b/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs index 384753dd9e..41475a3bbe 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/cmdline_generator.rs @@ -4,12 +4,15 @@ // use crate::device::topology::{PCIePortBusPrefix, TopologyPortDevice, DEFAULT_PCIE_ROOT_BUS}; -use crate::utils::{clear_cloexec, create_vhost_net_fds, open_named_tuntap}; +use crate::utils::{clear_cloexec, create_vhost_net_fds, open_named_tuntap, SocketAddress}; + use crate::{kernel_param::KernelParams, Address, HypervisorConfig}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use kata_types::config::hypervisor::VIRTIO_SCSI; +use serde::{Deserialize, Serialize}; +use serde_json; use std::collections::HashMap; use std::fmt::Display; use std::fs::{read_to_string, File}; @@ -1825,6 +1828,87 @@ impl ToQemuParams for ObjectSevSnpGuest { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct ObjectTdxGuest { + // QOM Object type + qom_type: String, + + // unique ID + id: String, + + // The sept-ve-disable option prevents EPT violation conversions to #VE on guest TD + // accesses of PENDING pages, which is essential for certain guest OS compatibility, + // like Linux TD guests. + sept_ve_disable: bool, + + // Base64 encoded 48 bytes of data (e.g., a sha384 digest). + // ID for non-owner-defined configuration of the guest TD, which identifies the guest TD's run-time/OS configuration via a SHA384 digest. + // Defaults to zero if unspecified. + #[serde(skip_serializing_if = "Option::is_none")] + mrconfigid: Option, + + // Base64 encoded 48 bytes of data (e.g., a sha384 digest). ID for the guest TD's owner. + // Defaults to all zeros. + #[serde(skip_serializing_if = "Option::is_none")] + mrowner: Option, + + // Base64 encoded 48 bytes of data (e.g., a sha384 digest). + // ID for owner-defined configuration of the guest TD, e.g., specific to the workload rather than the run-time or OS. + // Defaults to all zeros. + #[serde(skip_serializing_if = "Option::is_none")] + mrownerconfig: Option, + + // Quote generation socket. + #[serde(skip_serializing_if = "Option::is_none")] + quote_generation_socket: Option, + + // Debug mode + #[serde(skip_serializing_if = "Option::is_none")] + debug: Option, +} + +impl std::fmt::Display for ObjectTdxGuest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + serde_json::to_string(self) + .map_err(|_| std::fmt::Error) + .and_then(|s| write!(f, "{}", s)) + } +} + +#[allow(clippy::doc_lazy_continuation)] +/// 1. Add property "quote-generation-socket" to tdx-guest +/// https://lore.kernel.org/qemu-devel/Zv7dtghi20DZ9ozz@redhat.com/ +/// 2. Support user configurable mrconfigid/mrowner/mrownerconfig +/// https://patchew.org/QEMU/20241105062408.3533704-1-xiaoyao.li@intel.com/20241105062408.3533704-15-xiaoyao.li@intel.com/ +/// 3. Add command line and validation for TDX type +/// https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/6N7KP5F5Z44NI3R5U7STSPWUYXK6QYUO/ +/// Example: +/// -object { "qom-type": "tdx-guest","id": "tdx", "mrconfigid": "mrconfigid2", "debug":true,"sept-ve-disable":true, \ +/// "quote-generation-socket": { "type": "vsock","cid": "2","port": "4050" }} +impl ObjectTdxGuest { + pub fn new(id: &str, mrconfigid: Option, qgs_port: u32, debug: bool) -> Self { + let qgs_socket = SocketAddress::new(qgs_port); + Self { + qom_type: "tdx-guest".to_owned(), + id: id.to_owned(), + mrconfigid, + mrowner: None, + mrownerconfig: None, + sept_ve_disable: true, + quote_generation_socket: Some(qgs_socket), + debug: if debug { Some(debug) } else { None }, + } + } +} + +#[async_trait] +impl ToQemuParams for ObjectTdxGuest { + async fn qemu_params(&self) -> Result> { + Ok(vec!["-object".to_owned(), self.to_string()]) + } +} + /// PCIeRootPortDevice directly attached onto the root bus /// -device pcie-root-port,id=rp0,bus=pcie.0,chassis=0,slot=0,multifunction=off,pref64-reserve=B,mem-reserve=B #[derive(Debug, Default)] @@ -2354,6 +2438,23 @@ impl<'a> QemuCmdLine<'a> { self.cpu.set_type("EPYC-v4"); } + pub fn add_tdx_protection_device( + &mut self, + id: &str, + firmware: &str, + qgs_port: u32, + mrconfigid: &Option, + debug: bool, + ) { + let tdx_object = ObjectTdxGuest::new(id, mrconfigid.clone(), qgs_port, debug); + self.devices.push(Box::new(tdx_object)); + self.devices.push(Box::new(Bios::new(firmware.to_owned()))); + + self.machine + .set_confidential_guest_support("tdx") + .set_nvdimm(false); + } + /// Note: add_pcie_root_port and add_pcie_switch_port follow kata-runtime's related implementations of vfio devices. /// The design origins from https://github.com/qemu/qemu/blob/master/docs/pcie.txt /// diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs index 123067ea67..98cec4d8ba 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs @@ -145,6 +145,13 @@ impl QemuInner { } } ProtectionDeviceConfig::Se => cmdline.add_se_protection_device(), + ProtectionDeviceConfig::Tdx(tdx_config) => cmdline.add_tdx_protection_device( + &tdx_config.id, + &tdx_config.firmware, + tdx_config.qgs_port, + &tdx_config.mrconfigid, + tdx_config.debug, + ), }, DeviceType::PortDevice(port_device) => { let port_type = port_device.config.port_type; diff --git a/src/runtime-rs/crates/hypervisor/src/utils.rs b/src/runtime-rs/crates/hypervisor/src/utils.rs index 6a0e41bc00..d1fe47a5f1 100644 --- a/src/runtime-rs/crates/hypervisor/src/utils.rs +++ b/src/runtime-rs/crates/hypervisor/src/utils.rs @@ -16,6 +16,8 @@ use nix::{ fcntl, sched::{setns, CloneFlags}, }; +use serde::{Deserialize, Serialize}; +use serde_json; use crate::device::Tap; @@ -144,9 +146,56 @@ fn create_fds(device: &str, num_fds: usize) -> Result> { Ok(fds) } +// QGS_SOCKET_PATH: the Unix Domain Socket Path served by Intel TDX Quote Generation Service +const QGS_SOCKET_PATH: &str = "/var/run/tdx-qgs/qgs.socket"; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SocketAddress { + #[serde(rename = "type")] + pub typ: String, + + #[serde(rename = "cid", skip_serializing_if = "String::is_empty")] + pub cid: String, + + #[serde(rename = "port", skip_serializing_if = "String::is_empty")] + pub port: String, + + #[serde(rename = "path", skip_serializing_if = "String::is_empty")] + pub path: String, +} + +impl SocketAddress { + pub fn new(port: u32) -> Self { + if port == 0 { + Self { + typ: "unix".to_string(), + cid: "".to_string(), + port: "".to_string(), + path: QGS_SOCKET_PATH.to_string(), + } + } else { + Self { + typ: "vsock".to_string(), + cid: format!("{}", 2), + port: port.to_string(), + path: "".to_string(), + } + } + } +} + +impl std::fmt::Display for SocketAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + serde_json::to_string(self) + .map_err(|_| std::fmt::Error) + .and_then(|s| write!(f, "{}", s)) + } +} + #[cfg(test)] mod tests { use super::create_fds; + use super::SocketAddress; #[test] fn test_ctreate_fds() { @@ -156,4 +205,43 @@ mod tests { assert!(fds.is_ok()); assert_eq!(fds.unwrap().len(), num_fds); } + + #[test] + fn test_vsocket_address_new() { + let socket = SocketAddress::new(8866); + assert_eq!(socket.typ, "vsock"); + assert_eq!(socket.cid, "2"); + assert_eq!(socket.port, "8866"); + } + + #[test] + fn test_unix_address_new() { + let socket = SocketAddress::new(0); + assert_eq!(socket.typ, "unix"); + assert_eq!(socket.path, "/var/run/tdx-qgs/qgs.socket"); + } + + #[test] + fn test_socket_address_display() { + let socket = SocketAddress::new(6688); + let expected_json = r#"{"type":"vsock","cid":"2","port":"6688"}"#; + assert_eq!(format!("{}", socket), expected_json); + } + + #[test] + fn test_socket_address_serialize_deserialize() { + let socket = SocketAddress::new(0); + let serialized = serde_json::to_string(&socket).unwrap(); + let expected_json = r#"{"type":"unix","path":"/var/run/tdx-qgs/qgs.socket"}"#; + assert_eq!(expected_json, serialized); + } + + #[test] + fn test_socket_address_kebab_case() { + let socket = SocketAddress::new(6868); + let serialized = serde_json::to_string(&socket).unwrap(); + assert!(serialized.contains(r#""type":"#)); + assert!(serialized.contains(r#""cid":"#)); + assert!(serialized.contains(r#""port":"#)); + } } diff --git a/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs b/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs index 8864bade94..bf504f394b 100644 --- a/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs +++ b/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs @@ -30,7 +30,7 @@ use hypervisor::{dragonball::Dragonball, HYPERVISOR_DRAGONBALL}; use hypervisor::{qemu::Qemu, HYPERVISOR_QEMU}; use hypervisor::{utils::get_hvsock_path, HybridVsockConfig, DEFAULT_GUEST_VSOCK_CID}; use hypervisor::{BlockConfig, Hypervisor}; -use hypervisor::{ProtectionDeviceConfig, SevSnpConfig}; +use hypervisor::{ProtectionDeviceConfig, SevSnpConfig, TdxConfig}; use kata_sys_util::hooks::HookStates; use kata_sys_util::protection::{available_guest_protection, GuestProtection}; use kata_types::capabilities::CapabilityBits; @@ -398,6 +398,15 @@ impl VirtSandbox { GuestProtection::Se => { Ok(Some(ProtectionDeviceConfig::Se)) } + GuestProtection::Tdx => { + Ok(Some(ProtectionDeviceConfig::Tdx(TdxConfig { + id: "tdx".to_owned(), + firmware: hypervisor_config.boot_info.firmware.clone(), + qgs_port: hypervisor_config.security_info.qgs_port, + mrconfigid: None, + debug: false, + }))) + }, _ => Err(anyhow!("confidential_guest requested by configuration but no supported protection available")) } }