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 b1ee9f136d..206c447a99 100644 --- a/src/tools/kata-ctl/src/arch/x86_64/mod.rs +++ b/src/tools/kata-ctl/src/arch/x86_64/mod.rs @@ -13,7 +13,7 @@ mod arch_specific { use crate::check::{GuestProtection, ProtectionError}; use crate::types::*; use crate::utils; - use anyhow::{anyhow, Result}; + use anyhow::{anyhow, Context, Result}; use nix::unistd::Uid; use std::fs; use std::path::Path; @@ -41,6 +41,12 @@ mod arch_specific { fp: check_kernel_modules, perm: PermissionType::NonPrivileged, }, + CheckItem { + name: CheckType::KvmIsUsable, + descr: "This parameter performs check to see if KVM is usable", + fp: check_kvm_is_usable, + perm: PermissionType::Privileged, + }, ]; static MODULE_LIST: &[KernelModule] = &[ @@ -114,6 +120,15 @@ mod arch_specific { utils::get_generic_cpu_details(check::PROC_CPUINFO) } + // check if kvm is usable + fn check_kvm_is_usable(_args: &str) -> Result<()> { + println!("INFO: check if kvm is usable: x86_64"); + + let result = check::check_kvm_is_usable_generic(); + + result.context("KVM check failed") + } + 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"; diff --git a/src/tools/kata-ctl/src/check.rs b/src/tools/kata-ctl/src/check.rs index 8f21f6fabb..ef1007f86b 100644 --- a/src/tools/kata-ctl/src/check.rs +++ b/src/tools/kata-ctl/src/check.rs @@ -6,6 +6,10 @@ // Contains checks that are not architecture-specific use anyhow::{anyhow, Result}; +use nix::fcntl::{open, OFlag}; +use nix::sys::stat::Mode; +use nix::unistd::close; +use nix::{ioctl_write_int_bad, request_code_none}; use reqwest::header::{CONTENT_TYPE, USER_AGENT}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -176,6 +180,65 @@ pub fn run_network_checks() -> Result<()> { Ok(()) } +// Set of basic checks for kvm. Architectures should implement more specific checks if needed +#[allow(dead_code)] +pub fn check_kvm_is_usable_generic() -> Result<()> { + // check for root user + if !nix::unistd::Uid::effective().is_root() { + return Err(anyhow!("Will not perform kvm checks as non root user")); + } + + // we do not want to create syscalls to any device besides /dev/kvm + const KVM_DEVICE: &str = "/dev/kvm"; + + // constants specific to kvm ioctls found in kvm.h + const KVM_IOCTL_ID: u8 = 0xAE; + const KVM_CREATE_VM: u8 = 0x01; + const KVM_GET_API_VERSION: u8 = 0x00; + // per kvm api documentation, this number should always be 12 + // https://www.kernel.org/doc/html/latest/virt/kvm/api.html#kvm-get-api-version + const API_VERSION: i32 = 12; + + // open kvm device + // since file is not being created, mode argument is not relevant + let mode = Mode::empty(); + let flags = OFlag::O_RDWR | OFlag::O_CLOEXEC; + let fd = open(KVM_DEVICE, flags, mode)?; + + // check kvm api version + ioctl_write_int_bad!( + kvm_api_version, + request_code_none!(KVM_IOCTL_ID, KVM_GET_API_VERSION) + ); + // 0 is not used but required to produce output + let v = unsafe { kvm_api_version(fd, 0)? }; + if v != API_VERSION { + return Err(anyhow!("KVM API version is not correct")); + } + + // check if you can create vm + ioctl_write_int_bad!( + kvm_create_vm, + request_code_none!(KVM_IOCTL_ID, KVM_CREATE_VM) + ); + // 0 is default machine type + let vmfd = unsafe { kvm_create_vm(fd, 0) }; + let _vmfd = match vmfd { + Ok(vm) => vm, + Err(ref error) if error.to_string() == "EBUSY: Device or resource busy" => { + return Err(anyhow!( + "Another hypervisor is running. KVM_CREATE_VM error: {:?}", + error + )) + } + Err(error) => return Err(anyhow!("Other KVM_CREATE_VM error: {:?}", error)), + }; + + let _ = close(fd); + + Ok(()) +} + fn get_kata_all_releases_by_url(url: &str) -> std::result::Result, reqwest::Error> { let releases: Vec = reqwest::blocking::Client::new() .get(url) @@ -332,6 +395,7 @@ mod tests { use std::fs; use std::io::Write; use tempfile::tempdir; + use test_utils::skip_if_root; #[test] fn test_get_single_cpu_info() { @@ -459,6 +523,16 @@ mod tests { } } + #[test] + fn test_check_kvm_is_usable_generic() { + skip_if_root!(); + #[allow(dead_code)] + let result = check_kvm_is_usable_generic(); + assert!( + result.err().unwrap().to_string() == "Will not perform kvm checks as non root user" + ); + } + #[test] fn test_get_kata_all_releases_by_url() { #[derive(Debug)] diff --git a/src/tools/kata-ctl/src/ops/check_ops.rs b/src/tools/kata-ctl/src/ops/check_ops.rs index 298e214c55..ed418169d0 100644 --- a/src/tools/kata-ctl/src/ops/check_ops.rs +++ b/src/tools/kata-ctl/src/ops/check_ops.rs @@ -80,6 +80,9 @@ pub fn handle_check(checkcmd: CheckArgument) -> Result<()> { // run kernel module checks handle_builtin_check(CheckType::KernelModules, "")?; + + // run kvm checks + handle_builtin_check(CheckType::KvmIsUsable, "")?; } CheckSubCommand::NoNetworkChecks => { diff --git a/src/tools/kata-ctl/src/types.rs b/src/tools/kata-ctl/src/types.rs index 26f5954d80..fcafeb435e 100644 --- a/src/tools/kata-ctl/src/types.rs +++ b/src/tools/kata-ctl/src/types.rs @@ -15,6 +15,7 @@ pub enum CheckType { Cpu, Network, KernelModules, + KvmIsUsable, } // PermissionType is used to show whether a check needs to run with elevated (super-user)