From a24dbdc781d6c9c880c1907d99f24981e7ade7d5 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Wed, 5 Jul 2023 19:08:18 +0530 Subject: [PATCH] kata-sys-util: Move utilities to get platform protection Add utilities to get platform protection to kata-sys-util Fixes: #7144 Signed-off-by: Archana Shinde --- src/libs/kata-sys-util/src/lib.rs | 43 ++++ src/libs/kata-sys-util/src/protection.rs | 270 +++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 src/libs/kata-sys-util/src/protection.rs diff --git a/src/libs/kata-sys-util/src/lib.rs b/src/libs/kata-sys-util/src/lib.rs index f7dad3290..e91704b0b 100644 --- a/src/libs/kata-sys-util/src/lib.rs +++ b/src/libs/kata-sys-util/src/lib.rs @@ -13,10 +13,15 @@ pub mod hooks; pub mod k8s; pub mod mount; pub mod numa; +pub mod protection; pub mod rand; pub mod spec; pub mod validate; +use anyhow::Result; +use std::io::BufRead; +use std::io::BufReader; + // Convenience macro to obtain the scoped logger #[macro_export] macro_rules! sl { @@ -32,3 +37,41 @@ macro_rules! eother { std::io::Error::new(std::io::ErrorKind::Other, format!($fmt, $($arg)*)) }) } + +pub fn check_kernel_cmd_line( + kernel_cmdline_path: &str, + search_param: &str, + search_values: &[&str], +) -> Result { + let f = std::fs::File::open(kernel_cmdline_path)?; + let reader = BufReader::new(f); + + let check_fn = if search_values.is_empty() { + |param: &str, search_param: &str, _search_values: &[&str]| { + return param.eq_ignore_ascii_case(search_param); + } + } else { + |param: &str, search_param: &str, search_values: &[&str]| { + let split: Vec<&str> = param.splitn(2, "=").collect(); + if split.len() < 2 || split[0] != search_param { + return false; + } + + for value in search_values { + if value.eq_ignore_ascii_case(split[1]) { + return true; + } + } + false + } + }; + + for line in reader.lines() { + for field in line?.split_whitespace() { + if check_fn(field, search_param, search_values) { + return Ok(true); + } + } + } + Ok(false) +} diff --git a/src/libs/kata-sys-util/src/protection.rs b/src/libs/kata-sys-util/src/protection.rs new file mode 100644 index 000000000..02b356d33 --- /dev/null +++ b/src/libs/kata-sys-util/src/protection.rs @@ -0,0 +1,270 @@ +// Copyright (c) 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +#[cfg(target_arch = "x86_64")] +use anyhow::anyhow; +#[cfg(any(target_arch = "s390x", target_arch = "x86_64", target_arch = "aarch64"))] +use anyhow::Result; +use std::fmt; +#[cfg(target_arch = "x86_64")] +use std::path::Path; +use thiserror::Error; + +#[cfg(any(target_arch = "s390x", target_arch = "x86_64"))] +use nix::unistd::Uid; + +#[cfg(target_arch = "x86_64")] +use std::fs; + +#[allow(dead_code)] +#[derive(Debug, PartialEq)] +pub enum GuestProtection { + NoProtection, + Tdx, + Sev, + Snp, + Pef, + Se, +} + +impl fmt::Display for GuestProtection { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + GuestProtection::Tdx => write!(f, "tdx"), + GuestProtection::Sev => write!(f, "sev"), + GuestProtection::Snp => write!(f, "snp"), + GuestProtection::Pef => write!(f, "pef"), + GuestProtection::Se => write!(f, "se"), + GuestProtection::NoProtection => write!(f, "none"), + } + } +} + +#[allow(dead_code)] +#[derive(Error, Debug)] +pub enum ProtectionError { + #[error("No permission to check guest protection")] + NoPerms, + + #[error("Failed to check guest protection: {0}")] + CheckFailed(String), + + #[error("Invalid guest protection value: {0}")] + InvalidValue(String), +} + +#[cfg(target_arch = "x86_64")] +pub const TDX_SYS_FIRMWARE_DIR: &str = "/sys/firmware/tdx_seam/"; +#[cfg(target_arch = "x86_64")] +pub const TDX_CPU_FLAG: &str = "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"; + +#[cfg(target_arch = "x86_64")] +pub fn available_guest_protection() -> Result { + if !Uid::effective().is_root() { + return Err(ProtectionError::NoPerms); + } + + arch_guest_protection( + TDX_SYS_FIRMWARE_DIR, + TDX_CPU_FLAG, + SEV_KVM_PARAMETER_PATH, + SNP_KVM_PARAMETER_PATH, + ) +} + +#[cfg(target_arch = "x86_64")] +fn retrieve_cpu_flags() -> Result { + let cpu_info = + crate::cpu::get_single_cpu_info(crate::cpu::PROC_CPUINFO, crate::cpu::CPUINFO_DELIMITER)?; + + let cpu_flags = + crate::cpu::get_cpu_flags(&cpu_info, crate::cpu::CPUINFO_FLAGS_TAG).map_err(|e| { + anyhow!( + "Error parsing CPU flags, file {:?}, {:?}", + crate::cpu::PROC_CPUINFO, + e + ) + })?; + + Ok(cpu_flags) +} + +#[cfg(target_arch = "x86_64")] +pub fn arch_guest_protection( + tdx_path: &str, + tdx_flag: &str, + sev_path: &str, + snp_path: &str, +) -> Result { + let flags = + retrieve_cpu_flags().map_err(|err| ProtectionError::CheckFailed(err.to_string()))?; + + let metadata = fs::metadata(tdx_path); + + if metadata.is_ok() && metadata.unwrap().is_dir() && flags.contains(tdx_flag) { + return Ok(GuestProtection::Tdx); + } + + let check_contents = |file_name: &str| -> Result { + let file_path = Path::new(file_name); + if !file_path.exists() { + return Ok(false); + } + + let contents = fs::read_to_string(file_name).map_err(|err| { + ProtectionError::CheckFailed(format!("Error reading file {} : {}", file_name, err)) + })?; + + if contents.trim() == "Y" { + return Ok(true); + } + Ok(false) + }; + + if check_contents(snp_path)? { + return Ok(GuestProtection::Snp); + } + + if check_contents(sev_path)? { + return Ok(GuestProtection::Sev); + } + + Ok(GuestProtection::NoProtection) +} + +#[cfg(target_arch = "s390x")] +#[allow(dead_code)] +// Guest protection is not supported on ARM64. +pub fn available_guest_protection() -> Result { + if !Uid::effective().is_root() { + return Err(ProtectionError::NoPerms)?; + } + + let facilities = crate::cpu::retrieve_cpu_facilities().map_err(|err| { + ProtectionError::CheckFailed(format!( + "Error retrieving cpu facilities file : {}", + err.to_string() + )) + })?; + + // Secure Execution + // https://www.kernel.org/doc/html/latest/virt/kvm/s390-pv.html + let se_cpu_facility_bit: i32 = 158; + if !facilities.contains_key(&se_cpu_facility_bit) { + return Ok(GuestProtection::NoProtection); + } + + let cmd_line_values = vec!["1", "on", "y", "yes"]; + let se_cmdline_param = "prot_virt"; + + let se_cmdline_present = + crate::check_kernel_cmd_line("/proc/cmdline", se_cmdline_param, &cmd_line_values) + .map_err(|err| ProtectionError::CheckFailed(err.to_string()))?; + + if !se_cmdline_present { + return Err(ProtectionError::InvalidValue(String::from( + "Protected Virtualization is not enabled on kernel command line!", + ))); + } + + Ok(GuestProtection::Se) +} + +#[cfg(target_arch = "powerpc64le")] +pub fn available_guest_protection() -> Result { + if !Uid::effective().is_root() { + return Err(check::ProtectionError::NoPerms); + } + + let metadata = fs::metadata(PEF_SYS_FIRMWARE_DIR); + if metadata.is_ok() && metadata.unwrap().is_dir() { + Ok(check::GuestProtection::Pef) + } + + Ok(check::GuestProtection::NoProtection) +} + +#[cfg(target_arch = "aarch64")] +#[allow(dead_code)] +// Guest protection is not supported on ARM64. +pub fn available_guest_protection() -> Result { + Ok(GuestProtection::NoProtection) +} + +#[cfg(target_arch = "x86_64")] +#[cfg(test)] +mod tests { + use super::*; + use nix::unistd::Uid; + use std::fs; + use std::io::Write; + use tempfile::tempdir; + + #[test] + fn test_available_guest_protection_no_privileges() { + if !Uid::effective().is_root() { + let res = available_guest_protection(); + assert!(res.is_err()); + assert_eq!( + "No permission to check guest protection", + res.unwrap_err().to_string() + ); + } + } + + #[test] + fn test_arch_guest_protection_snp() { + // Test snp + let dir = tempdir().unwrap(); + let snp_file_path = dir.path().join("sev_snp"); + let path = snp_file_path.clone(); + let mut snp_file = fs::File::create(snp_file_path).unwrap(); + writeln!(snp_file, "Y").unwrap(); + + let actual = + arch_guest_protection("/xyz/tmp", TDX_CPU_FLAG, "/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", TDX_CPU_FLAG, "/xyz/tmp", path.to_str().unwrap()); + assert!(actual.is_ok()); + assert_eq!(actual.unwrap(), GuestProtection::NoProtection); + } + + #[test] + fn test_arch_guest_protection_sev() { + // Test sev + let dir = tempdir().unwrap(); + let sev_file_path = dir.path().join("sev"); + let sev_path = sev_file_path.clone(); + let mut sev_file = fs::File::create(sev_file_path).unwrap(); + writeln!(sev_file, "Y").unwrap(); + + let actual = arch_guest_protection( + "/xyz/tmp", + TDX_CPU_FLAG, + 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", + TDX_CPU_FLAG, + sev_path.to_str().unwrap(), + "/xyz/tmp", + ); + assert!(actual.is_ok()); + assert_eq!(actual.unwrap(), GuestProtection::NoProtection); + } +}