Skip to content

Commit

Permalink
agent: Only allow proc mount if it is procfs
Browse files Browse the repository at this point in the history
This only allows some whitelists files bind mounted under proc
and prevent other malicious mount to procfs.

Fixes: kata-containers#807

Signed-off-by: fupan.lfp <[email protected]>
  • Loading branch information
lifupan committed Sep 25, 2020
1 parent 33513fb commit acaa806
Showing 1 changed file with 95 additions and 0 deletions.
95 changes: 95 additions & 0 deletions src/agent/rustjail/src/mount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use nix::NixPath;
use oci::{LinuxDevice, Mount, Spec};
use std::collections::{HashMap, HashSet};
use std::fs::{self, OpenOptions};
use std::mem::MaybeUninit;
use std::os::unix;
use std::os::unix::io::RawFd;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -48,6 +49,13 @@ pub struct Info {
}

const MOUNTINFOFORMAT: &'static str = "{d} {d} {d}:{d} {} {} {} {}";
const PROC_PATH: &str = "/proc";

// since libc didn't defined this const for musl, thus redefined it here.
#[cfg(all(target_os = "linux", target_env = "gnu"))]
const PROC_SUPER_MAGIC: libc::c_long = 0x00009fa0;
#[cfg(all(target_os = "linux", target_env = "musl"))]
const PROC_SUPER_MAGIC: libc::c_ulong = 0x00009fa0;

lazy_static! {
static ref PROPAGATION: HashMap<&'static str, MsFlags> = {
Expand Down Expand Up @@ -181,13 +189,18 @@ pub fn init_rootfs(
m.destination
));
}

if m.r#type == "cgroup" {
mount_cgroups(cfd_log, &m, rootfs, flags, &data, cpath, mounts)?;
} else {
if m.destination == "/dev" {
flags &= !MsFlags::MS_RDONLY;
}

if m.r#type == "bind" {
check_proc_mount(m)?;
}

mount_from(cfd_log, &m, &rootfs, flags, &data, "")?;
// bind mount won't change mount options, we need remount to make mount options
// effective.
Expand Down Expand Up @@ -216,6 +229,59 @@ pub fn init_rootfs(
Ok(())
}

fn check_proc_mount(m: &Mount) -> Result<()> {
// White list, it should be sub directories of invalid destinations
// These entries can be bind mounted by files emulated by fuse,
// so commands like top, free displays stats in container.
let valid_destinations = [
"/proc/cpuinfo",
"/proc/diskstats",
"/proc/meminfo",
"/proc/stat",
"/proc/swaps",
"/proc/uptime",
"/proc/loadavg",
"/proc/net/dev",
];

for i in valid_destinations.iter() {
if m.destination.as_str() == *i {
return Ok(());
}
}

if m.destination == PROC_PATH {
// only allow a mount on-top of proc if it's source is "proc"
unsafe {
let mut stats = MaybeUninit::<libc::statfs>::uninit();
if let Ok(_) = m
.source
.with_nix_path(|path| libc::statfs(path.as_ptr(), stats.as_mut_ptr()))
{
if stats.assume_init().f_type == PROC_SUPER_MAGIC {
return Ok(());
}
} else {
return Ok(());
}

return Err(anyhow!(format!(
"{} cannot be mounted to {} because it is not of type proc",
m.source, m.destination
)));
}
}

if m.destination.starts_with(PROC_PATH) {
return Err(anyhow!(format!(
"{} cannot be mounted because it is inside /proc",
m.destination
)));
}

return Ok(());
}

fn mount_cgroups_v2(cfd_log: RawFd, m: &Mount, rootfs: &str, flags: MsFlags) -> Result<()> {
let olddir = unistd::getcwd()?;
unistd::chdir(rootfs)?;
Expand Down Expand Up @@ -1126,4 +1192,33 @@ mod tests {
let ret = stat::stat("fifo");
assert!(ret.is_ok(), "Should pass. Got: {:?}", ret);
}
#[test]
fn test_check_proc_mount() {
let mount = oci::Mount {
destination: "/proc".to_string(),
r#type: "bind".to_string(),
source: "/test".to_string(),
options: vec!["shared".to_string()],
};

assert!(check_proc_mount(&mount).is_err());

let mount = oci::Mount {
destination: "/proc/cpuinfo".to_string(),
r#type: "bind".to_string(),
source: "/test".to_string(),
options: vec!["shared".to_string()],
};

assert!(check_proc_mount(&mount).is_ok());

let mount = oci::Mount {
destination: "/proc/test".to_string(),
r#type: "bind".to_string(),
source: "/test".to_string(),
options: vec!["shared".to_string()],
};

assert!(check_proc_mount(&mount).is_err());
}
}

0 comments on commit acaa806

Please sign in to comment.