From d3bb25418875d2cbb329812b6aab28819ba7a1f3 Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Fri, 3 Feb 2023 15:41:59 -0800 Subject: [PATCH 1/2] utils: Add function to check vhost-vsock Add function to check if the host-system has the vhost-vsock kernel module. Signed-off-by: Archana Shinde --- src/tools/kata-ctl/src/utils.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/tools/kata-ctl/src/utils.rs b/src/tools/kata-ctl/src/utils.rs index b1564f4c4..9c92f82bf 100644 --- a/src/tools/kata-ctl/src/utils.rs +++ b/src/tools/kata-ctl/src/utils.rs @@ -144,6 +144,12 @@ pub fn get_generic_cpu_details(cpu_info_file: &str) -> Result<(String, String)> Ok((vendor, model)) } +const VHOST_VSOCK_DEVICE: &str = "/dev/vhost-vsock"; +pub fn supports_vsocks(vsock_path: &str) -> Result { + let metadata = fs::metadata(vsock_path)?; + Ok(metadata.is_file()) +} + #[cfg(test)] mod tests { use super::*; @@ -283,4 +289,30 @@ mod tests { ); assert_eq!(actual, expected); } + + #[test] + fn check_supports_vsocks_valid() { + let dir = tempdir().unwrap(); + let file_path = dir.path().join("vhost-vsock"); + let path = file_path.clone(); + let _file = fs::File::create(file_path).unwrap(); + let res = supports_vsocks(path.to_str().unwrap()).unwrap(); + assert!(res); + } + + #[test] + fn check_supports_vsocks_dir() { + let dir = tempdir().unwrap(); + let file_path = dir.path().join("vhost-vsock"); + let path = file_path.clone(); + let _dir = fs::create_dir(file_path).unwrap(); + let res = supports_vsocks(path.to_str().unwrap()).unwrap(); + assert!(!res); + } + + #[test] + fn check_supports_vsocks_missing_file() { + let res = supports_vsocks("/xyz/vhost-vsock"); + assert!(res.is_err()); + } } From a914283ce03869b3ef78ea833c684932d366052d Mon Sep 17 00:00:00 2001 From: Archana Shinde Date: Wed, 8 Feb 2023 00:33:43 -0800 Subject: [PATCH 2/2] kata-ctl: add function to get platform protection. This function checks for tdx, sev or snp protection on x86 platform. Fixes: #1000 Signed-off-by: Archana Shinde --- src/tools/kata-ctl/Cargo.toml | 1 + src/tools/kata-ctl/src/arch/aarch64/mod.rs | 7 + .../kata-ctl/src/arch/powerpc64le/mod.rs | 16 ++ src/tools/kata-ctl/src/arch/s390x/mod.rs | 115 +++++++++++++ src/tools/kata-ctl/src/arch/x86_64/mod.rs | 151 ++++++++++++++++++ src/tools/kata-ctl/src/check.rs | 25 +++ src/tools/kata-ctl/src/utils.rs | 2 +- 7 files changed, 316 insertions(+), 1 deletion(-) diff --git a/src/tools/kata-ctl/Cargo.toml b/src/tools/kata-ctl/Cargo.toml index cc32054d2..48a0b2794 100644 --- a/src/tools/kata-ctl/Cargo.toml +++ b/src/tools/kata-ctl/Cargo.toml @@ -40,4 +40,5 @@ reqwest = { version = "0.11", default-features = false, features = ["json", "blo [dev-dependencies] semver = "1.0.12" tempfile = "3.1.0" +nix = "0.25.0" test-utils = { path = "../../libs/test-utils" } diff --git a/src/tools/kata-ctl/src/arch/aarch64/mod.rs b/src/tools/kata-ctl/src/arch/aarch64/mod.rs index 7966123bd..65e3ed93f 100644 --- a/src/tools/kata-ctl/src/arch/aarch64/mod.rs +++ b/src/tools/kata-ctl/src/arch/aarch64/mod.rs @@ -7,6 +7,7 @@ pub use arch_specific::*; mod arch_specific { + use crate::check; use crate::types::*; use anyhow::Result; use std::path::Path; @@ -39,4 +40,10 @@ mod arch_specific { pub fn get_checks() -> Option<&'static [CheckItem<'static>]> { Some(CHECK_LIST) } + + #[allow(dead_code)] + // Guest protection is not supported on ARM64. + pub fn available_guest_protection() -> Result { + Ok(check::GuestProtection::NoProtection) + } } diff --git a/src/tools/kata-ctl/src/arch/powerpc64le/mod.rs b/src/tools/kata-ctl/src/arch/powerpc64le/mod.rs index 1cc49d70c..8290dbb13 100644 --- a/src/tools/kata-ctl/src/arch/powerpc64le/mod.rs +++ b/src/tools/kata-ctl/src/arch/powerpc64le/mod.rs @@ -8,6 +8,7 @@ use crate::types::*; pub use arch_specific::*; mod arch_specific { + use crate::check; use anyhow::Result; pub const ARCH_CPU_VENDOR_FIELD: &str = ""; @@ -20,4 +21,19 @@ mod arch_specific { pub fn get_checks() -> Option<&'static [CheckItem<'static>]> { None } + + const PEF_SYS_FIRMWARE_DIR: &str = "/sys/firmware/ultravisor/"; + + 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) + } } diff --git a/src/tools/kata-ctl/src/arch/s390x/mod.rs b/src/tools/kata-ctl/src/arch/s390x/mod.rs index b3196547b..276c75a9e 100644 --- a/src/tools/kata-ctl/src/arch/s390x/mod.rs +++ b/src/tools/kata-ctl/src/arch/s390x/mod.rs @@ -11,6 +11,10 @@ mod arch_specific { use crate::check; use crate::types::*; use anyhow::{anyhow, Result}; + use nix::unistd::Uid; + use std::collections::HashMap; + use std::io::BufRead; + use std::io::BufReader; const CPUINFO_DELIMITER: &str = "processor "; const CPUINFO_FEATURES_TAG: &str = "features"; @@ -65,4 +69,115 @@ mod arch_specific { pub fn get_checks() -> Option<&'static [CheckItem<'static>]> { Some(CHECK_LIST) } + + #[allow(dead_code)] + fn retrieve_cpu_facilities() -> Result> { + let f = std::fs::File::open(check::PROC_CPUINFO)?; + let mut reader = BufReader::new(f); + let mut contents = String::new(); + let facilities_field = "facilities"; + let mut facilities = HashMap::new(); + + while reader.read_line(&mut contents)? > 0 { + let fields: Vec<&str> = contents.split_whitespace().collect(); + if fields.len() < 2 { + contents.clear(); + continue; + } + + if !fields[0].starts_with(facilities_field) { + contents.clear(); + continue; + } + + let mut start = 1; + if fields[1] == ":" { + start = 2; + } + + for field in fields.iter().skip(start) { + let bit = field.parse::()?; + facilities.insert(bit, true); + } + return Ok(facilities); + } + + Ok(facilities) + } + + #[allow(dead_code)] + pub fn check_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) + } + + #[allow(dead_code)] + // Guest protection is not supported on ARM64. + pub fn available_guest_protection() -> Result { + if !Uid::effective().is_root() { + return Err(check::ProtectionError::NoPerms)?; + } + + let facilities = retrieve_cpu_facilities().map_err(|err| { + check::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(check::GuestProtection::NoProtection); + } + + let cmd_line_values = vec!["1", "on", "y", "yes"]; + let se_cmdline_param = "prot_virt"; + + let se_cmdline_present = + check_cmd_line("/proc/cmdline", se_cmdline_param, &cmd_line_values) + .map_err(|err| check::ProtectionError::CheckFailed(err.to_string()))?; + + if !se_cmdline_present { + return Err(check::ProtectionError::InvalidValue(String::from( + "Protected Virtualization is not enabled on kernel command line!", + ))); + } + + Ok(check::GuestProtection::Se) + } } diff --git a/src/tools/kata-ctl/src/arch/x86_64/mod.rs b/src/tools/kata-ctl/src/arch/x86_64/mod.rs index 924536a13..9c8782fa7 100644 --- a/src/tools/kata-ctl/src/arch/x86_64/mod.rs +++ b/src/tools/kata-ctl/src/arch/x86_64/mod.rs @@ -3,13 +3,19 @@ // SPDX-License-Identifier: Apache-2.0 // +#![allow(dead_code)] + #[cfg(target_arch = "x86_64")] pub use arch_specific::*; mod arch_specific { use crate::check; + use crate::check::{GuestProtection, ProtectionError}; use crate::types::*; use anyhow::{anyhow, Result}; + use nix::unistd::Uid; + use std::fs; + use std::path::Path; const CPUINFO_DELIMITER: &str = "\nprocessor"; const CPUINFO_FLAGS_TAG: &str = "flags"; @@ -61,4 +67,149 @@ mod arch_specific { Ok(()) } + + fn retrieve_cpu_flags() -> Result { + let cpu_info = check::get_single_cpu_info(check::PROC_CPUINFO, CPUINFO_DELIMITER)?; + + let cpu_flags = check::get_cpu_flags(&cpu_info, CPUINFO_FLAGS_TAG).map_err(|e| { + anyhow!( + "Error parsing CPU flags, file {:?}, {:?}", + check::PROC_CPUINFO, + e + ) + })?; + + Ok(cpu_flags) + } + + pub const TDX_SYS_FIRMWARE_DIR: &str = "/sys/firmware/tdx_seam/"; + pub const TDX_CPU_FLAG: &str = "tdx"; + pub const SEV_KVM_PARAMETER_PATH: &str = "/sys/module/kvm_amd/parameters/sev"; + pub const SNP_KVM_PARAMETER_PATH: &str = "/sys/module/kvm_amd/parameters/sev_snp"; + + 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, + ) + } + + 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 == "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 = "x86_64")] +#[cfg(test)] +mod tests { + use super::*; + use crate::check; + 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() + ); + } + } + + 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(), check::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(), check::GuestProtection::NoProtection); + } + + 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(), check::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(), check::GuestProtection::NoProtection); + } } diff --git a/src/tools/kata-ctl/src/check.rs b/src/tools/kata-ctl/src/check.rs index 81b9b83a7..a39c3e61f 100644 --- a/src/tools/kata-ctl/src/check.rs +++ b/src/tools/kata-ctl/src/check.rs @@ -8,6 +8,7 @@ use anyhow::{anyhow, Result}; use reqwest::header::{CONTENT_TYPE, USER_AGENT}; use serde::{Deserialize, Serialize}; +use thiserror::Error; #[derive(Debug, Deserialize, Serialize, PartialEq)] struct Release { tag_name: String, @@ -118,6 +119,30 @@ pub fn check_cpu_attribs( Ok(missing_attribs) } +#[allow(dead_code)] +#[derive(Debug, PartialEq)] +pub enum GuestProtection { + NoProtection, + Tdx, + Sev, + Snp, + Pef, + Se, +} + +#[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), +} + pub fn run_network_checks() -> Result<()> { Ok(()) } diff --git a/src/tools/kata-ctl/src/utils.rs b/src/tools/kata-ctl/src/utils.rs index 9c92f82bf..6252271c8 100644 --- a/src/tools/kata-ctl/src/utils.rs +++ b/src/tools/kata-ctl/src/utils.rs @@ -305,7 +305,7 @@ mod tests { let dir = tempdir().unwrap(); let file_path = dir.path().join("vhost-vsock"); let path = file_path.clone(); - let _dir = fs::create_dir(file_path).unwrap(); + fs::create_dir(file_path).unwrap(); let res = supports_vsocks(path.to_str().unwrap()).unwrap(); assert!(!res); }