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 <Manabu.Sugimoto@sony.com>
This commit is contained in:
Manabu Sugimoto 2022-08-07 18:14:22 +09:00
parent a75f99d20d
commit 9354769286
7 changed files with 190 additions and 7 deletions

10
src/agent/Cargo.lock generated
View File

@ -1764,6 +1764,7 @@ dependencies = [
"tempfile", "tempfile",
"test-utils", "test-utils",
"tokio", "tokio",
"xattr",
"zbus", "zbus",
] ]
@ -2552,6 +2553,15 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "xattr"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "zbus" name = "zbus"
version = "2.3.2" version = "2.3.2"

View File

@ -35,6 +35,7 @@ inotify = "0.9.2"
libseccomp = { version = "0.3.0", optional = true } libseccomp = { version = "0.3.0", optional = true }
zbus = "2.3.0" zbus = "2.3.0"
bit-vec= "0.6.3" bit-vec= "0.6.3"
xattr = "0.2.3"
[dev-dependencies] [dev-dependencies]
serial_test = "0.5.0" serial_test = "0.5.0"

View File

@ -30,6 +30,7 @@ use crate::log_child;
use crate::process::Process; use crate::process::Process;
#[cfg(feature = "seccomp")] #[cfg(feature = "seccomp")]
use crate::seccomp; use crate::seccomp;
use crate::selinux;
use crate::specconv::CreateOpts; use crate::specconv::CreateOpts;
use crate::{mount, validator}; 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)?; sched::unshare(to_new & !CloneFlags::CLONE_NEWUSER)?;
if userns { 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"))?; 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. // Log unknown seccomp system calls in advance before the log file descriptor closes.
#[cfg(feature = "seccomp")] #[cfg(feature = "seccomp")]
if let Some(ref scmp) = linux.seccomp { if let Some(ref scmp) = linux.seccomp {

View File

@ -38,6 +38,7 @@ pub mod pipestream;
pub mod process; pub mod process;
#[cfg(feature = "seccomp")] #[cfg(feature = "seccomp")]
pub mod seccomp; pub mod seccomp;
pub mod selinux;
pub mod specconv; pub mod specconv;
pub mod sync; pub mod sync;
pub mod sync_with_async; pub mod sync_with_async;

View File

@ -25,6 +25,7 @@ use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use crate::container::DEFAULT_DEVICES; use crate::container::DEFAULT_DEVICES;
use crate::selinux;
use crate::sync::write_count; use crate::sync::write_count;
use std::string::ToString; use std::string::ToString;
@ -181,6 +182,8 @@ pub fn init_rootfs(
None => flags |= MsFlags::MS_SLAVE, None => flags |= MsFlags::MS_SLAVE,
} }
let label = &linux.mount_label;
let root = spec let root = spec
.root .root
.as_ref() .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 // bind mount won't change mount options, we need remount to make mount options
// effective. // effective.
// first check that we have non-default options required before attempting a // first check that we have non-default options required before attempting a
@ -524,7 +527,6 @@ pub fn pivot_rootfs<P: ?Sized + NixPath + std::fmt::Debug>(path: &P) -> Result<(
fn rootfs_parent_mount_private(path: &str) -> Result<()> { fn rootfs_parent_mount_private(path: &str) -> Result<()> {
let mount_infos = parse_mount_table(MOUNTINFO_PATH)?; let mount_infos = parse_mount_table(MOUNTINFO_PATH)?;
let mut max_len = 0; let mut max_len = 0;
let mut mount_point = String::from(""); let mut mount_point = String::from("");
let mut options = String::from(""); let mut options = String::from("");
@ -767,9 +769,9 @@ fn mount_from(
rootfs: &str, rootfs: &str,
flags: MsFlags, flags: MsFlags,
data: &str, data: &str,
_label: &str, label: &str,
) -> Result<()> { ) -> Result<()> {
let d = String::from(data); let mut d = String::from(data);
let dest = secure_join(rootfs, &m.destination); let dest = secure_join(rootfs, &m.destination);
let src = if m.r#type.as_str() == "bind" { let src = if m.r#type.as_str() == "bind" {
@ -822,6 +824,37 @@ fn mount_from(
e 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( mount(
Some(src.as_str()), Some(src.as_str()),
dest.as_str(), dest.as_str(),
@ -834,6 +867,10 @@ fn mount_from(
e 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) if flags.contains(MsFlags::MS_BIND)
&& flags.intersects( && flags.intersects(
!(MsFlags::MS_REC !(MsFlags::MS_REC

View File

@ -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<bool> {
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);
}
}
}

View File

@ -6,6 +6,7 @@
use crate::container::Config; use crate::container::Config;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use oci::{Linux, LinuxIdMapping, LinuxNamespace, Spec}; use oci::{Linux, LinuxIdMapping, LinuxNamespace, Spec};
use regex::Regex;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Component, PathBuf}; use std::path::{Component, PathBuf};
@ -86,6 +87,23 @@ fn hostname(oci: &Spec) -> Result<()> {
fn security(oci: &Spec) -> Result<()> { fn security(oci: &Spec) -> Result<()> {
let linux = get_linux(oci)?; 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() { if linux.masked_paths.is_empty() && linux.readonly_paths.is_empty() {
return Ok(()); return Ok(());
@ -95,8 +113,6 @@ fn security(oci: &Spec) -> Result<()> {
return Err(anyhow!("Linux namespace does not contain mount")); return Err(anyhow!("Linux namespace does not contain mount"));
} }
// don't care about selinux at present
Ok(()) Ok(())
} }
@ -285,7 +301,7 @@ pub fn validate(conf: &Config) -> Result<()> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use oci::Mount; use oci::{Mount, Process};
#[test] #[test]
fn test_namespace() { fn test_namespace() {
@ -388,6 +404,29 @@ mod tests {
]; ];
spec.linux = Some(linux); spec.linux = Some(linux);
security(&spec).unwrap(); 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] #[test]