diff --git a/src/libs/kata-types/src/config/hypervisor/mod.rs b/src/libs/kata-types/src/config/hypervisor/mod.rs index 52ac7ba2b9..c6eb3c25ba 100644 --- a/src/libs/kata-types/src/config/hypervisor/mod.rs +++ b/src/libs/kata-types/src/config/hypervisor/mod.rs @@ -934,6 +934,10 @@ pub struct SecurityInfo { rename = "tdx_quote_generation_service_socket_port" )] pub qgs_port: u32, + + /// selinux_label defines SELinux label for the guest + #[serde(default)] + pub selinux_label: Option, } fn default_qgs_port() -> u32 { @@ -1251,12 +1255,20 @@ pub struct Hypervisor { /// Disable applying SELinux on the container process. #[serde(default = "yes")] pub disable_guest_selinux: bool, + + /// Disable applying SELinux on the VMM process. + #[serde(default = "no")] + pub disable_selinux: bool, } fn yes() -> bool { true } +fn no() -> bool { + false +} + impl Hypervisor { /// Validate path of hypervisor executable. pub fn validate_hypervisor_path>(&self, path: P) -> Result<()> { diff --git a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs index 768029608d..85ddae6d7d 100644 --- a/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs +++ b/src/runtime-rs/crates/hypervisor/src/ch/inner_hypervisor.rs @@ -531,7 +531,7 @@ impl CloudHypervisorInner { Ok(()) } - pub(crate) async fn prepare_vm(&mut self, id: &str, netns: Option) -> Result<()> { + pub(crate) async fn prepare_vm(&mut self, id: &str, netns: Option, _selinux_label: Option) -> Result<()> { self.id = id.to_string(); self.state = VmmState::NotReady; diff --git a/src/runtime-rs/crates/hypervisor/src/ch/mod.rs b/src/runtime-rs/crates/hypervisor/src/ch/mod.rs index 9381569af8..270ce16719 100644 --- a/src/runtime-rs/crates/hypervisor/src/ch/mod.rs +++ b/src/runtime-rs/crates/hypervisor/src/ch/mod.rs @@ -65,9 +65,10 @@ impl Hypervisor for CloudHypervisor { id: &str, netns: Option, _annotations: &HashMap, + selinux_label: Option, ) -> Result<()> { let mut inner = self.inner.write().await; - inner.prepare_vm(id, netns).await + inner.prepare_vm(id, netns, selinux_label).await } async fn start_vm(&self, timeout: i32) -> Result<()> { diff --git a/src/runtime-rs/crates/hypervisor/src/dragonball/inner_hypervisor.rs b/src/runtime-rs/crates/hypervisor/src/dragonball/inner_hypervisor.rs index 14b0d02198..fbc3cbacc9 100644 --- a/src/runtime-rs/crates/hypervisor/src/dragonball/inner_hypervisor.rs +++ b/src/runtime-rs/crates/hypervisor/src/dragonball/inner_hypervisor.rs @@ -19,7 +19,7 @@ use crate::{ }; impl DragonballInner { - pub(crate) async fn prepare_vm(&mut self, id: &str, netns: Option) -> Result<()> { + pub(crate) async fn prepare_vm(&mut self, id: &str, netns: Option, _selinux_label: Option) -> Result<()> { self.id = id.to_string(); self.state = VmmState::NotReady; diff --git a/src/runtime-rs/crates/hypervisor/src/dragonball/mod.rs b/src/runtime-rs/crates/hypervisor/src/dragonball/mod.rs index 4b85420ad2..8bfb539b2c 100644 --- a/src/runtime-rs/crates/hypervisor/src/dragonball/mod.rs +++ b/src/runtime-rs/crates/hypervisor/src/dragonball/mod.rs @@ -75,9 +75,10 @@ impl Hypervisor for Dragonball { id: &str, netns: Option, _annotations: &HashMap, + selinux_label: Option, ) -> Result<()> { let mut inner = self.inner.write().await; - inner.prepare_vm(id, netns).await + inner.prepare_vm(id, netns, selinux_label).await } #[instrument] diff --git a/src/runtime-rs/crates/hypervisor/src/firecracker/inner_hypervisor.rs b/src/runtime-rs/crates/hypervisor/src/firecracker/inner_hypervisor.rs index bd38385a51..fa96fd3ba8 100644 --- a/src/runtime-rs/crates/hypervisor/src/firecracker/inner_hypervisor.rs +++ b/src/runtime-rs/crates/hypervisor/src/firecracker/inner_hypervisor.rs @@ -19,7 +19,7 @@ pub const ROOT: &str = "root"; const HYBRID_VSOCK_SCHEME: &str = "hvsock"; impl FcInner { - pub(crate) async fn prepare_vm(&mut self, id: &str, _netns: Option) -> Result<()> { + pub(crate) async fn prepare_vm(&mut self, id: &str, _netns: Option, _selinux_label: Option) -> Result<()> { debug!(sl(), "Preparing Firecracker"); self.id = id.to_string(); diff --git a/src/runtime-rs/crates/hypervisor/src/firecracker/mod.rs b/src/runtime-rs/crates/hypervisor/src/firecracker/mod.rs index 7c92a87075..05fd0c57cb 100644 --- a/src/runtime-rs/crates/hypervisor/src/firecracker/mod.rs +++ b/src/runtime-rs/crates/hypervisor/src/firecracker/mod.rs @@ -64,9 +64,10 @@ impl Hypervisor for Firecracker { id: &str, netns: Option, _annotations: &HashMap, + selinux_label: Option, ) -> Result<()> { let mut inner = self.inner.write().await; - inner.prepare_vm(id, netns).await + inner.prepare_vm(id, netns, selinux_label).await } async fn start_vm(&self, timeout: i32) -> Result<()> { diff --git a/src/runtime-rs/crates/hypervisor/src/lib.rs b/src/runtime-rs/crates/hypervisor/src/lib.rs index 6185fa8021..d1ac432c15 100644 --- a/src/runtime-rs/crates/hypervisor/src/lib.rs +++ b/src/runtime-rs/crates/hypervisor/src/lib.rs @@ -18,6 +18,7 @@ pub mod dragonball; pub mod firecracker; mod kernel_param; pub mod qemu; +pub mod selinux; pub mod remote; pub use kernel_param::Param; pub mod utils; @@ -103,6 +104,7 @@ pub trait Hypervisor: std::fmt::Debug + Send + Sync { id: &str, netns: Option, annotations: &HashMap, + selinux_label: Option, ) -> Result<()>; async fn start_vm(&self, timeout: i32) -> Result<()>; async fn stop_vm(&self) -> Result<()>; diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs index 7e4cc50cab..a1c96825a7 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/inner.rs @@ -7,8 +7,8 @@ use super::cmdline_generator::{get_network_device, QemuCmdLine, QMP_SOCKET_FILE} use super::qmp::Qmp; use crate::device::topology::PCIePort; use crate::{ - device::driver::ProtectionDeviceConfig, hypervisor_persist::HypervisorState, HypervisorConfig, - MemoryConfig, VcpuThreadIds, VsockDevice, HYPERVISOR_QEMU, + device::driver::ProtectionDeviceConfig, hypervisor_persist::HypervisorState, selinux, + HypervisorConfig, MemoryConfig, VcpuThreadIds, VsockDevice, HYPERVISOR_QEMU, }; use crate::utils::{bytes_to_megs, enter_netns, megs_to_bytes}; @@ -62,11 +62,24 @@ impl QemuInner { } } - pub(crate) async fn prepare_vm(&mut self, id: &str, netns: Option) -> Result<()> { + pub(crate) async fn prepare_vm( + &mut self, + id: &str, + netns: Option, + selinux_label: Option, + ) -> Result<()> { info!(sl!(), "Preparing QEMU VM"); self.id = id.to_string(); self.netns = netns; + if !self.hypervisor_config().disable_selinux { + if let Some(label) = selinux_label.as_ref() { + self.config.security_info.selinux_label = Some(label.to_string()); + selinux::set_exec_label(&selinux_label.unwrap()) + .context("failed to set SELinux process label ")?; + } + } + let vm_path = [KATA_PATH, self.id.as_str()].join("/"); std::fs::create_dir_all(vm_path)?; @@ -193,11 +206,23 @@ impl QemuInner { info!(sl!(), "qemu cmd: {:?}", command); - // we need move the qemu process into Network Namespace. + // we need move the qemu process into Network Namespace and set SELinux label. unsafe { + let selinux_label = self.config.security_info.selinux_label.clone(); let _pre_exec = command.pre_exec(move || { let _ = enter_netns(&netns); - + if let Some(label) = selinux_label.as_ref() { + if let Err(e) = selinux::set_exec_label(&label) { + error!(sl!(), "Failed to set SELinux label in child process: {}", e); + // Don't return error here to avoid breaking the process startup + // Log the error and continue + } else { + info!( + sl!(), + "Successfully set SELinux label in child process: {}", &label + ); + } + } Ok(()) }); } diff --git a/src/runtime-rs/crates/hypervisor/src/qemu/mod.rs b/src/runtime-rs/crates/hypervisor/src/qemu/mod.rs index 01a0d96db0..944d319782 100644 --- a/src/runtime-rs/crates/hypervisor/src/qemu/mod.rs +++ b/src/runtime-rs/crates/hypervisor/src/qemu/mod.rs @@ -58,9 +58,10 @@ impl Hypervisor for Qemu { id: &str, netns: Option, _annotations: &HashMap, + selinux_label: Option, ) -> Result<()> { let mut inner = self.inner.write().await; - inner.prepare_vm(id, netns).await + inner.prepare_vm(id, netns, selinux_label).await } async fn start_vm(&self, timeout: i32) -> Result<()> { diff --git a/src/runtime-rs/crates/hypervisor/src/remote/inner.rs b/src/runtime-rs/crates/hypervisor/src/remote/inner.rs index 246c6f19b7..87557c066f 100644 --- a/src/runtime-rs/crates/hypervisor/src/remote/inner.rs +++ b/src/runtime-rs/crates/hypervisor/src/remote/inner.rs @@ -146,6 +146,7 @@ impl RemoteInner { id: &str, netns: Option, annotations: &HashMap, + _selinux_label: Option, ) -> Result<()> { info!(sl!(), "Preparing REMOTE VM"); self.id = id.to_string(); diff --git a/src/runtime-rs/crates/hypervisor/src/remote/mod.rs b/src/runtime-rs/crates/hypervisor/src/remote/mod.rs index 233aacb3ec..00ca44ff4c 100644 --- a/src/runtime-rs/crates/hypervisor/src/remote/mod.rs +++ b/src/runtime-rs/crates/hypervisor/src/remote/mod.rs @@ -48,9 +48,10 @@ impl Hypervisor for Remote { id: &str, netns: Option, annotations: &HashMap, + _selinux_label: Option, ) -> Result<()> { let mut inner = self.inner.write().await; - inner.prepare_vm(id, netns, annotations).await + inner.prepare_vm(id, netns, annotations, _selinux_label).await } async fn start_vm(&self, timeout: i32) -> Result<()> { diff --git a/src/runtime-rs/crates/hypervisor/src/selinux.rs b/src/runtime-rs/crates/hypervisor/src/selinux.rs new file mode 100644 index 0000000000..eebc69716a --- /dev/null +++ b/src/runtime-rs/crates/hypervisor/src/selinux.rs @@ -0,0 +1,71 @@ +// Copyright 2024 The Kata Containers community +// +// SPDX-License-Identifier: Apache-2.0 + +use std::fs::{self, OpenOptions}; +use std::io::prelude::*; +use std::path::Path; + +use anyhow::{Context, Result}; +use nix::unistd::gettid; + +/// Check if SELinux is enabled on the system +pub fn is_selinux_enabled() -> bool { + let buf = match fs::read_to_string("/proc/mounts") { + Ok(content) => content, + Err(_) => return false, + }; + buf.contains("selinuxfs") +} + +pub fn set_exec_label(label: &str) -> Result<()> { + let mut attr_path = Path::new("/proc/thread-self/attr/exec").to_path_buf(); + if !attr_path.exists() { + // Fall back to the old convention + attr_path = Path::new("/proc/self/task") + .join(gettid().to_string()) + .join("attr/exec") + } + + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(attr_path)?; + file.write_all(label.as_bytes()) + .with_context(|| "failed to apply SELinux label")?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_LABEL: &str = "system_u:system_r:unconfined_t:s0"; + + #[test] + fn test_is_selinux_enabled() { + let str = fs::read_to_string("/proc/mounts").unwrap(); + let expected = str.contains("selinuxfs"); + assert_eq!(is_selinux_enabled(), expected); + } + + #[test] + fn test_set_exec_label() { + let ret = set_exec_label(TEST_LABEL); + if is_selinux_enabled() { + assert!(ret.is_ok(), "Expecting Ok, Got {:?}", ret); + // 检查 label 是否被正确设置 + let mut attr_path = std::path::Path::new("/proc/thread-self/attr/exec").to_path_buf(); + if !attr_path.exists() { + attr_path = std::path::Path::new("/proc/self/task") + .join(nix::unistd::gettid().to_string()) + .join("attr/exec"); + } + let label = std::fs::read_to_string(attr_path).unwrap(); + assert_eq!(label.trim_end_matches('\0'), TEST_LABEL); + } else { + assert!(ret.is_err(), "Expecting error, Got {:?}", ret); + } + } +} diff --git a/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs b/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs index 801718e734..3d55a81f84 100644 --- a/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs +++ b/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs @@ -38,6 +38,7 @@ use kata_types::config::hypervisor::Hypervisor as HypervisorConfig; use kata_types::config::hypervisor::HYPERVISOR_NAME_CH; use kata_types::config::TomlConfig; use kata_types::initdata::{calculate_initdata_digest, ProtectedPlatform}; +use kata_sys_util::spec::load_oci_spec; use oci_spec::runtime as oci; use persist::{self, sandbox_persist::Persist}; use protobuf::SpecialFields; @@ -510,12 +511,20 @@ impl Sandbox for VirtSandbox { warn!(sl!(), "sandbox is started"); return Ok(()); } + let selinux_label = load_oci_spec() + .ok() + .and_then(|spec| { + spec.process() + .as_ref() + .and_then(|process| process.selinux_label().clone()) + }); self.hypervisor .prepare_vm( id, sandbox_config.network_env.netns.clone(), &sandbox_config.annotations, + selinux_label, ) .await .context("prepare vm")?;