From 7c4ff4123f3d2102aa6646c57847144bbcedc0dc Mon Sep 17 00:00:00 2001 From: higuruchi Date: Tue, 6 Dec 2022 05:22:31 +0000 Subject: [PATCH] Support recursive mount attrs by using mount_setattr(2). Signed-off-by: higuruchi --- Cargo.lock | 10 + crates/libcontainer/Cargo.toml | 1 + crates/libcontainer/src/rootfs/mount.rs | 113 ++-- crates/libcontainer/src/rootfs/utils.rs | 490 ++++++++++++------ crates/libcontainer/src/syscall/linux.rs | 63 ++- crates/libcontainer/src/syscall/syscall.rs | 14 +- crates/libcontainer/src/syscall/test.rs | 13 +- .../integration_test/src/main.rs | 2 +- .../integration_test/src/tests/mod.rs | 2 +- 9 files changed, 507 insertions(+), 201 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f393797d..ff84387c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1247,6 +1247,7 @@ dependencies = [ "mio", "nix", "oci-spec", + "openat", "path-clean", "prctl", "procfs", @@ -1637,6 +1638,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "openat" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95aa7c05907b3ebde2610d602f4ddd992145cc6a84493647c30396f30ba83abe" +dependencies = [ + "libc", +] + [[package]] name = "os_str_bytes" version = "6.4.1" diff --git a/crates/libcontainer/Cargo.toml b/crates/libcontainer/Cargo.toml index e44bcf493..6719d01cf 100644 --- a/crates/libcontainer/Cargo.toml +++ b/crates/libcontainer/Cargo.toml @@ -45,6 +45,7 @@ rust-criu = "0.2.0" wasmer = { version = "2.2.0", optional = true } wasmer-wasi = { version = "2.3.0", optional = true } wasmedge-sdk = { version = "0.7.1", optional = true } +openat = "0.1.21" [dev-dependencies] oci-spec = { version = "0.5.8", features = ["proptests", "runtime"] } diff --git a/crates/libcontainer/src/rootfs/mount.rs b/crates/libcontainer/src/rootfs/mount.rs index ee65e06c8..417de6d7e 100644 --- a/crates/libcontainer/src/rootfs/mount.rs +++ b/crates/libcontainer/src/rootfs/mount.rs @@ -1,8 +1,8 @@ #[cfg(feature = "v1")] use super::symlink::Symlink; -use super::utils::{find_parent_mount, parse_mount}; +use super::utils::{find_parent_mount, parse_mount, MountOptionConfig}; use crate::{ - syscall::{syscall::create_syscall, Syscall}, + syscall::{linux, syscall::create_syscall, Syscall}, utils, utils::PathBufExt, }; @@ -14,9 +14,13 @@ use libcgroups::common::CgroupSetup::{Hybrid, Legacy, Unified}; use libcgroups::common::DEFAULT_CGROUP_ROOT; use nix::{errno::Errno, mount::MsFlags}; use oci_spec::runtime::{Mount as SpecMount, MountBuilder as SpecMountBuilder}; +use openat::Dir; use procfs::process::{MountInfo, MountOptFields, Process}; use std::fs::{canonicalize, create_dir_all, OpenOptions}; +use std::mem; +use std::os::unix::io::AsRawFd; use std::path::{Path, PathBuf}; + #[cfg(feature = "v1")] use std::{borrow::Cow, collections::HashMap}; @@ -46,7 +50,7 @@ impl Mount { pub fn setup_mount(&self, mount: &SpecMount, options: &MountOptions) -> Result<()> { log::debug!("Mounting {:?}", mount); - let (flags, data) = parse_mount(mount); + let mut mount_option_config = parse_mount(mount); match mount.typ().as_deref() { Some("cgroup") => { @@ -64,24 +68,29 @@ impl Mount { #[cfg(not(feature = "v2"))] panic!("libcontainer can't run in a Unified cgroup setup without the v2 feature"); #[cfg(feature = "v2")] - self.mount_cgroup_v2(mount, options, flags, &data) + self.mount_cgroup_v2(mount, options, &mount_option_config) .context("failed to mount cgroup v2")? } } } _ => { if *mount.destination() == PathBuf::from("/dev") { + mount_option_config.flags &= !MsFlags::MS_RDONLY; self.mount_into_container( mount, options.root, - flags & !MsFlags::MS_RDONLY, - &data, + &mount_option_config, options.label, ) .with_context(|| format!("failed to mount /dev: {:?}", mount))?; } else { - self.mount_into_container(mount, options.root, flags, &data, options.label) - .with_context(|| format!("failed to mount: {:?}", mount))?; + self.mount_into_container( + mount, + options.root, + &mount_option_config, + options.label, + ) + .with_context(|| format!("failed to mount: {:?}", mount))?; } } } @@ -197,11 +206,16 @@ impl Mount { subsystem_name.into() }; + let mount_options_config = MountOptionConfig { + flags: MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV, + data: data.to_string(), + rec_attr: None, + }; + self.mount_into_container( &subsystem_mount, options.root, - MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV, - &data, + &mount_options_config, options.label, ) .with_context(|| format!("failed to mount {:?}", subsystem_mount)) @@ -271,8 +285,7 @@ impl Mount { &self, cgroup_mount: &SpecMount, options: &MountOptions, - flags: MsFlags, - data: &str, + mount_option_config: &MountOptionConfig, ) -> Result<()> { log::debug!("Mounting cgroup v2 filesystem"); @@ -285,7 +298,12 @@ impl Mount { log::debug!("{:?}", cgroup_mount); if self - .mount_into_container(&cgroup_mount, options.root, flags, data, options.label) + .mount_into_container( + &cgroup_mount, + options.root, + mount_option_config, + options.label, + ) .context("failed to mount into container") .is_err() { @@ -309,11 +327,12 @@ impl Mount { .context("failed to build cgroup bind mount")?; log::debug!("{:?}", bind_mount); + let mut mount_option_config = (*mount_option_config).clone(); + mount_option_config.flags |= MsFlags::MS_BIND; self.mount_into_container( &bind_mount, options.root, - flags | MsFlags::MS_BIND, - data, + &mount_option_config, options.label, ) .context("failed to bind mount cgroup hierarchy")?; @@ -351,18 +370,17 @@ impl Mount { &self, m: &SpecMount, rootfs: &Path, - flags: MsFlags, - data: &str, + mount_option_config: &MountOptionConfig, label: Option<&str>, ) -> Result<()> { let typ = m.typ().as_deref(); - let mut d = data.to_string(); + let mut d = mount_option_config.data.to_string(); if let Some(l) = label { if typ != Some("proc") && typ != Some("sysfs") { - match data.is_empty() { + match mount_option_config.data.is_empty() { true => d = format!("context=\"{}\"", l), - false => d = format!("{},context=\"{}\"", data, l), + false => d = format!("{},context=\"{}\"", mount_option_config.data, l), } } } @@ -402,7 +420,10 @@ impl Mount { PathBuf::from(source) }; - if let Err(err) = self.syscall.mount(Some(&*src), dest, typ, flags, Some(&*d)) { + if let Err(err) = + self.syscall + .mount(Some(&*src), dest, typ, mount_option_config.flags, Some(&*d)) + { if let Some(errno) = err.downcast_ref() { if !matches!(errno, Errno::EINVAL) { bail!("mount of {:?} failed. {}", m.destination(), errno); @@ -410,12 +431,18 @@ impl Mount { } self.syscall - .mount(Some(&*src), dest, typ, flags, Some(data)) + .mount( + Some(&*src), + dest, + typ, + mount_option_config.flags, + Some(&mount_option_config.data), + ) .with_context(|| format!("failed to mount {:?} to {:?}", src, dest))?; } if typ == Some("bind") - && flags.intersects( + && mount_option_config.flags.intersects( !(MsFlags::MS_REC | MsFlags::MS_REMOUNT | MsFlags::MS_BIND @@ -425,10 +452,28 @@ impl Mount { ) { self.syscall - .mount(Some(dest), dest, None, flags | MsFlags::MS_REMOUNT, None) + .mount( + Some(dest), + dest, + None, + mount_option_config.flags | MsFlags::MS_REMOUNT, + None, + ) .with_context(|| format!("Failed to remount: {:?}", dest))?; } + if let Some(mount_attr) = &mount_option_config.rec_attr { + let open_dir = Dir::open(dest)?; + let dir_fd_pathbuf = PathBuf::from(format!("/proc/self/fd/{}", open_dir.as_raw_fd())); + self.syscall.mount_setattr( + -1, + &dir_fd_pathbuf, + linux::AT_RECURSIVE, + mount_attr, + mem::size_of::(), + )?; + } + Ok(()) } } @@ -462,10 +507,15 @@ mod tests { ]) .build() .unwrap(); - let (flags, data) = parse_mount(mount); + let mount_option_config = parse_mount(mount); assert!(m - .mount_into_container(mount, tmp_dir.path(), flags, &data, Some("defaults")) + .mount_into_container( + mount, + tmp_dir.path(), + &mount_option_config, + Some("defaults") + ) .is_ok()); let want = vec![MountArgs { @@ -495,7 +545,7 @@ mod tests { .options(vec!["ro".to_string()]) .build() .unwrap(); - let (flags, data) = parse_mount(mount); + let mount_option_config = parse_mount(mount); OpenOptions::new() .create(true) .write(true) @@ -503,7 +553,7 @@ mod tests { .unwrap(); assert!(m - .mount_into_container(mount, tmp_dir.path(), flags, &data, None) + .mount_into_container(mount, tmp_dir.path(), &mount_option_config, None) .is_ok()); let want = vec![ @@ -768,8 +818,13 @@ mod tests { let flags = MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV; // act + let mount_option_config = MountOptionConfig { + flags, + data: String::new(), + rec_attr: None, + }; mounter - .mount_cgroup_v2(&spec_cgroup_mount, &mount_opts, flags, "") + .mount_cgroup_v2(&spec_cgroup_mount, &mount_opts, &mount_option_config) .context("failed to mount cgroup v2")?; // assert diff --git a/crates/libcontainer/src/rootfs/utils.rs b/crates/libcontainer/src/rootfs/utils.rs index cfc9c5696..612e0b625 100644 --- a/crates/libcontainer/src/rootfs/utils.rs +++ b/crates/libcontainer/src/rootfs/utils.rs @@ -4,6 +4,18 @@ use oci_spec::runtime::{LinuxDevice, LinuxDeviceBuilder, LinuxDeviceType, Mount} use procfs::process::MountInfo; use std::path::{Path, PathBuf}; +use crate::syscall::linux; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MountOptionConfig { + // Mount Flags. + pub flags: MsFlags, + // Mount data applied to the mount. + pub data: String, + // RecAttr represents mount properties to be applied recrusively. + pub rec_attr: Option, +} + pub fn default_devices() -> Vec { vec![ LinuxDeviceBuilder::default() @@ -66,9 +78,11 @@ pub fn to_sflag(dev_type: LinuxDeviceType) -> SFlag { } } -pub fn parse_mount(m: &Mount) -> (MsFlags, String) { +pub fn parse_mount(m: &Mount) -> MountOptionConfig { let mut flags = MsFlags::empty(); let mut data = Vec::new(); + let mut mount_attr: Option = None; + if let Some(options) = &m.options() { for s in options { if let Some((is_clear, flag)) = match s.as_str() { @@ -112,12 +126,64 @@ pub fn parse_mount(m: &Mount) -> (MsFlags, String) { } else { flags |= flag; } - } else { - data.push(s.as_str()); - }; + continue; + } + + if let Some((is_clear, flag)) = match s.as_str() { + "rro" => Some((false, linux::MOUNT_ATTR_RDONLY)), + "rrw" => Some((true, linux::MOUNT_ATTR_RDONLY)), + "rnosuid" => Some((false, linux::MOUNT_ATTR_NOSUID)), + "rsuid" => Some((true, linux::MOUNT_ATTR_NOSUID)), + "rnodev" => Some((false, linux::MOUNT_ATTR_NODEV)), + "rdev" => Some((true, linux::MOUNT_ATTR_NODEV)), + "rnoexec" => Some((false, linux::MOUNT_ATTR_NOEXEC)), + "rexec" => Some((true, linux::MOUNT_ATTR_NOEXEC)), + "rnodiratime" => Some((false, linux::MOUNT_ATTR_NODIRATIME)), + "rdiratime" => Some((true, linux::MOUNT_ATTR_NODIRATIME)), + "rrelatime" => Some((false, linux::MOUNT_ATTR_RELATIME)), + "rnorelatime" => Some((true, linux::MOUNT_ATTR_RELATIME)), + "rnoatime" => Some((false, linux::MOUNT_ATTR_NOATIME)), + "ratime" => Some((true, linux::MOUNT_ATTR_NOATIME)), + "rstrictatime" => Some((false, linux::MOUNT_ATTR_STRICTATIME)), + "rnostrictatime" => Some((true, linux::MOUNT_ATTR_STRICTATIME)), + "rnosymfollow" => Some((false, linux::MOUNT_ATTR_NOSYMFOLLOW)), + "rsymfollow" => Some((true, linux::MOUNT_ATTR_NOSYMFOLLOW)), + // No support for MOUNT_ATTR_IDMAP yet (needs UserNS FD) + _ => None, + } { + if mount_attr.is_none() { + mount_attr = Some(linux::MountAttr { + attr_set: 0, + attr_clr: 0, + propagation: 0, + userns_fd: 0, + }); + } + + if let Some(mount_attr) = &mut mount_attr { + if is_clear { + mount_attr.attr_clr |= flag; + } else { + mount_attr.attr_set |= flag; + if flag & linux::MOUNT_ATTR__ATIME == flag { + // https://man7.org/linux/man-pages/man2/mount_setattr.2.html + // cannot simply specify the access-time setting in attr_set, but must + // also include MOUNT_ATTR__ATIME in the attr_clr field. + mount_attr.attr_clr |= linux::MOUNT_ATTR__ATIME; + } + } + } + continue; + } + + data.push(s.as_str()); } } - (flags, data.join(",")) + MountOptionConfig { + flags, + data: data.join(","), + rec_attr: mount_attr, + } } /// Find parent mount of rootfs in given mount infos @@ -193,141 +259,214 @@ mod tests { #[test] fn test_parse_mount() { + let mount_option_config = parse_mount( + &MountBuilder::default() + .destination(PathBuf::from("/proc")) + .typ("proc") + .source(PathBuf::from("proc")) + .build() + .unwrap(), + ); assert_eq!( - (MsFlags::empty(), "".to_string()), - parse_mount( - &MountBuilder::default() - .destination(PathBuf::from("/proc")) - .typ("proc") - .source(PathBuf::from("proc")) - .build() - .unwrap() - ) + MountOptionConfig { + flags: MsFlags::empty(), + data: "".to_string(), + rec_attr: None, + }, + mount_option_config + ); + + let mount_option_config = parse_mount( + &MountBuilder::default() + .destination(PathBuf::from("/dev")) + .typ("tmpfs") + .source(PathBuf::from("tmpfs")) + .options(vec![ + "nosuid".to_string(), + "strictatime".to_string(), + "mode=755".to_string(), + "size=65536k".to_string(), + ]) + .build() + .unwrap(), ); assert_eq!( - (MsFlags::MS_NOSUID, "mode=755,size=65536k".to_string()), - parse_mount( - &MountBuilder::default() - .destination(PathBuf::from("/dev")) - .typ("tmpfs") - .source(PathBuf::from("tmpfs")) - .options(vec![ - "nosuid".to_string(), - "strictatime".to_string(), - "mode=755".to_string(), - "size=65536k".to_string(), - ]) - .build() - .unwrap() - ) + MountOptionConfig { + flags: MsFlags::MS_NOSUID, + data: "mode=755,size=65536k".to_string(), + rec_attr: None, + }, + mount_option_config + ); + + let mount_option_config = parse_mount( + &MountBuilder::default() + .destination(PathBuf::from("/dev/pts")) + .typ("devpts") + .source(PathBuf::from("devpts")) + .options(vec![ + "nosuid".to_string(), + "noexec".to_string(), + "newinstance".to_string(), + "ptmxmode=0666".to_string(), + "mode=0620".to_string(), + "gid=5".to_string(), + ]) + .build() + .unwrap(), ); assert_eq!( - ( - MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC, - "newinstance,ptmxmode=0666,mode=0620,gid=5".to_string() - ), - parse_mount( - &MountBuilder::default() - .destination(PathBuf::from("/dev/pts")) - .typ("devpts") - .source(PathBuf::from("devpts")) - .options(vec![ - "nosuid".to_string(), - "noexec".to_string(), - "newinstance".to_string(), - "ptmxmode=0666".to_string(), - "mode=0620".to_string(), - "gid=5".to_string(), - ]) - .build() - .unwrap() - ) + MountOptionConfig { + flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC, + data: "newinstance,ptmxmode=0666,mode=0620,gid=5".to_string(), + rec_attr: None + }, + mount_option_config + ); + + let mount_option_config = parse_mount( + &MountBuilder::default() + .destination(PathBuf::from("/dev/shm")) + .typ("tmpfs") + .source(PathBuf::from("shm")) + .options(vec![ + "nosuid".to_string(), + "noexec".to_string(), + "nodev".to_string(), + "mode=1777".to_string(), + "size=65536k".to_string(), + ]) + .build() + .unwrap(), ); assert_eq!( - ( - MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV, - "mode=1777,size=65536k".to_string() - ), - parse_mount( - &MountBuilder::default() - .destination(PathBuf::from("/dev/shm")) - .typ("tmpfs") - .source(PathBuf::from("shm")) - .options(vec![ - "nosuid".to_string(), - "noexec".to_string(), - "nodev".to_string(), - "mode=1777".to_string(), - "size=65536k".to_string(), - ]) - .build() - .unwrap() - ) + MountOptionConfig { + flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV, + data: "mode=1777,size=65536k".to_string(), + rec_attr: None + }, + mount_option_config + ); + + let mount_option_config = parse_mount( + &MountBuilder::default() + .destination(PathBuf::from("/dev/mqueue")) + .typ("mqueue") + .source(PathBuf::from("mqueue")) + .options(vec![ + "nosuid".to_string(), + "noexec".to_string(), + "nodev".to_string(), + ]) + .build() + .unwrap(), ); assert_eq!( - ( - MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV, - "".to_string() - ), - parse_mount( - &MountBuilder::default() - .destination(PathBuf::from("/dev/mqueue")) - .typ("mqueue") - .source(PathBuf::from("mqueue")) - .options(vec![ - "nosuid".to_string(), - "noexec".to_string(), - "nodev".to_string(), - ]) - .build() - .unwrap() - ) + MountOptionConfig { + flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV, + data: "".to_string(), + rec_attr: None + }, + mount_option_config + ); + + let mount_option_config = parse_mount( + &MountBuilder::default() + .destination(PathBuf::from("/sys")) + .typ("sysfs") + .source(PathBuf::from("sysfs")) + .options(vec![ + "nosuid".to_string(), + "noexec".to_string(), + "nodev".to_string(), + "ro".to_string(), + ]) + .build() + .unwrap(), ); assert_eq!( - ( - MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV | MsFlags::MS_RDONLY, - "".to_string() - ), - parse_mount( - &MountBuilder::default() - .destination(PathBuf::from("/sys")) - .typ("sysfs") - .source(PathBuf::from("sysfs")) - .options(vec![ - "nosuid".to_string(), - "noexec".to_string(), - "nodev".to_string(), - "ro".to_string(), - ]) - .build() - .unwrap() - ) + MountOptionConfig { + flags: MsFlags::MS_NOSUID + | MsFlags::MS_NOEXEC + | MsFlags::MS_NODEV + | MsFlags::MS_RDONLY, + data: "".to_string(), + rec_attr: None, + }, + mount_option_config + ); + + let mount_option_config = parse_mount( + &MountBuilder::default() + .destination(PathBuf::from("/sys/fs/cgroup")) + .typ("cgroup") + .source(PathBuf::from("cgroup")) + .options(vec![ + "nosuid".to_string(), + "noexec".to_string(), + "nodev".to_string(), + "relatime".to_string(), + "ro".to_string(), + ]) + .build() + .unwrap(), ); assert_eq!( - ( - MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV | MsFlags::MS_RDONLY, - "".to_string() - ), - parse_mount( - &MountBuilder::default() - .destination(PathBuf::from("/sys/fs/cgroup")) - .typ("cgroup") - .source(PathBuf::from("cgroup")) - .options(vec![ - "nosuid".to_string(), - "noexec".to_string(), - "nodev".to_string(), - "relatime".to_string(), - "ro".to_string(), - ]) - .build() - .unwrap() - ) + MountOptionConfig { + flags: MsFlags::MS_NOSUID + | MsFlags::MS_NOEXEC + | MsFlags::MS_NODEV + | MsFlags::MS_RDONLY, + data: "".to_string(), + rec_attr: None + }, + mount_option_config, ); + // this case is just for coverage purpose + let mount_option_config = parse_mount( + &MountBuilder::default() + .options(vec![ + "defaults".to_string(), + "ro".to_string(), + "rw".to_string(), + "suid".to_string(), + "nosuid".to_string(), + "dev".to_string(), + "nodev".to_string(), + "exec".to_string(), + "noexec".to_string(), + "sync".to_string(), + "async".to_string(), + "dirsync".to_string(), + "remount".to_string(), + "mand".to_string(), + "nomand".to_string(), + "atime".to_string(), + "noatime".to_string(), + "diratime".to_string(), + "nodiratime".to_string(), + "bind".to_string(), + "rbind".to_string(), + "unbindable".to_string(), + "runbindable".to_string(), + "private".to_string(), + "rprivate".to_string(), + "shared".to_string(), + "rshared".to_string(), + "slave".to_string(), + "rslave".to_string(), + "relatime".to_string(), + "norelatime".to_string(), + "strictatime".to_string(), + "nostrictatime".to_string(), + ]) + .build() + .unwrap(), + ); assert_eq!( - ( - MsFlags::MS_NOSUID + MountOptionConfig { + flags: MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_REMOUNT @@ -336,48 +475,67 @@ mod tests { | MsFlags::MS_NODIRATIME | MsFlags::MS_BIND | MsFlags::MS_UNBINDABLE, - "".to_string() - ), - parse_mount( - &MountBuilder::default() - .options(vec![ - "defaults".to_string(), - "ro".to_string(), - "rw".to_string(), - "suid".to_string(), - "nosuid".to_string(), - "dev".to_string(), - "nodev".to_string(), - "exec".to_string(), - "noexec".to_string(), - "sync".to_string(), - "async".to_string(), - "dirsync".to_string(), - "remount".to_string(), - "mand".to_string(), - "nomand".to_string(), - "atime".to_string(), - "noatime".to_string(), - "diratime".to_string(), - "nodiratime".to_string(), - "bind".to_string(), - "rbind".to_string(), - "unbindable".to_string(), - "runbindable".to_string(), - "private".to_string(), - "rprivate".to_string(), - "shared".to_string(), - "rshared".to_string(), - "slave".to_string(), - "rslave".to_string(), - "relatime".to_string(), - "norelatime".to_string(), - "strictatime".to_string(), - "nostrictatime".to_string(), - ]) - .build() - .unwrap() - ) + data: "".to_string(), + rec_attr: None, + }, + mount_option_config + ); + + // this case is just for coverage purpose + let mount_option_config = parse_mount( + &MountBuilder::default() + .options(vec![ + "rro".to_string(), + "rrw".to_string(), + "rnosuid".to_string(), + "rsuid".to_string(), + "rnodev".to_string(), + "rdev".to_string(), + "rnoexec".to_string(), + "rexec".to_string(), + "rnodiratime".to_string(), + "rdiratime".to_string(), + "rrelatime".to_string(), + "rnorelatime".to_string(), + "rnoatime".to_string(), + "ratime".to_string(), + "rstrictatime".to_string(), + "rnostrictatime".to_string(), + "rnosymfollow".to_string(), + "rsymfollow".to_string(), + ]) + .build() + .unwrap(), + ); + assert_eq!( + MountOptionConfig { + flags: MsFlags::empty(), + data: "".to_string(), + rec_attr: Some(linux::MountAttr { + attr_set: linux::MOUNT_ATTR_RDONLY + | linux::MOUNT_ATTR_NOSUID + | linux::MOUNT_ATTR_NODEV + | linux::MOUNT_ATTR_NOEXEC + | linux::MOUNT_ATTR_NODIRATIME + | linux::MOUNT_ATTR_RELATIME + | linux::MOUNT_ATTR_NOATIME + | linux::MOUNT_ATTR_STRICTATIME + | linux::MOUNT_ATTR_NOSYMFOLLOW, + attr_clr: linux::MOUNT_ATTR_RDONLY + | linux::MOUNT_ATTR_NOSUID + | linux::MOUNT_ATTR_NODEV + | linux::MOUNT_ATTR_NOEXEC + | linux::MOUNT_ATTR_NODIRATIME + | linux::MOUNT_ATTR_RELATIME + | linux::MOUNT_ATTR_NOATIME + | linux::MOUNT_ATTR_STRICTATIME + | linux::MOUNT_ATTR_NOSYMFOLLOW + | linux::MOUNT_ATTR__ATIME, + propagation: 0, + userns_fd: 0, + }), + }, + mount_option_config ); } } diff --git a/crates/libcontainer/src/syscall/linux.rs b/crates/libcontainer/src/syscall/linux.rs index 4f06e88e5..cc65b3000 100644 --- a/crates/libcontainer/src/syscall/linux.rs +++ b/crates/libcontainer/src/syscall/linux.rs @@ -1,8 +1,9 @@ //! Implements Command trait for Linux systems -use std::ffi::{CStr, OsStr}; +use std::ffi::{CStr, CString, OsStr}; use std::fs; use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::symlink; +use std::os::unix::io::RawFd; use std::sync::Arc; use std::{any::Any, mem, path::Path, ptr}; @@ -19,7 +20,7 @@ use nix::{ unistd, unistd::{chown, fchdir, pivot_root, setgroups, sethostname, Gid, Uid}, }; -use syscalls::{syscall, Sysno::close_range}; +use syscalls::{syscall, Sysno, Sysno::close_range}; use oci_spec::runtime::LinuxRlimit; @@ -27,6 +28,33 @@ use super::Syscall; use crate::syscall::syscall::CloseRange; use crate::{capabilities, utils}; +// Constants used by mount_setattr(2). +pub const MOUNT_ATTR_RDONLY: u64 = 0x00000001; // Mount read-only. +pub const MOUNT_ATTR_NOSUID: u64 = 0x00000002; // Ignore suid and sgid bits. +pub const MOUNT_ATTR_NODEV: u64 = 0x00000004; // Disallow access to device special files. +pub const MOUNT_ATTR_NOEXEC: u64 = 0x00000008; // Disallow program execution. +pub const MOUNT_ATTR__ATIME: u64 = 0x00000070; // Setting on how atime should be updated. +pub const MOUNT_ATTR_RELATIME: u64 = 0x00000000; // - Update atime relative to mtime/ctime. +pub const MOUNT_ATTR_NOATIME: u64 = 0x00000010; // - Do not update access times. +pub const MOUNT_ATTR_STRICTATIME: u64 = 0x00000020; // - Always perform atime updates. +pub const MOUNT_ATTR_NODIRATIME: u64 = 0x00000080; // Do not update directory access times. +pub const MOUNT_ATTR_NOSYMFOLLOW: u64 = 0x00200000; // Prevents following symbolic links. +pub const AT_RECURSIVE: u32 = 0x00008000; // Change the mount properties of the entire mount tree. + +#[repr(C)] +#[derive(Debug, Clone, PartialEq, Eq)] +// A structure used as te third argument of mount_setattr(2). +pub struct MountAttr { + // Mount properties to set. + pub attr_set: u64, + // Mount properties to clear. + pub attr_clr: u64, + // Mount propagation type. + pub propagation: u64, + // User namespace file descriptor. + pub userns_fd: u64, +} + /// Empty structure to implement Command trait for #[derive(Clone)] pub struct LinuxSyscall; @@ -334,6 +362,37 @@ impl Syscall for LinuxSyscall { Err(e) => bail!(e), } } + + fn mount_setattr( + &self, + dirfd: RawFd, + pathname: &Path, + flags: u32, + mount_attr: &MountAttr, + size: libc::size_t, + ) -> Result<()> { + let path_pathbuf = pathname.to_path_buf(); + let path_str = path_pathbuf.to_str(); + let path_c_string = match path_str { + Some(path_str) => CString::new(path_str)?, + None => bail!("Invalid filename"), + }; + let result = unsafe { + syscall!( + Sysno::mount_setattr, + dirfd, + path_c_string.as_ptr(), + flags, + mount_attr as *const MountAttr, + size + ) + }; + + match result { + Ok(_) => Ok(()), + Err(e) => bail!(e), + } + } } #[cfg(test)] diff --git a/crates/libcontainer/src/syscall/syscall.rs b/crates/libcontainer/src/syscall/syscall.rs index d10648244..924339acb 100644 --- a/crates/libcontainer/src/syscall/syscall.rs +++ b/crates/libcontainer/src/syscall/syscall.rs @@ -6,6 +6,7 @@ use std::{any::Any, ffi::OsStr, path::Path, sync::Arc}; use anyhow::Result; use bitflags::bitflags; use caps::{CapSet, CapsHashSet}; +use libc; use nix::{ mount::MsFlags, sched::CloneFlags, @@ -15,7 +16,10 @@ use nix::{ use oci_spec::runtime::LinuxRlimit; -use crate::syscall::{linux::LinuxSyscall, test::TestHelperSyscall}; +use crate::syscall::{ + linux::{LinuxSyscall, MountAttr}, + test::TestHelperSyscall, +}; /// This specifies various kernel/other functionalities required for /// container management @@ -44,6 +48,14 @@ pub trait Syscall { fn chown(&self, path: &Path, owner: Option, group: Option) -> Result<()>; fn set_groups(&self, groups: &[Gid]) -> Result<()>; fn close_range(&self, preserve_fds: i32) -> Result<()>; + fn mount_setattr( + &self, + dirfd: i32, + pathname: &Path, + flags: u32, + mount_attr: &MountAttr, + size: libc::size_t, + ) -> Result<()>; } pub fn create_syscall() -> Box { diff --git a/crates/libcontainer/src/syscall/test.rs b/crates/libcontainer/src/syscall/test.rs index c2d35fafb..2bf7f034f 100644 --- a/crates/libcontainer/src/syscall/test.rs +++ b/crates/libcontainer/src/syscall/test.rs @@ -17,7 +17,7 @@ use nix::{ use oci_spec::runtime::LinuxRlimit; -use super::Syscall; +use super::{linux, Syscall}; #[derive(Clone, PartialEq, Eq, Debug)] pub struct MountArgs { @@ -238,6 +238,17 @@ impl Syscall for TestHelperSyscall { fn close_range(&self, _: i32) -> anyhow::Result<()> { todo!() } + + fn mount_setattr( + &self, + _: i32, + _: &Path, + _: u32, + _: &linux::MountAttr, + _: libc::size_t, + ) -> anyhow::Result<()> { + todo!() + } } impl TestHelperSyscall { diff --git a/tests/rust-integration-tests/integration_test/src/main.rs b/tests/rust-integration-tests/integration_test/src/main.rs index 6bead642a..eaeadec8e 100644 --- a/tests/rust-integration-tests/integration_test/src/main.rs +++ b/tests/rust-integration-tests/integration_test/src/main.rs @@ -2,13 +2,13 @@ mod tests; mod utils; use crate::tests::hooks::get_hooks_tests; +use crate::tests::hostname::get_hostname_test; use crate::tests::lifecycle::{ContainerCreate, ContainerLifecycle}; use crate::tests::linux_ns_itype::get_ns_itype_tests; use crate::tests::pidfile::get_pidfile_test; use crate::tests::readonly_paths::get_ro_paths_test; use crate::tests::seccomp_notify::get_seccomp_notify_test; use crate::tests::tlb::get_tlb_test; -use crate::tests::hostname::get_hostname_test; use crate::utils::support::{set_runtime_path, set_runtimetest_path}; use anyhow::{Context, Result}; use clap::Parser; diff --git a/tests/rust-integration-tests/integration_test/src/tests/mod.rs b/tests/rust-integration-tests/integration_test/src/tests/mod.rs index ae2f3dbbf..64b1939d8 100644 --- a/tests/rust-integration-tests/integration_test/src/tests/mod.rs +++ b/tests/rust-integration-tests/integration_test/src/tests/mod.rs @@ -1,9 +1,9 @@ pub mod cgroups; pub mod hooks; +pub mod hostname; pub mod lifecycle; pub mod linux_ns_itype; pub mod pidfile; pub mod readonly_paths; pub mod seccomp_notify; pub mod tlb; -pub mod hostname;