mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-06-26 07:22:20 +00:00
Merge pull request #6295 from dborquez/add_kernel_module_checks_kvm
kata-ctl: checks for kvm, kvm_intel modules loaded
This commit is contained in:
commit
7fdaab49bc
@ -20,7 +20,7 @@ mod arch_specific {
|
||||
|
||||
// List of check functions
|
||||
static CHECK_LIST: &[CheckItem] = &[CheckItem {
|
||||
name: CheckType::CheckCpu,
|
||||
name: CheckType::Cpu,
|
||||
descr: "This parameter performs the host check",
|
||||
fp: check,
|
||||
perm: PermissionType::NonPrivileged,
|
||||
|
@ -60,7 +60,7 @@ mod arch_specific {
|
||||
|
||||
// List of check functions
|
||||
static CHECK_LIST: &[CheckItem] = &[CheckItem {
|
||||
name: CheckType::CheckCpu,
|
||||
name: CheckType::Cpu,
|
||||
descr: "This parameter performs the cpu check",
|
||||
fp: check,
|
||||
perm: PermissionType::NonPrivileged,
|
||||
|
@ -21,16 +21,43 @@ mod arch_specific {
|
||||
const CPUINFO_FLAGS_TAG: &str = "flags";
|
||||
const CPU_FLAGS_INTEL: &[&str] = &["lm", "sse4_1", "vmx"];
|
||||
const CPU_ATTRIBS_INTEL: &[&str] = &["GenuineIntel"];
|
||||
const VMM_FLAGS: &[&str] = &["hypervisor"];
|
||||
|
||||
pub const ARCH_CPU_VENDOR_FIELD: &str = check::GENERIC_CPU_VENDOR_FIELD;
|
||||
pub const ARCH_CPU_MODEL_FIELD: &str = check::GENERIC_CPU_MODEL_FIELD;
|
||||
|
||||
// List of check functions
|
||||
static CHECK_LIST: &[CheckItem] = &[CheckItem {
|
||||
name: CheckType::CheckCpu,
|
||||
descr: "This parameter performs the cpu check",
|
||||
fp: check_cpu,
|
||||
perm: PermissionType::NonPrivileged,
|
||||
}];
|
||||
static CHECK_LIST: &[CheckItem] = &[
|
||||
CheckItem {
|
||||
name: CheckType::Cpu,
|
||||
descr: "This parameter performs the cpu check",
|
||||
fp: check_cpu,
|
||||
perm: PermissionType::NonPrivileged,
|
||||
},
|
||||
CheckItem {
|
||||
name: CheckType::KernelModules,
|
||||
descr: "This parameter performs the kvm check",
|
||||
fp: check_kernel_modules,
|
||||
perm: PermissionType::NonPrivileged,
|
||||
},
|
||||
];
|
||||
|
||||
static MODULE_LIST: &[KernelModule] = &[
|
||||
KernelModule {
|
||||
name: "kvm",
|
||||
parameter: KernelParam {
|
||||
name: "kvmclock_periodic_sync",
|
||||
value: KernelParamType::Simple("Y"),
|
||||
},
|
||||
},
|
||||
KernelModule {
|
||||
name: "kvm_intel",
|
||||
parameter: KernelParam {
|
||||
name: "unrestricted_guest",
|
||||
value: KernelParamType::Predicate(unrestricted_guest_param_check),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
pub fn get_checks() -> Option<&'static [CheckItem<'static>]> {
|
||||
Some(CHECK_LIST)
|
||||
@ -141,6 +168,120 @@ mod arch_specific {
|
||||
|
||||
Ok(GuestProtection::NoProtection)
|
||||
}
|
||||
|
||||
fn running_on_vmm() -> Result<bool> {
|
||||
match check::get_single_cpu_info(check::PROC_CPUINFO, CPUINFO_DELIMITER) {
|
||||
Ok(cpu_info) => {
|
||||
// check if the 'hypervisor' flag exist in the cpu features
|
||||
let missing_hypervisor_flag = check::check_cpu_attribs(&cpu_info, VMM_FLAGS)?;
|
||||
|
||||
if missing_hypervisor_flag.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(anyhow!(
|
||||
"Unable to determine if the OS is running on a VM: {}: {}",
|
||||
e,
|
||||
check::PROC_CPUINFO
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
// check the host kernel parameter value is valid
|
||||
// and check if we are running inside a VMM
|
||||
fn unrestricted_guest_param_check(
|
||||
module: &str,
|
||||
param_name: &str,
|
||||
param_value_host: &str,
|
||||
) -> Result<()> {
|
||||
let expected_param_value: char = 'Y';
|
||||
|
||||
let running_on_vmm_alt = running_on_vmm()?;
|
||||
|
||||
if running_on_vmm_alt {
|
||||
let msg = format!("You are running in a VM, where the kernel module '{}' parameter '{:}' has a value '{:}'. This causes conflict when running kata.",
|
||||
module,
|
||||
param_name,
|
||||
param_value_host
|
||||
);
|
||||
return Err(anyhow!(msg));
|
||||
}
|
||||
|
||||
if param_value_host == expected_param_value.to_string() {
|
||||
Ok(())
|
||||
} else {
|
||||
let error_msg = format!(
|
||||
"Kernel Module: '{:}' parameter '{:}' should have value '{:}', but found '{:}.'.",
|
||||
module, param_name, expected_param_value, param_value_host
|
||||
);
|
||||
|
||||
let action_msg = format!("Remove the '{:}' module using `rmmod` and then reload using `modprobe`, setting '{:}={:}'",
|
||||
module,
|
||||
param_name,
|
||||
expected_param_value
|
||||
);
|
||||
|
||||
return Err(anyhow!("{} {}", error_msg, action_msg));
|
||||
}
|
||||
}
|
||||
|
||||
fn check_kernel_param(
|
||||
module: &str,
|
||||
param_name: &str,
|
||||
param_value_host: &str,
|
||||
param_type: KernelParamType,
|
||||
) -> Result<()> {
|
||||
match param_type {
|
||||
KernelParamType::Simple(param_value_req) => {
|
||||
if param_value_host != param_value_req {
|
||||
return Err(anyhow!(
|
||||
"Kernel module '{}': parameter '{}' should have value '{}', but found '{}'",
|
||||
module,
|
||||
param_name,
|
||||
param_value_req,
|
||||
param_value_host
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
KernelParamType::Predicate(pred_func) => {
|
||||
pred_func(module, param_name, param_value_host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_kernel_modules(_args: &str) -> Result<()> {
|
||||
println!("INFO: check kernel modules for: x86_64");
|
||||
|
||||
for module in MODULE_LIST {
|
||||
let module_loaded =
|
||||
check::check_kernel_module_loaded(module.name, module.parameter.name);
|
||||
|
||||
match module_loaded {
|
||||
Ok(param_value_host) => {
|
||||
let parameter_check = check_kernel_param(
|
||||
module.name,
|
||||
module.parameter.name,
|
||||
¶m_value_host,
|
||||
module.parameter.value.clone(),
|
||||
);
|
||||
|
||||
match parameter_check {
|
||||
Ok(_v) => println!("{} Ok", module.name),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("WARNING {:}", err.replace('\n', ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
|
@ -9,6 +9,10 @@ use anyhow::{anyhow, Result};
|
||||
use reqwest::header::{CONTENT_TYPE, USER_AGENT};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(any(target_arch = "x86_64"))]
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
struct Release {
|
||||
tag_name: String,
|
||||
@ -17,6 +21,12 @@ struct Release {
|
||||
tarball_url: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const MODPROBE_PATH: &str = "/sbin/modprobe";
|
||||
|
||||
#[allow(dead_code)]
|
||||
const MODINFO_PATH: &str = "/sbin/modinfo";
|
||||
|
||||
const KATA_GITHUB_RELEASE_URL: &str =
|
||||
"https://api.github.com/repos/kata-containers/kata-containers/releases";
|
||||
|
||||
@ -29,6 +39,7 @@ const ERR_NO_CPUINFO: &str = "cpu_info string is empty";
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const GENERIC_CPU_VENDOR_FIELD: &str = "vendor_id";
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const GENERIC_CPU_MODEL_FIELD: &str = "model name";
|
||||
|
||||
@ -36,8 +47,8 @@ pub const GENERIC_CPU_MODEL_FIELD: &str = "model name";
|
||||
pub const PROC_CPUINFO: &str = "/proc/cpuinfo";
|
||||
|
||||
#[cfg(any(target_arch = "s390x", target_arch = "x86_64"))]
|
||||
fn get_cpu_info(cpu_info_file: &str) -> Result<String> {
|
||||
let contents = std::fs::read_to_string(cpu_info_file)?;
|
||||
fn read_file_contents(file_path: &str) -> Result<String> {
|
||||
let contents = std::fs::read_to_string(file_path)?;
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
@ -45,7 +56,7 @@ fn get_cpu_info(cpu_info_file: &str) -> Result<String> {
|
||||
// the specified cpuinfo file by parsing based on a specified delimiter
|
||||
#[cfg(any(target_arch = "s390x", target_arch = "x86_64"))]
|
||||
pub fn get_single_cpu_info(cpu_info_file: &str, substring: &str) -> Result<String> {
|
||||
let contents = get_cpu_info(cpu_info_file)?;
|
||||
let contents = read_file_contents(cpu_info_file)?;
|
||||
|
||||
if contents.is_empty() {
|
||||
return Err(anyhow!(ERR_NO_CPUINFO));
|
||||
@ -57,7 +68,6 @@ pub fn get_single_cpu_info(cpu_info_file: &str, substring: &str) -> Result<Strin
|
||||
.ok_or("error splitting contents of cpuinfo")
|
||||
.map_err(|e| anyhow!(e))?
|
||||
.to_string();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@ -70,7 +80,7 @@ pub fn get_cpu_flags(cpu_info: &str, cpu_flags_tag: &str) -> Result<String> {
|
||||
}
|
||||
|
||||
if cpu_flags_tag.is_empty() {
|
||||
return Err(anyhow!("cpu flags delimiter string is empty"));
|
||||
return Err(anyhow!("cpu flags delimiter string is empty"))?;
|
||||
}
|
||||
|
||||
let subcontents: Vec<&str> = cpu_info.split('\n').collect();
|
||||
@ -222,6 +232,86 @@ pub fn check_official_releases() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64"))]
|
||||
pub fn check_kernel_module_loaded(module: &str, parameter: &str) -> Result<String, String> {
|
||||
const MODPROBE_PARAMETERS_DRY_RUN: &str = "--dry-run";
|
||||
const MODPROBE_PARAMETERS_FIRST_TIME: &str = "--first-time";
|
||||
const MODULES_PATH: &str = "/sys/module";
|
||||
|
||||
let status_modinfo_success;
|
||||
|
||||
// Partial check w/ modinfo
|
||||
// verifies that the module exists
|
||||
match Command::new(MODINFO_PATH)
|
||||
.arg(module)
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
{
|
||||
Ok(v) => {
|
||||
status_modinfo_success = v.status.success();
|
||||
|
||||
// The module is already not loaded.
|
||||
if !status_modinfo_success {
|
||||
let msg = String::from_utf8_lossy(&v.stderr).replace('\n', "");
|
||||
return Err(msg);
|
||||
}
|
||||
}
|
||||
Err(_e) => {
|
||||
let msg = format!(
|
||||
"Command {:} not found, verify that `kmod` package is already installed.",
|
||||
MODINFO_PATH,
|
||||
);
|
||||
return Err(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Partial check w/ modprobe
|
||||
// check that the module is already loaded
|
||||
match Command::new(MODPROBE_PATH)
|
||||
.arg(MODPROBE_PARAMETERS_DRY_RUN)
|
||||
.arg(MODPROBE_PARAMETERS_FIRST_TIME)
|
||||
.arg(module)
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
{
|
||||
Ok(v) => {
|
||||
// a successful simulated modprobe insert, means the module is not already loaded
|
||||
let status_modprobe_success = v.status.success();
|
||||
|
||||
if status_modprobe_success && status_modinfo_success {
|
||||
// This condition is true in the case that the module exist, but is not already loaded
|
||||
let msg = format!("The kernel module `{:}` exist but is not already loaded. Try reloading it using 'modprobe {:}=Y'",
|
||||
module, module
|
||||
);
|
||||
return Err(msg);
|
||||
}
|
||||
}
|
||||
|
||||
Err(_e) => {
|
||||
let msg = format!(
|
||||
"Command {:} not found, verify that `kmod` package is already installed.",
|
||||
MODPROBE_PATH,
|
||||
);
|
||||
return Err(msg);
|
||||
}
|
||||
}
|
||||
|
||||
let module_path = format!("{}/{}/parameters/{}", MODULES_PATH, module, parameter);
|
||||
|
||||
// Here the currently loaded kernel parameter value
|
||||
// is retrieved and returned on success
|
||||
match read_file_contents(&module_path) {
|
||||
Ok(result) => Ok(result.replace('\n', "")),
|
||||
Err(_e) => {
|
||||
let msg = format!(
|
||||
"'{:}' kernel module parameter `{:}` not found.",
|
||||
module, parameter
|
||||
);
|
||||
Err(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "s390x", target_arch = "x86_64"))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@ -413,4 +503,64 @@ mod tests {
|
||||
assert!(!v.minor.to_string().is_empty());
|
||||
assert!(!v.patch.to_string().is_empty());
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64"))]
|
||||
#[test]
|
||||
fn check_module_loaded() {
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
struct TestData<'a> {
|
||||
module_name: &'a str,
|
||||
param_name: &'a str,
|
||||
param_value: &'a str,
|
||||
result: Result<String>,
|
||||
}
|
||||
|
||||
let tests = &[
|
||||
// Failure scenarios
|
||||
TestData {
|
||||
module_name: "",
|
||||
param_name: "",
|
||||
param_value: "",
|
||||
result: Err(anyhow!("modinfo: ERROR: Module {} not found.", "")),
|
||||
},
|
||||
TestData {
|
||||
module_name: "kvm",
|
||||
param_name: "",
|
||||
param_value: "",
|
||||
result: Err(anyhow!(
|
||||
"'{:}' kernel module parameter `{:}` not found.",
|
||||
"kvm",
|
||||
""
|
||||
)),
|
||||
},
|
||||
// Success scenarios
|
||||
TestData {
|
||||
module_name: "kvm",
|
||||
param_name: "kvmclock_periodic_sync",
|
||||
param_value: "Y",
|
||||
result: Ok("Y".to_string()),
|
||||
},
|
||||
];
|
||||
|
||||
for (i, d) in tests.iter().enumerate() {
|
||||
let msg = format!("test[{}]: {:?}", i, d);
|
||||
let result = check_kernel_module_loaded(d.module_name, d.param_name);
|
||||
let msg = format!("{}, result: {:?}", msg, result);
|
||||
|
||||
if d.result.is_ok() {
|
||||
assert_eq!(
|
||||
result.as_ref().unwrap(),
|
||||
d.result.as_ref().unwrap(),
|
||||
"{}",
|
||||
msg
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let expected_error = format!("{}", &d.result.as_ref().unwrap_err());
|
||||
let actual_error = result.unwrap_err().to_string();
|
||||
assert!(actual_error == expected_error, "{}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,15 +73,18 @@ pub fn handle_check(checkcmd: CheckArgument) -> Result<()> {
|
||||
match command {
|
||||
CheckSubCommand::All => {
|
||||
// run architecture-specific tests
|
||||
handle_builtin_check(CheckType::CheckCpu, "")?;
|
||||
handle_builtin_check(CheckType::Cpu, "")?;
|
||||
|
||||
// run code that uses network checks
|
||||
check::run_network_checks()?;
|
||||
|
||||
// run kernel module checks
|
||||
handle_builtin_check(CheckType::KernelModules, "")?;
|
||||
}
|
||||
|
||||
CheckSubCommand::NoNetworkChecks => {
|
||||
// run architecture-specific tests
|
||||
handle_builtin_check(CheckType::CheckCpu, "")?;
|
||||
handle_builtin_check(CheckType::Cpu, "")?;
|
||||
}
|
||||
|
||||
CheckSubCommand::CheckVersionOnly => {
|
||||
|
@ -12,8 +12,9 @@ pub type BuiltinCmdFp = fn(args: &str) -> Result<()>;
|
||||
// CheckType encodes the name of each check provided by kata-ctl.
|
||||
#[derive(Debug, strum_macros::Display, EnumString, PartialEq)]
|
||||
pub enum CheckType {
|
||||
CheckCpu,
|
||||
CheckNetwork,
|
||||
Cpu,
|
||||
Network,
|
||||
KernelModules,
|
||||
}
|
||||
|
||||
// PermissionType is used to show whether a check needs to run with elevated (super-user)
|
||||
@ -33,3 +34,39 @@ pub struct CheckItem<'a> {
|
||||
pub fp: BuiltinCmdFp,
|
||||
pub perm: PermissionType,
|
||||
}
|
||||
|
||||
// Builtin module parameter check handler type.
|
||||
//
|
||||
// BuiltinModuleParamFp represents a predicate function to determine if a
|
||||
// kernel parameter _value_ is as expected. If not, the returned Error will
|
||||
// explain what is wrong.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// - module: name of kernel module.
|
||||
// - param: name of parameter for the kernel module.
|
||||
// - value: value of the kernel parameter.
|
||||
pub type BuiltinModuleParamFp = fn(module: &str, param: &str, value: &str) -> Result<()>;
|
||||
|
||||
// KernelParamType encodes the value and a handler
|
||||
// function for kernel module parameters
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
pub enum KernelParamType<'a> {
|
||||
Simple(&'a str),
|
||||
Predicate(BuiltinModuleParamFp),
|
||||
}
|
||||
|
||||
// Parameters is used to encode the module parameters
|
||||
#[derive(Clone)]
|
||||
pub struct KernelParam<'a> {
|
||||
pub name: &'a str,
|
||||
pub value: KernelParamType<'a>,
|
||||
}
|
||||
|
||||
// KernelModule is used to describe a kernel module along with its required parameters.
|
||||
#[allow(dead_code)]
|
||||
pub struct KernelModule<'a> {
|
||||
pub name: &'a str,
|
||||
pub parameter: KernelParam<'a>,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user