Merge pull request #6257 from amshinde/kata-ctl-env

kata-ctl: add function to get platform protection.
This commit is contained in:
Archana Shinde 2023-03-29 11:55:07 -07:00 committed by GitHub
commit 07e49c63e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 347 additions and 0 deletions

View File

@ -49,5 +49,6 @@ 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" }
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" }

View File

@ -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<check::GuestProtection, check::ProtectionError> {
Ok(check::GuestProtection::NoProtection)
}
}

View File

@ -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<check::GuestProtection, check::ProtectionError> {
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)
}
}

View File

@ -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<HashMap<i32, bool>> {
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::<i32>()?;
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<bool> {
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<check::GuestProtection, check::ProtectionError> {
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)
}
}

View File

@ -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<String> {
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<GuestProtection, ProtectionError> {
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<GuestProtection, ProtectionError> {
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<bool, ProtectionError> {
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);
}
}

View File

@ -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,
@ -125,6 +126,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(())
}

View File

@ -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<bool> {
let metadata = fs::metadata(vsock_path)?;
Ok(metadata.is_file())
}
#[cfg(test)]
mod tests {
use super::*;
@ -287,4 +293,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();
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());
}
}