Skip to content

Commit

Permalink
Implement apparmor support (#312)
Browse files Browse the repository at this point in the history
* Implement apparmor support

* Add explanation to apparmor test
  • Loading branch information
Furisto authored Sep 20, 2021
1 parent 2af5216 commit 19316ce
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 20 deletions.
2 changes: 2 additions & 0 deletions integration_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ test_cases=(
"linux_ns_nopath/linux_ns_nopath.t"
"linux_ns_path/linux_ns_path.t"
"linux_ns_path_type/linux_ns_path_type.t"
# This test case requires that an apparmor profile named 'acme_secure_profile' has been installed on the system. It needs to allow the capabilites
# validated by runtime-tools otherwise the test case will fail despite the profile being available.
# "linux_process_apparmor_profile/linux_process_apparmor_profile.t"
"linux_readonly_paths/linux_readonly_paths.t"
"linux_rootfs_propagation/linux_rootfs_propagation.t"
Expand Down
37 changes: 37 additions & 0 deletions src/apparmor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use anyhow::{Context, Result};
use std::{
fs::{self},
path::Path,
};

use crate::utils;

const ENABLED_PARAMETER_PATH: &str = "/sys/module/apparmor/parameters/enabled";

/// Checks if AppArmor has been enabled on the system.
pub fn is_enabled() -> Result<bool> {
let aa_enabled = fs::read_to_string(ENABLED_PARAMETER_PATH)
.with_context(|| format!("could not read {}", ENABLED_PARAMETER_PATH))?;
Ok(aa_enabled.starts_with('Y'))
}

/// Applies an AppArmor profile to the container.
pub fn apply_profile(profile: &str) -> Result<()> {
if profile.is_empty() {
return Ok(());
}

// Try the module specific subdirectory. This is the recommended way to configure
// LSMs since Linux 5.1. AppArmor has such a directory since Linux 5.8.
if activate_profile(Path::new("/proc/self/attr/apparmor/exec"), profile).is_ok() {
return Ok(());
}

// try the legacy interface
activate_profile(Path::new("/proc/self/attr/exec"), profile)
}

fn activate_profile(path: &Path, profile: &str) -> Result<()> {
utils::ensure_procfs(path)?;
utils::write_file(path, format!("exec {}", profile))
}
25 changes: 22 additions & 3 deletions src/container/init_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{
path::{Path, PathBuf},
};

use crate::{notify_socket::NOTIFY_FILE, rootless, tty, utils};
use crate::{apparmor, notify_socket::NOTIFY_FILE, rootless, tty, utils};

use super::{
builder::ContainerBuilder, builder_impl::ContainerBuilderImpl, Container, ContainerStatus,
Expand Down Expand Up @@ -100,14 +100,33 @@ impl<'a> InitContainerBuilder<'a> {
fn load_spec(&self) -> Result<Spec> {
let source_spec_path = self.bundle.join("config.json");
let mut spec = Spec::load(&source_spec_path)?;
Self::validate_spec(&spec).context("failed to validate runtime spec")?;

spec.canonicalize_rootfs(&self.bundle)?;
Ok(spec)
}

fn validate_spec(spec: &Spec) -> Result<()> {
if !spec.version.starts_with("1.0") {
bail!(
"runtime spec has incompatible version '{}'. Only 1.0.X is supported",
spec.version
);
}
spec.canonicalize_rootfs(&self.bundle)?;
Ok(spec)

if let Some(process) = &spec.process {
if let Some(profile) = &process.apparmor_profile {
if !apparmor::is_enabled()? {
bail!(
"apparmor profile {} is specified in runtime spec, \
but apparmor is not activated on this system",
profile
);
}
}
}

Ok(())
}

fn save_spec(&self, spec: &Spec, container_dir: &Path) -> Result<()> {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod apparmor;
pub mod capabilities;
pub mod commands;
pub mod container;
Expand Down
25 changes: 8 additions & 17 deletions src/process/init.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::args::ContainerArgs;
use crate::apparmor;
use crate::{
capabilities, hooks, namespaces::Namespaces, process::channel, rootfs, rootless::Rootless,
seccomp, tty, utils,
Expand All @@ -9,34 +10,19 @@ use nix::mount::MsFlags;
use nix::sched::CloneFlags;
use nix::{
fcntl,
sys::statfs,
unistd::{self, Gid, Uid},
};
use oci_spec::runtime::{LinuxNamespaceType, User};
use std::collections::HashMap;
use std::{
env, fs,
os::unix::io::AsRawFd,
path::{Path, PathBuf},
};

// Make sure a given path is on procfs. This is to avoid the security risk that
// /proc path is mounted over. Ref: CVE-2019-16884
fn ensure_procfs(path: &Path) -> Result<()> {
let procfs_fd = fs::File::open(path)?;
let fstat_info = statfs::fstatfs(&procfs_fd.as_raw_fd())?;

if fstat_info.filesystem_type() != statfs::PROC_SUPER_MAGIC {
bail!(format!("{:?} is not on the procfs", path));
}

Ok(())
}

// Get a list of open fds for the calling process.
fn get_open_fds() -> Result<Vec<i32>> {
const PROCFS_FD_PATH: &str = "/proc/self/fd";
ensure_procfs(Path::new(PROCFS_FD_PATH))
utils::ensure_procfs(Path::new(PROCFS_FD_PATH))
.with_context(|| format!("{} is not the actual procfs", PROCFS_FD_PATH))?;

let fds: Vec<i32> = fs::read_dir(PROCFS_FD_PATH)?
Expand Down Expand Up @@ -257,6 +243,11 @@ pub fn container_init(
}
}

if let Some(profile) = &proc.apparmor_profile {
apparmor::apply_profile(profile)
.with_context(|| format!("failed to apply apparmor profile {}", profile))?;
}

if let Some(true) = spec.root.as_ref().map(|r| r.readonly.unwrap_or(false)) {
nix_mount(
None::<&str>,
Expand Down Expand Up @@ -470,7 +461,7 @@ mod tests {
use anyhow::{bail, Result};
use nix::{fcntl, sys, unistd};
use serial_test::serial;
use std::fs;
use std::{fs, os::unix::prelude::AsRawFd};

// Note: We have to run these tests here as serial. The main issue is that
// these tests has a dependency on the system state. The
Expand Down
15 changes: 15 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ use std::fs::{self, DirBuilder, File};
use std::ops::Deref;
use std::os::linux::fs::MetadataExt;
use std::os::unix::fs::DirBuilderExt;
use std::os::unix::prelude::AsRawFd;
use std::path::{Path, PathBuf};
use std::time::Duration;

use anyhow::Context;
use anyhow::{bail, Result};
use nix::sys::stat::Mode;
use nix::sys::statfs;
use nix::unistd;

pub trait PathBufExt {
Expand Down Expand Up @@ -152,6 +154,19 @@ pub fn create_dir_all_with_mode<P: AsRef<Path>>(path: P, owner: u32, mode: Mode)
}
}

// Make sure a given path is on procfs. This is to avoid the security risk that
// /proc path is mounted over. Ref: CVE-2019-16884
pub fn ensure_procfs(path: &Path) -> Result<()> {
let procfs_fd = fs::File::open(path)?;
let fstat_info = statfs::fstatfs(&procfs_fd.as_raw_fd())?;

if fstat_info.filesystem_type() != statfs::PROC_SUPER_MAGIC {
bail!(format!("{:?} is not on the procfs", path));
}

Ok(())
}

pub struct TempDir {
path: Option<PathBuf>,
}
Expand Down

0 comments on commit 19316ce

Please sign in to comment.