From 93547692863aea6bbb3374752d75839730692463 Mon Sep 17 00:00:00 2001 From: Manabu Sugimoto Date: Sun, 7 Aug 2022 18:14:22 +0900 Subject: [PATCH] agent: Add SELinux support for containers The kata-agent supports SELinux for containers inside the guest to comply with the OCI runtime specification. Fixes: #4812 Signed-off-by: Manabu Sugimoto --- src/agent/Cargo.lock | 10 ++++ src/agent/rustjail/Cargo.toml | 1 + src/agent/rustjail/src/container.rs | 15 ++++++ src/agent/rustjail/src/lib.rs | 1 + src/agent/rustjail/src/mount.rs | 45 ++++++++++++++-- src/agent/rustjail/src/selinux.rs | 80 +++++++++++++++++++++++++++++ src/agent/rustjail/src/validator.rs | 45 ++++++++++++++-- 7 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 src/agent/rustjail/src/selinux.rs diff --git a/src/agent/Cargo.lock b/src/agent/Cargo.lock index d4407e7ee..b6949e329 100644 --- a/src/agent/Cargo.lock +++ b/src/agent/Cargo.lock @@ -1764,6 +1764,7 @@ dependencies = [ "tempfile", "test-utils", "tokio", + "xattr", "zbus", ] @@ -2552,6 +2553,15 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + [[package]] name = "zbus" version = "2.3.2" diff --git a/src/agent/rustjail/Cargo.toml b/src/agent/rustjail/Cargo.toml index 031175e86..851721ce0 100644 --- a/src/agent/rustjail/Cargo.toml +++ b/src/agent/rustjail/Cargo.toml @@ -35,6 +35,7 @@ inotify = "0.9.2" libseccomp = { version = "0.3.0", optional = true } zbus = "2.3.0" bit-vec= "0.6.3" +xattr = "0.2.3" [dev-dependencies] serial_test = "0.5.0" diff --git a/src/agent/rustjail/src/container.rs b/src/agent/rustjail/src/container.rs index de92adf4c..2cc4da9e4 100644 --- a/src/agent/rustjail/src/container.rs +++ b/src/agent/rustjail/src/container.rs @@ -30,6 +30,7 @@ use crate::log_child; use crate::process::Process; #[cfg(feature = "seccomp")] use crate::seccomp; +use crate::selinux; use crate::specconv::CreateOpts; use crate::{mount, validator}; @@ -537,6 +538,8 @@ fn do_init_child(cwfd: RawFd) -> Result<()> { } } + let selinux_enabled = selinux::is_enabled()?; + sched::unshare(to_new & !CloneFlags::CLONE_NEWUSER)?; if userns { @@ -638,6 +641,18 @@ fn do_init_child(cwfd: RawFd) -> Result<()> { capctl::prctl::set_no_new_privs().map_err(|_| anyhow!("cannot set no new privileges"))?; } + // Set SELinux label + if !oci_process.selinux_label.is_empty() { + if !selinux_enabled { + return Err(anyhow!( + "SELinux label for the process is provided but SELinux is not enabled on the running kernel" + )); + } + + log_child!(cfd_log, "Set SELinux label to the container process"); + selinux::set_exec_label(&oci_process.selinux_label)?; + } + // Log unknown seccomp system calls in advance before the log file descriptor closes. #[cfg(feature = "seccomp")] if let Some(ref scmp) = linux.seccomp { diff --git a/src/agent/rustjail/src/lib.rs b/src/agent/rustjail/src/lib.rs index fb51d9f39..6f96d18c2 100644 --- a/src/agent/rustjail/src/lib.rs +++ b/src/agent/rustjail/src/lib.rs @@ -38,6 +38,7 @@ pub mod pipestream; pub mod process; #[cfg(feature = "seccomp")] pub mod seccomp; +pub mod selinux; pub mod specconv; pub mod sync; pub mod sync_with_async; diff --git a/src/agent/rustjail/src/mount.rs b/src/agent/rustjail/src/mount.rs index d3f87a8b4..a6418a343 100644 --- a/src/agent/rustjail/src/mount.rs +++ b/src/agent/rustjail/src/mount.rs @@ -25,6 +25,7 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use crate::container::DEFAULT_DEVICES; +use crate::selinux; use crate::sync::write_count; use std::string::ToString; @@ -181,6 +182,8 @@ pub fn init_rootfs( None => flags |= MsFlags::MS_SLAVE, } + let label = &linux.mount_label; + let root = spec .root .as_ref() @@ -244,7 +247,7 @@ pub fn init_rootfs( } } - mount_from(cfd_log, m, rootfs, flags, &data, "")?; + mount_from(cfd_log, m, rootfs, flags, &data, label)?; // bind mount won't change mount options, we need remount to make mount options // effective. // first check that we have non-default options required before attempting a @@ -524,7 +527,6 @@ pub fn pivot_rootfs(path: &P) -> Result<( fn rootfs_parent_mount_private(path: &str) -> Result<()> { let mount_infos = parse_mount_table(MOUNTINFO_PATH)?; - let mut max_len = 0; let mut mount_point = String::from(""); let mut options = String::from(""); @@ -767,9 +769,9 @@ fn mount_from( rootfs: &str, flags: MsFlags, data: &str, - _label: &str, + label: &str, ) -> Result<()> { - let d = String::from(data); + let mut d = String::from(data); let dest = secure_join(rootfs, &m.destination); let src = if m.r#type.as_str() == "bind" { @@ -822,6 +824,37 @@ fn mount_from( e })?; + // Set the SELinux context for the mounts + let mut use_xattr = false; + if !label.is_empty() { + if selinux::is_enabled()? { + let device = Path::new(&m.source) + .file_name() + .ok_or_else(|| anyhow!("invalid device source path: {}", &m.source))? + .to_str() + .ok_or_else(|| anyhow!("failed to convert device source path: {}", &m.source))?; + + match device { + // SELinux does not support labeling of /proc or /sys + "proc" | "sysfs" => (), + // SELinux does not support mount labeling against /dev/mqueue, + // so we use setxattr instead + "mqueue" => { + use_xattr = true; + } + _ => { + log_child!(cfd_log, "add SELinux mount label to {}", dest.as_str()); + selinux::add_mount_label(&mut d, label); + } + } + } else { + log_child!( + cfd_log, + "SELinux label for the mount is provided but SELinux is not enabled on the running kernel" + ); + } + } + mount( Some(src.as_str()), dest.as_str(), @@ -834,6 +867,10 @@ fn mount_from( e })?; + if !label.is_empty() && selinux::is_enabled()? && use_xattr { + xattr::set(dest.as_str(), "security.selinux", label.as_bytes())?; + } + if flags.contains(MsFlags::MS_BIND) && flags.intersects( !(MsFlags::MS_REC diff --git a/src/agent/rustjail/src/selinux.rs b/src/agent/rustjail/src/selinux.rs new file mode 100644 index 000000000..5a647e3cc --- /dev/null +++ b/src/agent/rustjail/src/selinux.rs @@ -0,0 +1,80 @@ +// Copyright 2022 Sony Group Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::{Context, Result}; +use nix::unistd::gettid; +use std::fs::{self, OpenOptions}; +use std::io::prelude::*; +use std::path::Path; + +pub fn is_enabled() -> Result { + let buf = fs::read_to_string("/proc/mounts")?; + let enabled = buf.contains("selinuxfs"); + + Ok(enabled) +} + +pub fn add_mount_label(data: &mut String, label: &str) { + if data.is_empty() { + let context = format!("context=\"{}\"", label); + data.push_str(&context); + } else { + let context = format!(",context=\"{}\"", label); + data.push_str(&context); + } +} + +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_enabled() { + let ret = is_enabled(); + assert!(ret.is_ok(), "Expecting Ok, Got {:?}", ret); + } + + #[test] + fn test_add_mount_label() { + let mut data = String::new(); + add_mount_label(&mut data, TEST_LABEL); + assert_eq!(data, format!("context=\"{}\"", TEST_LABEL)); + + let mut data = String::from("defaults"); + add_mount_label(&mut data, TEST_LABEL); + assert_eq!(data, format!("defaults,context=\"{}\"", TEST_LABEL)); + } + + #[test] + fn test_set_exec_label() { + let ret = set_exec_label(TEST_LABEL); + if is_enabled().unwrap() { + assert!(ret.is_ok(), "Expecting Ok, Got {:?}", ret); + } else { + assert!(ret.is_err(), "Expecting error, Got {:?}", ret); + } + } +} diff --git a/src/agent/rustjail/src/validator.rs b/src/agent/rustjail/src/validator.rs index aea0f8f06..4955fbf46 100644 --- a/src/agent/rustjail/src/validator.rs +++ b/src/agent/rustjail/src/validator.rs @@ -6,6 +6,7 @@ use crate::container::Config; use anyhow::{anyhow, Context, Result}; use oci::{Linux, LinuxIdMapping, LinuxNamespace, Spec}; +use regex::Regex; use std::collections::HashMap; use std::path::{Component, PathBuf}; @@ -86,6 +87,23 @@ fn hostname(oci: &Spec) -> Result<()> { fn security(oci: &Spec) -> Result<()> { let linux = get_linux(oci)?; + let label_pattern = r".*_u:.*_r:.*_t:s[0-9]|1[0-5].*"; + let label_regex = Regex::new(label_pattern)?; + + if let Some(ref process) = oci.process { + if !process.selinux_label.is_empty() && !label_regex.is_match(&process.selinux_label) { + return Err(anyhow!( + "SELinux label for the process is invalid format: {}", + &process.selinux_label + )); + } + } + if !linux.mount_label.is_empty() && !label_regex.is_match(&linux.mount_label) { + return Err(anyhow!( + "SELinux label for the mount is invalid format: {}", + &linux.mount_label + )); + } if linux.masked_paths.is_empty() && linux.readonly_paths.is_empty() { return Ok(()); @@ -95,8 +113,6 @@ fn security(oci: &Spec) -> Result<()> { return Err(anyhow!("Linux namespace does not contain mount")); } - // don't care about selinux at present - Ok(()) } @@ -285,7 +301,7 @@ pub fn validate(conf: &Config) -> Result<()> { #[cfg(test)] mod tests { use super::*; - use oci::Mount; + use oci::{Mount, Process}; #[test] fn test_namespace() { @@ -388,6 +404,29 @@ mod tests { ]; spec.linux = Some(linux); security(&spec).unwrap(); + + // SELinux + let valid_label = "system_u:system_r:container_t:s0:c123,c456"; + let mut process = Process::default(); + process.selinux_label = valid_label.to_string(); + spec.process = Some(process); + security(&spec).unwrap(); + + let mut linux = Linux::default(); + linux.mount_label = valid_label.to_string(); + spec.linux = Some(linux); + security(&spec).unwrap(); + + let invalid_label = "system_u:system_r:container_t"; + let mut process = Process::default(); + process.selinux_label = invalid_label.to_string(); + spec.process = Some(process); + security(&spec).unwrap_err(); + + let mut linux = Linux::default(); + linux.mount_label = invalid_label.to_string(); + spec.linux = Some(linux); + security(&spec).unwrap_err(); } #[test]