From 976bd5ec1b381bf23bd46bc94659c4c52da6cb48 Mon Sep 17 00:00:00 2001 From: tommady Date: Mon, 27 Sep 2021 13:25:21 +0000 Subject: [PATCH 01/13] rootfs.rs add test_to_sflag --- src/rootfs.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/rootfs.rs b/src/rootfs.rs index 5e21d64be..216f506f5 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -442,6 +442,8 @@ pub fn adjust_root_mount_propagation(linux: &Linux) -> Result<()> { #[cfg(test)] mod tests { use anyhow::{Context, Result}; + use nix::sys::stat::SFlag; + use oci_spec::runtime::LinuxDeviceType; use procfs::process::MountInfo; use std::path::{Path, PathBuf}; @@ -486,4 +488,16 @@ mod tests { let res = super::find_parent_mount(Path::new("/path/to/rootfs"), &mount_infos); assert!(res.is_err()); } + + #[test] + fn test_to_sflag() { + assert_eq!( + SFlag::S_IFBLK | SFlag::S_IFCHR | SFlag::S_IFIFO, + super::to_sflag(LinuxDeviceType::A) + ); + assert_eq!(SFlag::S_IFBLK, super::to_sflag(LinuxDeviceType::B)); + assert_eq!(SFlag::S_IFCHR, super::to_sflag(LinuxDeviceType::C)); + assert_eq!(SFlag::S_IFCHR, super::to_sflag(LinuxDeviceType::U)); + assert_eq!(SFlag::S_IFIFO, super::to_sflag(LinuxDeviceType::P)); + } } From 6a04ddc39f3ffad0a48a7473b6ef7c8b90bbfff7 Mon Sep 17 00:00:00 2001 From: tommady Date: Tue, 28 Sep 2021 07:03:18 +0000 Subject: [PATCH 02/13] rootfs.rs add test_parse_mount --- src/rootfs.rs | 193 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 192 insertions(+), 1 deletion(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index 216f506f5..3134d6d34 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -442,8 +442,9 @@ pub fn adjust_root_mount_propagation(linux: &Linux) -> Result<()> { #[cfg(test)] mod tests { use anyhow::{Context, Result}; + use nix::mount::MsFlags; use nix::sys::stat::SFlag; - use oci_spec::runtime::LinuxDeviceType; + use oci_spec::runtime::{LinuxDeviceType, MountBuilder}; use procfs::process::MountInfo; use std::path::{Path, PathBuf}; @@ -500,4 +501,194 @@ mod tests { assert_eq!(SFlag::S_IFCHR, super::to_sflag(LinuxDeviceType::U)); assert_eq!(SFlag::S_IFIFO, super::to_sflag(LinuxDeviceType::P)); } + + #[test] + fn test_parse_mount() { + assert_eq!( + (MsFlags::empty(), "".to_string()), + super::parse_mount( + &MountBuilder::default() + .destination(PathBuf::from("/proc")) + .typ("proc") + .source(PathBuf::from("proc")) + .build() + .unwrap() + ) + ); + assert_eq!( + (MsFlags::MS_NOSUID, "mode=755,size=65536k".to_string()), + super::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 | MsFlags::MS_NOEXEC, + "newinstance,ptmxmode=0666,mode=0620,gid=5".to_string() + ), + super::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 | MsFlags::MS_NODEV, + "mode=1777,size=65536k".to_string() + ), + super::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, + "".to_string() + ), + super::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 | MsFlags::MS_RDONLY, + "".to_string() + ), + super::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() + ), + super::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() + ) + ); + // this case is just for coverage purpose + assert_eq!( + ( + MsFlags::MS_NOSUID + | MsFlags::MS_NODEV + | MsFlags::MS_NOEXEC + | MsFlags::MS_REMOUNT + | MsFlags::MS_DIRSYNC + | MsFlags::MS_NOATIME + | MsFlags::MS_NODIRATIME + | MsFlags::MS_BIND + | MsFlags::MS_UNBINDABLE, + "".to_string() + ), + super::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() + ) + ); + } } From 9d98c14c07cdd9565aeff752d4aba504358a66ec Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 30 Sep 2021 01:50:26 +0000 Subject: [PATCH 03/13] adding mount syscall in syscall trait for better mock testing --- src/syscall/linux.rs | 22 ++++++++++++++++++++++ src/syscall/syscall.rs | 9 +++++++++ src/syscall/test.rs | 11 +++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/syscall/linux.rs b/src/syscall/linux.rs index b5c5d33ab..201271c68 100644 --- a/src/syscall/linux.rs +++ b/src/syscall/linux.rs @@ -211,4 +211,26 @@ impl Syscall for LinuxSyscall { Ok(()) } + + fn mount( + &self, + source: Option<&str>, + target: &str, + fstype: Option<&str>, + flags: MsFlags, + data: Option<&str>, + ) -> Result<()> { + if let Err(e) = nix::mount::mount(source, target, fstype, flags, data) { + bail!( + "Failed to mount source:{:?}, target:{:?}, fstype:{:?}, flags:{:?}, data:{:?}, err:{:?}", + source, + target, + fstype, + flags, + data, + e, + ); + } + Ok(()) + } } diff --git a/src/syscall/syscall.rs b/src/syscall/syscall.rs index 15bd53fba..5d99fa060 100644 --- a/src/syscall/syscall.rs +++ b/src/syscall/syscall.rs @@ -6,6 +6,7 @@ use std::{any::Any, ffi::OsStr, path::Path, sync::Arc}; use anyhow::Result; use caps::{errors::CapsError, CapSet, CapsHashSet}; use nix::{ + mount::MsFlags, sched::CloneFlags, unistd::{Gid, Uid}, }; @@ -27,6 +28,14 @@ pub trait Syscall { fn set_hostname(&self, hostname: &str) -> Result<()>; fn set_rlimit(&self, rlimit: &LinuxRlimit) -> Result<()>; fn get_pwuid(&self, uid: u32) -> Option>; + fn mount( + &self, + source: Option<&str>, + target: &str, + fstype: Option<&str>, + flags: MsFlags, + data: Option<&str>, + ) -> Result<()>; } pub fn create_syscall() -> Box { diff --git a/src/syscall/test.rs b/src/syscall/test.rs index 6259bb70b..29c2aa283 100644 --- a/src/syscall/test.rs +++ b/src/syscall/test.rs @@ -68,6 +68,17 @@ impl Syscall for TestHelperSyscall { fn chroot(&self, _: &std::path::Path) -> anyhow::Result<()> { todo!() } + + fn mount( + &self, + _source: Option<&str>, + _target: &str, + _fstype: Option<&str>, + _flags: nix::mount::MsFlags, + _data: Option<&str>, + ) -> anyhow::Result<()> { + todo!() + } } impl TestHelperSyscall { From bf9fae3fc1120ca1e38fb5dc309d2c78a7425e0c Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 30 Sep 2021 09:03:08 +0000 Subject: [PATCH 04/13] adjust rootfs to be using self contained command of syscalls --- src/process/init.rs | 21 ++--- src/rootfs.rs | 174 ++++++++++++++++++++++++----------------- src/syscall/linux.rs | 14 +--- src/syscall/syscall.rs | 4 +- src/syscall/test.rs | 4 +- 5 files changed, 120 insertions(+), 97 deletions(-) diff --git a/src/process/init.rs b/src/process/init.rs index 0b5327a7b..7adaead05 100644 --- a/src/process/init.rs +++ b/src/process/init.rs @@ -1,8 +1,8 @@ use super::args::ContainerArgs; use crate::apparmor; use crate::{ - capabilities, hooks, namespaces::Namespaces, process::channel, rootfs, rootless::Rootless, - seccomp, tty, utils, + capabilities, hooks, namespaces::Namespaces, process::channel, rootfs::RootFS, + rootless::Rootless, seccomp, tty, utils, }; use anyhow::{bail, Context, Result}; use nix::mount::mount as nix_mount; @@ -167,7 +167,7 @@ pub fn container_init( let linux = spec.linux().as_ref().context("no linux in spec")?; let proc = spec.process().as_ref().context("no process in spec")?; let mut envs: Vec = proc.env().as_ref().unwrap_or(&vec![]).clone(); - let rootfs = &args.rootfs; + let rootfs_path = &args.rootfs; let hooks = spec.hooks().as_ref(); let container = args.container.as_ref(); let namespaces = Namespaces::from(linux.namespaces().as_ref()); @@ -215,8 +215,10 @@ pub fn container_init( .context("Failed to run create container hooks")?; } + let rootfs = RootFS::new(); let bind_service = namespaces.get(LinuxNamespaceType::User).is_some(); - rootfs::prepare_rootfs(spec, rootfs, bind_service) + rootfs + .prepare_rootfs(spec, rootfs_path, bind_service) .with_context(|| "Failed to prepare rootfs")?; // Entering into the rootfs jail. If mount namespace is specified, then @@ -226,15 +228,16 @@ pub fn container_init( if namespaces.get(LinuxNamespaceType::Mount).is_some() { // change the root of filesystem of the process to the rootfs command - .pivot_rootfs(rootfs) - .with_context(|| format!("Failed to pivot root to {:?}", rootfs))?; + .pivot_rootfs(rootfs_path) + .with_context(|| format!("Failed to pivot root to {:?}", rootfs_path))?; } else { command - .chroot(rootfs) - .with_context(|| format!("Failed to chroot to {:?}", rootfs))?; + .chroot(rootfs_path) + .with_context(|| format!("Failed to chroot to {:?}", rootfs_path))?; } - rootfs::adjust_root_mount_propagation(linux) + rootfs + .adjust_root_mount_propagation(linux) .context("Failed to set propagation type of root mount")?; if let Some(kernel_params) = &linux.sysctl() { diff --git a/src/rootfs.rs b/src/rootfs.rs index 3134d6d34..9867eb1a9 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -1,6 +1,7 @@ //! During kernel initialization, a minimal replica of the ramfs filesystem is loaded, called rootfs. //! Most systems mount another filesystem over it +use crate::syscall::{syscall::create_syscall, Syscall}; use crate::utils::PathBufExt; use anyhow::{anyhow, bail, Context, Result}; use nix::errno::Errno; @@ -19,73 +20,117 @@ use std::fs::{canonicalize, create_dir_all, remove_file}; use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; -pub fn prepare_rootfs(spec: &Spec, rootfs: &Path, bind_devices: bool) -> Result<()> { - log::debug!("Prepare rootfs: {:?}", rootfs); - let mut flags = MsFlags::MS_REC; - let linux = spec.linux().as_ref().context("no linux in spec")?; - if let Some(roofs_propagation) = linux.rootfs_propagation().as_ref() { - match roofs_propagation.as_str() { - "shared" => flags |= MsFlags::MS_SHARED, - "private" => flags |= MsFlags::MS_PRIVATE, - "slave" => flags |= MsFlags::MS_SLAVE, - "unbindable" => flags |= MsFlags::MS_SLAVE, // set unbindable after pivot_root - uknown => bail!("unknown rootfs_propagation: {}", uknown), +/// Holds information about rootfs +pub struct RootFS { + command: Box, +} + +impl RootFS { + pub fn new() -> RootFS { + RootFS { + command: create_syscall(), } - } else { - flags |= MsFlags::MS_SLAVE; } - nix_mount(None::<&str>, "/", None::<&str>, flags, None::<&str>) - .context("Failed to mount rootfs")?; + pub fn prepare_rootfs(&self, spec: &Spec, rootfs: &Path, bind_devices: bool) -> Result<()> { + log::debug!("Prepare rootfs: {:?}", rootfs); + let mut flags = MsFlags::MS_REC; + let linux = spec.linux().as_ref().context("no linux in spec")?; + if let Some(roofs_propagation) = linux.rootfs_propagation().as_ref() { + match roofs_propagation.as_str() { + "shared" => flags |= MsFlags::MS_SHARED, + "private" => flags |= MsFlags::MS_PRIVATE, + "slave" => flags |= MsFlags::MS_SLAVE, + "unbindable" => flags |= MsFlags::MS_SLAVE, // set unbindable after pivot_root + uknown => bail!("unknown rootfs_propagation: {}", uknown), + } + } else { + flags |= MsFlags::MS_SLAVE; + } - make_parent_mount_private(rootfs).context("Failed to change parent mount of rootfs private")?; + self.command + .mount( + None::<&Path>, + &Path::new("/"), + None::<&str>, + flags, + None::<&str>, + ) + .context("Failed to mount rootfs")?; - log::debug!("mount root fs {:?}", rootfs); - nix_mount::( - Some(rootfs), - rootfs, - None::<&str>, - MsFlags::MS_BIND | MsFlags::MS_REC, - None::<&str>, - )?; + make_parent_mount_private(rootfs) + .context("Failed to change parent mount of rootfs private")?; - if let Some(mounts) = spec.mounts().as_ref() { - for mount in mounts.iter() { - log::debug!("Mount... {:?}", mount); - let (flags, data) = parse_mount(mount); - let mount_label = linux.mount_label().as_ref(); - if *mount.typ() == Some("cgroup".to_string()) { - // skip - log::warn!("A feature of cgroup is unimplemented."); - } else if *mount.destination() == PathBuf::from("/dev") { - mount_to_container( - mount, - rootfs, - flags & !MsFlags::MS_RDONLY, - &data, - mount_label, - ) - .with_context(|| format!("Failed to mount /dev: {:?}", mount))?; - } else { - mount_to_container(mount, rootfs, flags, &data, mount_label) - .with_context(|| format!("Failed to mount: {:?}", mount))?; + log::debug!("mount root fs {:?}", rootfs); + self.command.mount( + Some(rootfs), + rootfs, + None::<&str>, + MsFlags::MS_BIND | MsFlags::MS_REC, + None::<&str>, + )?; + + if let Some(mounts) = spec.mounts().as_ref() { + for mount in mounts.iter() { + log::debug!("Mount... {:?}", mount); + let (flags, data) = parse_mount(mount); + let mount_label = linux.mount_label().as_ref(); + if *mount.typ() == Some("cgroup".to_string()) { + // skip + log::warn!("A feature of cgroup is unimplemented."); + } else if *mount.destination() == PathBuf::from("/dev") { + mount_to_container( + mount, + rootfs, + flags & !MsFlags::MS_RDONLY, + &data, + mount_label, + ) + .with_context(|| format!("Failed to mount /dev: {:?}", mount))?; + } else { + mount_to_container(mount, rootfs, flags, &data, mount_label) + .with_context(|| format!("Failed to mount: {:?}", mount))?; + } } } + + setup_default_symlinks(rootfs).context("Failed to setup default symlinks")?; + if let Some(added_devices) = linux.devices().as_ref() { + create_devices( + rootfs, + default_devices().iter().chain(added_devices), + bind_devices, + ) + } else { + create_devices(rootfs, default_devices().iter(), bind_devices) + }?; + + setup_ptmx(rootfs)?; + Ok(()) } - setup_default_symlinks(rootfs).context("Failed to setup default symlinks")?; - if let Some(added_devices) = linux.devices().as_ref() { - create_devices( - rootfs, - default_devices().iter().chain(added_devices), - bind_devices, - ) - } else { - create_devices(rootfs, default_devices().iter(), bind_devices) - }?; + /// Change propagation type of rootfs as specified in spec. + pub fn adjust_root_mount_propagation(&self, linux: &Linux) -> Result<()> { + let rootfs_propagation = linux.rootfs_propagation().as_deref(); + let flags = match rootfs_propagation { + Some("shared") => Some(MsFlags::MS_SHARED), + Some("unbindable") => Some(MsFlags::MS_UNBINDABLE), + _ => None, + }; - setup_ptmx(rootfs)?; - Ok(()) + if let Some(flags) = flags { + log::debug!("make root mount {:?}", flags); + self.command.mount( + None::<&Path>, + &Path::new("/"), + None::<&str>, + flags, + None::<&str>, + )?; + } + + Ok(()) + } } fn setup_ptmx(rootfs: &Path) -> Result<()> { @@ -422,23 +467,6 @@ fn make_parent_mount_private(rootfs: &Path) -> Result<()> { Ok(()) } -/// Change propagation type of rootfs as specified in spec. -pub fn adjust_root_mount_propagation(linux: &Linux) -> Result<()> { - let rootfs_propagation = linux.rootfs_propagation().as_deref(); - let flags = match rootfs_propagation { - Some("shared") => Some(MsFlags::MS_SHARED), - Some("unbindable") => Some(MsFlags::MS_UNBINDABLE), - _ => None, - }; - - if let Some(flags) = flags { - log::debug!("make root mount {:?}", flags); - nix_mount(None::<&str>, "/", None::<&str>, flags, None::<&str>)?; - } - - Ok(()) -} - #[cfg(test)] mod tests { use anyhow::{Context, Result}; diff --git a/src/syscall/linux.rs b/src/syscall/linux.rs index 201271c68..f85f823fe 100644 --- a/src/syscall/linux.rs +++ b/src/syscall/linux.rs @@ -214,22 +214,14 @@ impl Syscall for LinuxSyscall { fn mount( &self, - source: Option<&str>, - target: &str, + source: Option<&Path>, + target: &Path, fstype: Option<&str>, flags: MsFlags, data: Option<&str>, ) -> Result<()> { if let Err(e) = nix::mount::mount(source, target, fstype, flags, data) { - bail!( - "Failed to mount source:{:?}, target:{:?}, fstype:{:?}, flags:{:?}, data:{:?}, err:{:?}", - source, - target, - fstype, - flags, - data, - e, - ); + bail!("Failed to mount with flags:{:?}, err:{:?}", flags, e); } Ok(()) } diff --git a/src/syscall/syscall.rs b/src/syscall/syscall.rs index 5d99fa060..398185322 100644 --- a/src/syscall/syscall.rs +++ b/src/syscall/syscall.rs @@ -30,8 +30,8 @@ pub trait Syscall { fn get_pwuid(&self, uid: u32) -> Option>; fn mount( &self, - source: Option<&str>, - target: &str, + source: Option<&Path>, + target: &Path, fstype: Option<&str>, flags: MsFlags, data: Option<&str>, diff --git a/src/syscall/test.rs b/src/syscall/test.rs index 29c2aa283..52c8319f0 100644 --- a/src/syscall/test.rs +++ b/src/syscall/test.rs @@ -71,8 +71,8 @@ impl Syscall for TestHelperSyscall { fn mount( &self, - _source: Option<&str>, - _target: &str, + _source: Option<&std::path::Path>, + _target: &std::path::Path, _fstype: Option<&str>, _flags: nix::mount::MsFlags, _data: Option<&str>, From 86de272e4ce51a88425e4d10102ece2b219f182c Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 30 Sep 2021 09:24:30 +0000 Subject: [PATCH 05/13] fix conflics --- src/rootfs.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index 9867eb1a9..3b360adae 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -36,16 +36,12 @@ impl RootFS { log::debug!("Prepare rootfs: {:?}", rootfs); let mut flags = MsFlags::MS_REC; let linux = spec.linux().as_ref().context("no linux in spec")?; - if let Some(roofs_propagation) = linux.rootfs_propagation().as_ref() { - match roofs_propagation.as_str() { - "shared" => flags |= MsFlags::MS_SHARED, - "private" => flags |= MsFlags::MS_PRIVATE, - "slave" => flags |= MsFlags::MS_SLAVE, - "unbindable" => flags |= MsFlags::MS_SLAVE, // set unbindable after pivot_root - uknown => bail!("unknown rootfs_propagation: {}", uknown), - } - } else { - flags |= MsFlags::MS_SLAVE; + + match linux.rootfs_propagation().as_deref() { + Some("shared") => flags |= MsFlags::MS_SHARED, + Some("private") => flags |= MsFlags::MS_PRIVATE, + Some("slave" | "unbindable") | None => flags |= MsFlags::MS_SLAVE, + Some(uknown) => bail!("unknown rootfs_propagation: {}", uknown), } self.command @@ -70,8 +66,8 @@ impl RootFS { None::<&str>, )?; - if let Some(mounts) = spec.mounts().as_ref() { - for mount in mounts.iter() { + if let Some(mounts) = spec.mounts() { + for mount in mounts { log::debug!("Mount... {:?}", mount); let (flags, data) = parse_mount(mount); let mount_label = linux.mount_label().as_ref(); @@ -95,7 +91,7 @@ impl RootFS { } setup_default_symlinks(rootfs).context("Failed to setup default symlinks")?; - if let Some(added_devices) = linux.devices().as_ref() { + if let Some(added_devices) = linux.devices() { create_devices( rootfs, default_devices().iter().chain(added_devices), From 53a7484b13dc6e6a46bf0e8d7c94c177dfc31b42 Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 30 Sep 2021 10:11:57 +0000 Subject: [PATCH 06/13] fix clippy lints --- src/rootfs.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index 405fa82db..9c1c7caf1 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -25,6 +25,12 @@ pub struct RootFS { command: Box, } +impl Default for RootFS { + fn default() -> Self { + Self::new() + } +} + impl RootFS { pub fn new() -> RootFS { RootFS { @@ -47,7 +53,7 @@ impl RootFS { self.command .mount( None::<&Path>, - &Path::new("/"), + Path::new("/"), None::<&str>, flags, None::<&str>, @@ -118,7 +124,7 @@ impl RootFS { log::debug!("make root mount {:?}", flags); self.command.mount( None::<&Path>, - &Path::new("/"), + Path::new("/"), None::<&str>, flags, None::<&str>, From cb7377dfe06de0532ad3a192b1e19e1e74a4a789 Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 30 Sep 2021 12:48:18 +0000 Subject: [PATCH 07/13] move functions needed to be mocked into structure method --- src/rootfs.rs | 470 ++++++++++++++++++++++++-------------------------- 1 file changed, 229 insertions(+), 241 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index 9c1c7caf1..873d6f12d 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -51,16 +51,10 @@ impl RootFS { } self.command - .mount( - None::<&Path>, - Path::new("/"), - None::<&str>, - flags, - None::<&str>, - ) + .mount(None, Path::new("/"), None, flags, None) .context("Failed to mount rootfs")?; - make_parent_mount_private(rootfs) + self.make_parent_mount_private(rootfs) .context("Failed to change parent mount of rootfs private")?; log::debug!("mount root fs {:?}", rootfs); @@ -81,7 +75,7 @@ impl RootFS { // skip log::warn!("A feature of cgroup is unimplemented."); } else if *mount.destination() == PathBuf::from("/dev") { - mount_to_container( + self.mount_to_container( mount, rootfs, flags & !MsFlags::MS_RDONLY, @@ -90,24 +84,25 @@ impl RootFS { ) .with_context(|| format!("Failed to mount /dev: {:?}", mount))?; } else { - mount_to_container(mount, rootfs, flags, &data, mount_label) + self.mount_to_container(mount, rootfs, flags, &data, mount_label) .with_context(|| format!("Failed to mount: {:?}", mount))?; } } } - setup_default_symlinks(rootfs).context("Failed to setup default symlinks")?; + self.setup_default_symlinks(rootfs) + .context("Failed to setup default symlinks")?; if let Some(added_devices) = linux.devices() { - create_devices( + self.create_devices( rootfs, default_devices().iter().chain(added_devices), bind_devices, ) } else { - create_devices(rootfs, &default_devices(), bind_devices) + self.create_devices(rootfs, &default_devices(), bind_devices) }?; - setup_ptmx(rootfs)?; + self.setup_ptmx(rootfs)?; Ok(()) } @@ -122,46 +117,239 @@ impl RootFS { if let Some(flags) = flags { log::debug!("make root mount {:?}", flags); - self.command.mount( - None::<&Path>, - Path::new("/"), - None::<&str>, - flags, - None::<&str>, - )?; + self.command + .mount(None, Path::new("/"), None, flags, None)?; } Ok(()) } -} -fn setup_ptmx(rootfs: &Path) -> Result<()> { - if let Err(e) = remove_file(rootfs.join("dev/ptmx")) { - if e.kind() != ::std::io::ErrorKind::NotFound { - bail!("could not delete /dev/ptmx") + fn setup_ptmx(&self, rootfs: &Path) -> Result<()> { + if let Err(e) = remove_file(rootfs.join("dev/ptmx")) { + if e.kind() != ::std::io::ErrorKind::NotFound { + bail!("could not delete /dev/ptmx") + } } + + symlink("pts/ptmx", rootfs.join("dev/ptmx")).context("failed to symlink ptmx")?; + Ok(()) } - symlink("pts/ptmx", rootfs.join("dev/ptmx")).context("failed to symlink ptmx")?; - Ok(()) -} + fn setup_default_symlinks(&self, rootfs: &Path) -> Result<()> { + if Path::new("/proc/kcore").exists() { + symlink("/proc/kcore", rootfs.join("dev/kcore")).context("Failed to symlink kcore")?; + } + + let defaults = [ + ("/proc/self/fd", "dev/fd"), + ("/proc/self/fd/0", "dev/stdin"), + ("/proc/self/fd/1", "dev/stdout"), + ("/proc/self/fd/2", "dev/stderr"), + ]; + for (src, dst) in defaults { + symlink(src, rootfs.join(dst)).context("Fail to symlink defaults")?; + } + + Ok(()) + } + + fn bind_dev(&self, rootfs: &Path, dev: &LinuxDevice) -> Result<()> { + let full_container_path = rootfs.join(dev.path().as_in_container()?); + let fd = open( + &full_container_path, + OFlag::O_RDWR | OFlag::O_CREAT, + Mode::from_bits_truncate(0o644), + )?; + + close(fd)?; + + self.command.mount( + Some(dev.path()), + &full_container_path, + Some("bind"), + MsFlags::MS_BIND, + None, + )?; + + Ok(()) + } + + fn create_devices<'a, I>(&self, rootfs: &Path, devices: I, bind: bool) -> Result<()> + where + I: IntoIterator, + { + let old_mode = umask(Mode::from_bits_truncate(0o000)); + if bind { + let _ = devices + .into_iter() + .map(|dev| { + if !dev.path().starts_with("/dev") { + panic!("{} is not a valid device path", dev.path().display()); + } + + self.bind_dev(rootfs, dev) + }) + .collect::>>()?; + } else { + devices + .into_iter() + .map(|dev| { + if !dev.path().starts_with("/dev") { + panic!("{} is not a valid device path", dev.path().display()); + } + + self.mknod_dev(rootfs, dev) + }) + .collect::>>()?; + } + umask(old_mode); + + Ok(()) + } + + fn mknod_dev(&self, rootfs: &Path, dev: &LinuxDevice) -> Result<()> { + fn makedev(major: i64, minor: i64) -> u64 { + ((minor & 0xff) + | ((major & 0xfff) << 8) + | ((minor & !0xff) << 12) + | ((major & !0xfff) << 32)) as u64 + } + + let full_container_path = rootfs.join(dev.path().as_in_container()?); + mknod( + &full_container_path, + to_sflag(dev.typ()), + Mode::from_bits_truncate(dev.file_mode().unwrap_or(0)), + makedev(dev.major(), dev.minor()), + )?; + chown( + &full_container_path, + dev.uid().map(Uid::from_raw), + dev.gid().map(Gid::from_raw), + )?; + + Ok(()) + } + + fn mount_to_container( + &self, + m: &Mount, + rootfs: &Path, + flags: MsFlags, + data: &str, + label: Option<&String>, + ) -> Result<()> { + let typ = m.typ().as_deref(); + let d = if let Some(l) = label { + if typ != Some("proc") && typ != Some("sysfs") { + if data.is_empty() { + format!("context=\"{}\"", l) + } else { + format!("{},context=\"{}\"", data, l) + } + } else { + data.to_string() + } + } else { + data.to_string() + }; + let dest_for_host = format!( + "{}{}", + rootfs.to_string_lossy().into_owned(), + m.destination().display() + ); + let dest = Path::new(&dest_for_host); + let source = m.source().as_ref().context("no source in mount spec")?; + let src = if typ == Some("bind") { + let src = canonicalize(source)?; + let dir = if src.is_file() { + Path::new(&dest).parent().unwrap() + } else { + Path::new(&dest) + }; + create_dir_all(&dir) + .with_context(|| format!("Failed to create dir for bind mount: {:?}", dir))?; + if src.is_file() { + OpenOptions::new() + .create(true) + .write(true) + .open(&dest) + .unwrap(); + } + + src + } else { + create_dir_all(&dest) + .with_context(|| format!("Failed to create device: {:?}", dest))?; + PathBuf::from(source) + }; + + if let Err(errno) = nix_mount(Some(&*src), dest, typ, flags, Some(&*d)) { + if !matches!(errno, Errno::EINVAL) { + bail!("mount of {:?} failed", m.destination()); + } + nix_mount(Some(&*src), dest, typ, flags, Some(data))?; + } -fn setup_default_symlinks(rootfs: &Path) -> Result<()> { - if Path::new("/proc/kcore").exists() { - symlink("/proc/kcore", rootfs.join("dev/kcore")).context("Failed to symlink kcore")?; + if flags.contains(MsFlags::MS_BIND) + && flags.intersects( + !(MsFlags::MS_REC + | MsFlags::MS_REMOUNT + | MsFlags::MS_BIND + | MsFlags::MS_PRIVATE + | MsFlags::MS_SHARED + | MsFlags::MS_SLAVE), + ) + { + self.command + .mount(Some(dest), dest, None, flags | MsFlags::MS_REMOUNT, None)?; + } + Ok(()) } - let defaults = [ - ("/proc/self/fd", "dev/fd"), - ("/proc/self/fd/0", "dev/stdin"), - ("/proc/self/fd/1", "dev/stdout"), - ("/proc/self/fd/2", "dev/stderr"), - ]; - for (src, dst) in defaults { - symlink(src, rootfs.join(dst)).context("Fail to symlink defaults")?; + /// Make parent mount of rootfs private if it was shared, which is required by pivot_root. + /// It also makes sure following bind mount does not propagate in other namespaces. + fn make_parent_mount_private(&self, rootfs: &Path) -> Result<()> { + let mount_infos = Process::myself()?.mountinfo()?; + let parent_mount = find_parent_mount(rootfs, &mount_infos)?; + + // check parent mount has 'shared' propagation type + if parent_mount + .opt_fields + .iter() + .any(|field| matches!(field, MountOptFields::Shared(_))) + { + self.command.mount( + None, + &parent_mount.mount_point, + None, + MsFlags::MS_PRIVATE, + None, + )?; + } + + Ok(()) } +} - Ok(()) +fn to_sflag(dev_type: LinuxDeviceType) -> SFlag { + match dev_type { + LinuxDeviceType::A => SFlag::S_IFBLK | SFlag::S_IFCHR | SFlag::S_IFIFO, + LinuxDeviceType::B => SFlag::S_IFBLK, + LinuxDeviceType::C | LinuxDeviceType::U => SFlag::S_IFCHR, + LinuxDeviceType::P => SFlag::S_IFIFO, + } +} + +/// Find parent mount of rootfs in given mount infos +fn find_parent_mount<'a>(rootfs: &Path, mount_infos: &'a [MountInfo]) -> Result<&'a MountInfo> { + // find the longest mount point + let parent_mount_info = mount_infos + .iter() + .filter(|mi| rootfs.starts_with(&mi.mount_point)) + .max_by(|mi1, mi2| mi1.mount_point.len().cmp(&mi2.mount_point.len())) + .ok_or_else(|| anyhow!("couldn't find parent mount of {}", rootfs.display()))?; + Ok(parent_mount_info) } pub fn default_devices() -> Vec { @@ -217,171 +405,6 @@ pub fn default_devices() -> Vec { ] } -fn create_devices<'a, I>(rootfs: &Path, devices: I, bind: bool) -> Result<()> -where - I: IntoIterator, -{ - let old_mode = umask(Mode::from_bits_truncate(0o000)); - if bind { - let _ = devices - .into_iter() - .map(|dev| { - if !dev.path().starts_with("/dev") { - panic!("{} is not a valid device path", dev.path().display()); - } - - bind_dev(rootfs, dev) - }) - .collect::>>()?; - } else { - devices - .into_iter() - .map(|dev| { - if !dev.path().starts_with("/dev") { - panic!("{} is not a valid device path", dev.path().display()); - } - - mknod_dev(rootfs, dev) - }) - .collect::>>()?; - } - umask(old_mode); - - Ok(()) -} - -fn bind_dev(rootfs: &Path, dev: &LinuxDevice) -> Result<()> { - let full_container_path = rootfs.join(dev.path().as_in_container()?); - - let fd = open( - &full_container_path, - OFlag::O_RDWR | OFlag::O_CREAT, - Mode::from_bits_truncate(0o644), - )?; - close(fd)?; - nix_mount( - Some(dev.path()), - &full_container_path, - Some("bind"), - MsFlags::MS_BIND, - None::<&str>, - )?; - - Ok(()) -} - -fn to_sflag(dev_type: LinuxDeviceType) -> SFlag { - match dev_type { - LinuxDeviceType::A => SFlag::S_IFBLK | SFlag::S_IFCHR | SFlag::S_IFIFO, - LinuxDeviceType::B => SFlag::S_IFBLK, - LinuxDeviceType::C | LinuxDeviceType::U => SFlag::S_IFCHR, - LinuxDeviceType::P => SFlag::S_IFIFO, - } -} - -fn mknod_dev(rootfs: &Path, dev: &LinuxDevice) -> Result<()> { - fn makedev(major: i64, minor: i64) -> u64 { - ((minor & 0xff) - | ((major & 0xfff) << 8) - | ((minor & !0xff) << 12) - | ((major & !0xfff) << 32)) as u64 - } - - let full_container_path = rootfs.join(dev.path().as_in_container()?); - mknod( - &full_container_path, - to_sflag(dev.typ()), - Mode::from_bits_truncate(dev.file_mode().unwrap_or(0)), - makedev(dev.major(), dev.minor()), - )?; - chown( - &full_container_path, - dev.uid().map(Uid::from_raw), - dev.gid().map(Gid::from_raw), - )?; - - Ok(()) -} - -fn mount_to_container( - m: &Mount, - rootfs: &Path, - flags: MsFlags, - data: &str, - label: Option<&String>, -) -> Result<()> { - let typ = m.typ().as_deref(); - let d = if let Some(l) = label { - if typ != Some("proc") && typ != Some("sysfs") { - if data.is_empty() { - format!("context=\"{}\"", l) - } else { - format!("{},context=\"{}\"", data, l) - } - } else { - data.to_string() - } - } else { - data.to_string() - }; - let dest_for_host = format!( - "{}{}", - rootfs.to_string_lossy().into_owned(), - m.destination().display() - ); - let dest = Path::new(&dest_for_host); - let source = m.source().as_ref().context("no source in mount spec")?; - let src = if typ == Some("bind") { - let src = canonicalize(source)?; - let dir = if src.is_file() { - Path::new(&dest).parent().unwrap() - } else { - Path::new(&dest) - }; - create_dir_all(&dir) - .with_context(|| format!("Failed to create dir for bind mount: {:?}", dir))?; - if src.is_file() { - OpenOptions::new() - .create(true) - .write(true) - .open(&dest) - .unwrap(); - } - - src - } else { - create_dir_all(&dest).with_context(|| format!("Failed to create device: {:?}", dest))?; - PathBuf::from(source) - }; - - if let Err(errno) = nix_mount(Some(&*src), dest, typ, flags, Some(&*d)) { - if !matches!(errno, Errno::EINVAL) { - bail!("mount of {:?} failed", m.destination()); - } - nix_mount(Some(&*src), dest, typ, flags, Some(data))?; - } - - if flags.contains(MsFlags::MS_BIND) - && flags.intersects( - !(MsFlags::MS_REC - | MsFlags::MS_REMOUNT - | MsFlags::MS_BIND - | MsFlags::MS_PRIVATE - | MsFlags::MS_SHARED - | MsFlags::MS_SLAVE), - ) - { - nix_mount( - Some(dest), - dest, - None::<&str>, - flags | MsFlags::MS_REMOUNT, - None::<&str>, - )?; - } - Ok(()) -} - fn parse_mount(m: &Mount) -> (MsFlags, String) { let mut flags = MsFlags::empty(); let mut data = Vec::new(); @@ -436,41 +459,6 @@ fn parse_mount(m: &Mount) -> (MsFlags, String) { (flags, data.join(",")) } -/// Find parent mount of rootfs in given mount infos -fn find_parent_mount<'a>(rootfs: &Path, mount_infos: &'a [MountInfo]) -> Result<&'a MountInfo> { - // find the longest mount point - let parent_mount_info = mount_infos - .iter() - .filter(|mi| rootfs.starts_with(&mi.mount_point)) - .max_by(|mi1, mi2| mi1.mount_point.len().cmp(&mi2.mount_point.len())) - .ok_or_else(|| anyhow!("couldn't find parent mount of {}", rootfs.display()))?; - Ok(parent_mount_info) -} - -/// Make parent mount of rootfs private if it was shared, which is required by pivot_root. -/// It also makes sure following bind mount does not propagate in other namespaces. -fn make_parent_mount_private(rootfs: &Path) -> Result<()> { - let mount_infos = Process::myself()?.mountinfo()?; - let parent_mount = find_parent_mount(rootfs, &mount_infos)?; - - // check parent mount has 'shared' propagation type - if parent_mount - .opt_fields - .iter() - .any(|field| matches!(field, MountOptFields::Shared(_))) - { - nix_mount( - None::<&str>, - &parent_mount.mount_point, - None::<&str>, - MsFlags::MS_PRIVATE, - None::<&str>, - )?; - } - - Ok(()) -} - #[cfg(test)] mod tests { use anyhow::{Context, Result}; From 8f2bc9c78967a5ebdfb1f0f5794cc47800bd2ab3 Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 30 Sep 2021 14:07:22 +0000 Subject: [PATCH 08/13] adding test_setup_ptmx happy case --- src/rootfs.rs | 15 ++++++++++++--- src/syscall/linux.rs | 21 ++++++++++++--------- src/syscall/syscall.rs | 1 + src/syscall/test.rs | 4 ++++ 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index 873d6f12d..258864536 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -125,13 +125,16 @@ impl RootFS { } fn setup_ptmx(&self, rootfs: &Path) -> Result<()> { - if let Err(e) = remove_file(rootfs.join("dev/ptmx")) { + let ptmx = rootfs.join("dev/ptmx"); + if let Err(e) = remove_file(&ptmx) { if e.kind() != ::std::io::ErrorKind::NotFound { - bail!("could not delete /dev/ptmx") + bail!("could not delete {:?}", ptmx) } } - symlink("pts/ptmx", rootfs.join("dev/ptmx")).context("failed to symlink ptmx")?; + self.command + .symlink(Path::new("pts/ptmx"), &ptmx) + .context("failed to symlink ptmx")?; Ok(()) } @@ -711,4 +714,10 @@ mod tests { ) ); } + + #[test] + fn test_setup_ptmx() { + let rootfs = super::RootFS::new(); + assert!(rootfs.setup_ptmx(Path::new("/tmp")).is_ok()); + } } diff --git a/src/syscall/linux.rs b/src/syscall/linux.rs index f85f823fe..77471d3a1 100644 --- a/src/syscall/linux.rs +++ b/src/syscall/linux.rs @@ -1,6 +1,7 @@ //! Implements Command trait for Linux systems use std::ffi::{CStr, OsStr}; use std::os::unix::ffi::OsStrExt; +use std::os::unix::fs::symlink; use std::sync::Arc; use std::{any::Any, mem, path::Path, ptr}; @@ -9,18 +10,13 @@ use caps::{errors::CapsError, CapSet, Capability, CapsHashSet}; use libc::{c_char, uid_t}; use nix::{ errno::Errno, - unistd::{fchdir, pivot_root, sethostname}, -}; -use nix::{fcntl::open, sched::CloneFlags}; -use nix::{ - fcntl::OFlag, - unistd::{Gid, Uid}, -}; -use nix::{ + fcntl::{open, OFlag}, mount::{mount, umount2, MntFlags, MsFlags}, + sched::{unshare, CloneFlags}, + sys::stat::Mode, unistd, + unistd::{fchdir, pivot_root, sethostname, Gid, Uid}, }; -use nix::{sched::unshare, sys::stat::Mode}; use oci_spec::runtime::LinuxRlimit; @@ -225,4 +221,11 @@ impl Syscall for LinuxSyscall { } Ok(()) } + + fn symlink(&self, original: &Path, link: &Path) -> Result<()> { + match symlink(original, link) { + Ok(_) => Ok(()), + Err(e) => bail!("Failed to symlink {:?}", e), + } + } } diff --git a/src/syscall/syscall.rs b/src/syscall/syscall.rs index 398185322..f9307ad39 100644 --- a/src/syscall/syscall.rs +++ b/src/syscall/syscall.rs @@ -36,6 +36,7 @@ pub trait Syscall { flags: MsFlags, data: Option<&str>, ) -> Result<()>; + fn symlink(&self, original: &Path, link: &Path) -> Result<()>; } pub fn create_syscall() -> Box { diff --git a/src/syscall/test.rs b/src/syscall/test.rs index 52c8319f0..ce2b53ac1 100644 --- a/src/syscall/test.rs +++ b/src/syscall/test.rs @@ -79,6 +79,10 @@ impl Syscall for TestHelperSyscall { ) -> anyhow::Result<()> { todo!() } + + fn symlink(&self, _original: &std::path::Path, _link: &std::path::Path) -> anyhow::Result<()> { + Ok(()) + } } impl TestHelperSyscall { From bc68475b9f1effac19f1f288214e28e409a4c87e Mon Sep 17 00:00:00 2001 From: tommady Date: Fri, 1 Oct 2021 07:50:28 +0000 Subject: [PATCH 09/13] enhancing test_setup_ptmx --- src/rootfs.rs | 44 ++++++++++++++++++++++++++++---------------- src/syscall/test.rs | 11 +++++++++-- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index 258864536..89b46ee77 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -464,11 +464,14 @@ fn parse_mount(m: &Mount) -> (MsFlags, String) { #[cfg(test)] mod tests { + use super::*; + use crate::syscall::test::TestHelperSyscall; use anyhow::{Context, Result}; use nix::mount::MsFlags; use nix::sys::stat::SFlag; use oci_spec::runtime::{LinuxDeviceType, MountBuilder}; use procfs::process::MountInfo; + use serial_test::serial; use std::path::{Path, PathBuf}; #[test] @@ -500,7 +503,7 @@ mod tests { }, ]; - let res = super::find_parent_mount(Path::new("/path/to/rootfs"), &mount_infos) + let res = find_parent_mount(Path::new("/path/to/rootfs"), &mount_infos) .context("Failed to get parent mount")?; assert_eq!(res.mnt_id, 11); Ok(()) @@ -509,7 +512,7 @@ mod tests { #[test] fn test_find_parent_mount_with_empty_mount_infos() { let mount_infos = vec![]; - let res = super::find_parent_mount(Path::new("/path/to/rootfs"), &mount_infos); + let res = find_parent_mount(Path::new("/path/to/rootfs"), &mount_infos); assert!(res.is_err()); } @@ -517,19 +520,19 @@ mod tests { fn test_to_sflag() { assert_eq!( SFlag::S_IFBLK | SFlag::S_IFCHR | SFlag::S_IFIFO, - super::to_sflag(LinuxDeviceType::A) + to_sflag(LinuxDeviceType::A) ); - assert_eq!(SFlag::S_IFBLK, super::to_sflag(LinuxDeviceType::B)); - assert_eq!(SFlag::S_IFCHR, super::to_sflag(LinuxDeviceType::C)); - assert_eq!(SFlag::S_IFCHR, super::to_sflag(LinuxDeviceType::U)); - assert_eq!(SFlag::S_IFIFO, super::to_sflag(LinuxDeviceType::P)); + assert_eq!(SFlag::S_IFBLK, to_sflag(LinuxDeviceType::B)); + assert_eq!(SFlag::S_IFCHR, to_sflag(LinuxDeviceType::C)); + assert_eq!(SFlag::S_IFCHR, to_sflag(LinuxDeviceType::U)); + assert_eq!(SFlag::S_IFIFO, to_sflag(LinuxDeviceType::P)); } #[test] fn test_parse_mount() { assert_eq!( (MsFlags::empty(), "".to_string()), - super::parse_mount( + parse_mount( &MountBuilder::default() .destination(PathBuf::from("/proc")) .typ("proc") @@ -540,7 +543,7 @@ mod tests { ); assert_eq!( (MsFlags::MS_NOSUID, "mode=755,size=65536k".to_string()), - super::parse_mount( + parse_mount( &MountBuilder::default() .destination(PathBuf::from("/dev")) .typ("tmpfs") @@ -560,7 +563,7 @@ mod tests { MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC, "newinstance,ptmxmode=0666,mode=0620,gid=5".to_string() ), - super::parse_mount( + parse_mount( &MountBuilder::default() .destination(PathBuf::from("/dev/pts")) .typ("devpts") @@ -582,7 +585,7 @@ mod tests { MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV, "mode=1777,size=65536k".to_string() ), - super::parse_mount( + parse_mount( &MountBuilder::default() .destination(PathBuf::from("/dev/shm")) .typ("tmpfs") @@ -603,7 +606,7 @@ mod tests { MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV, "".to_string() ), - super::parse_mount( + parse_mount( &MountBuilder::default() .destination(PathBuf::from("/dev/mqueue")) .typ("mqueue") @@ -622,7 +625,7 @@ mod tests { MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV | MsFlags::MS_RDONLY, "".to_string() ), - super::parse_mount( + parse_mount( &MountBuilder::default() .destination(PathBuf::from("/sys")) .typ("sysfs") @@ -642,7 +645,7 @@ mod tests { MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV | MsFlags::MS_RDONLY, "".to_string() ), - super::parse_mount( + parse_mount( &MountBuilder::default() .destination(PathBuf::from("/sys/fs/cgroup")) .typ("cgroup") @@ -672,7 +675,7 @@ mod tests { | MsFlags::MS_UNBINDABLE, "".to_string() ), - super::parse_mount( + parse_mount( &MountBuilder::default() .options(vec![ "defaults".to_string(), @@ -716,8 +719,17 @@ mod tests { } #[test] + #[serial] fn test_setup_ptmx() { - let rootfs = super::RootFS::new(); + let rootfs = RootFS::new(); + let want = (PathBuf::from("pts/ptmx"), PathBuf::from("/tmp/dev/ptmx")); assert!(rootfs.setup_ptmx(Path::new("/tmp")).is_ok()); + let got = rootfs + .command + .as_any() + .downcast_ref::() + .unwrap() + .get_ptmx_args(); + assert_eq!(want, got) } } diff --git a/src/syscall/test.rs b/src/syscall/test.rs index ce2b53ac1..dc39700b4 100644 --- a/src/syscall/test.rs +++ b/src/syscall/test.rs @@ -1,4 +1,4 @@ -use std::{any::Any, cell::RefCell, ffi::OsStr, sync::Arc}; +use std::{any::Any, cell::RefCell, ffi::OsStr, path::PathBuf, sync::Arc}; use caps::{errors::CapsError, CapSet, CapsHashSet}; use nix::sched::CloneFlags; @@ -11,6 +11,7 @@ pub struct TestHelperSyscall { set_ns_args: RefCell>, unshare_args: RefCell>, set_capability_args: RefCell>, + ptmx_args: RefCell<(PathBuf, PathBuf)>, } impl Default for TestHelperSyscall { @@ -19,6 +20,7 @@ impl Default for TestHelperSyscall { set_ns_args: RefCell::new(vec![]), unshare_args: RefCell::new(vec![]), set_capability_args: RefCell::new(vec![]), + ptmx_args: RefCell::new((PathBuf::default(), PathBuf::default())), } } } @@ -80,7 +82,8 @@ impl Syscall for TestHelperSyscall { todo!() } - fn symlink(&self, _original: &std::path::Path, _link: &std::path::Path) -> anyhow::Result<()> { + fn symlink(&self, original: &std::path::Path, link: &std::path::Path) -> anyhow::Result<()> { + *self.ptmx_args.borrow_mut() = (original.to_path_buf(), link.to_path_buf()); Ok(()) } } @@ -97,4 +100,8 @@ impl TestHelperSyscall { pub fn get_set_capability_args(&self) -> Vec<(CapSet, CapsHashSet)> { self.set_capability_args.borrow_mut().clone() } + + pub fn get_ptmx_args(&self) -> (PathBuf, PathBuf) { + self.ptmx_args.borrow_mut().clone() + } } From fb4c3b5276298443d25023e25cca735f6b8c7574 Mon Sep 17 00:00:00 2001 From: tommady Date: Fri, 1 Oct 2021 09:59:55 +0000 Subject: [PATCH 10/13] adding test_setup_default_symlinks --- src/rootfs.rs | 50 +++++++++++++++++++++++++++++++++++++++------ src/syscall/test.rs | 12 ++++++----- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index 89b46ee77..d512132d2 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -17,7 +17,6 @@ use oci_spec::runtime::{Linux, LinuxDevice, LinuxDeviceBuilder, LinuxDeviceType, use procfs::process::{MountInfo, MountOptFields, Process}; use std::fs::OpenOptions; use std::fs::{canonicalize, create_dir_all, remove_file}; -use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; /// Holds information about rootfs @@ -90,8 +89,11 @@ impl RootFS { } } + self.setup_kcore_symlink(rootfs) + .context("Failed to setup kcore symlink")?; self.setup_default_symlinks(rootfs) .context("Failed to setup default symlinks")?; + if let Some(added_devices) = linux.devices() { self.create_devices( rootfs, @@ -138,11 +140,16 @@ impl RootFS { Ok(()) } - fn setup_default_symlinks(&self, rootfs: &Path) -> Result<()> { + fn setup_kcore_symlink(&self, rootfs: &Path) -> Result<()> { if Path::new("/proc/kcore").exists() { - symlink("/proc/kcore", rootfs.join("dev/kcore")).context("Failed to symlink kcore")?; + self.command + .symlink(Path::new("/proc/kcore"), &rootfs.join("dev/kcore")) + .context("Failed to symlink kcore")?; } + Ok(()) + } + fn setup_default_symlinks(&self, rootfs: &Path) -> Result<()> { let defaults = [ ("/proc/self/fd", "dev/fd"), ("/proc/self/fd/0", "dev/stdin"), @@ -150,7 +157,9 @@ impl RootFS { ("/proc/self/fd/2", "dev/stderr"), ]; for (src, dst) in defaults { - symlink(src, rootfs.join(dst)).context("Fail to symlink defaults")?; + self.command + .symlink(Path::new(src), &rootfs.join(dst)) + .context("Fail to symlink defaults")?; } Ok(()) @@ -722,14 +731,43 @@ mod tests { #[serial] fn test_setup_ptmx() { let rootfs = RootFS::new(); - let want = (PathBuf::from("pts/ptmx"), PathBuf::from("/tmp/dev/ptmx")); assert!(rootfs.setup_ptmx(Path::new("/tmp")).is_ok()); + let want = (PathBuf::from("pts/ptmx"), PathBuf::from("/tmp/dev/ptmx")); + let got = &rootfs + .command + .as_any() + .downcast_ref::() + .unwrap() + .get_symlink_args()[0]; + assert_eq!(want, *got) + } + + #[test] + #[serial] + fn test_setup_default_symlinks() { + let rootfs = RootFS::new(); + assert!(rootfs.setup_default_symlinks(Path::new("/tmp")).is_ok()); + let want = vec![ + (PathBuf::from("/proc/self/fd"), PathBuf::from("/tmp/dev/fd")), + ( + PathBuf::from("/proc/self/fd/0"), + PathBuf::from("/tmp/dev/stdin"), + ), + ( + PathBuf::from("/proc/self/fd/1"), + PathBuf::from("/tmp/dev/stdout"), + ), + ( + PathBuf::from("/proc/self/fd/2"), + PathBuf::from("/tmp/dev/stderr"), + ), + ]; let got = rootfs .command .as_any() .downcast_ref::() .unwrap() - .get_ptmx_args(); + .get_symlink_args(); assert_eq!(want, got) } } diff --git a/src/syscall/test.rs b/src/syscall/test.rs index dc39700b4..a8fe56df9 100644 --- a/src/syscall/test.rs +++ b/src/syscall/test.rs @@ -11,7 +11,7 @@ pub struct TestHelperSyscall { set_ns_args: RefCell>, unshare_args: RefCell>, set_capability_args: RefCell>, - ptmx_args: RefCell<(PathBuf, PathBuf)>, + symlink_args: RefCell>, } impl Default for TestHelperSyscall { @@ -20,7 +20,7 @@ impl Default for TestHelperSyscall { set_ns_args: RefCell::new(vec![]), unshare_args: RefCell::new(vec![]), set_capability_args: RefCell::new(vec![]), - ptmx_args: RefCell::new((PathBuf::default(), PathBuf::default())), + symlink_args: RefCell::new(vec![]), } } } @@ -83,7 +83,9 @@ impl Syscall for TestHelperSyscall { } fn symlink(&self, original: &std::path::Path, link: &std::path::Path) -> anyhow::Result<()> { - *self.ptmx_args.borrow_mut() = (original.to_path_buf(), link.to_path_buf()); + self.symlink_args + .borrow_mut() + .push((original.to_path_buf(), link.to_path_buf())); Ok(()) } } @@ -101,7 +103,7 @@ impl TestHelperSyscall { self.set_capability_args.borrow_mut().clone() } - pub fn get_ptmx_args(&self) -> (PathBuf, PathBuf) { - self.ptmx_args.borrow_mut().clone() + pub fn get_symlink_args(&self) -> Vec<(PathBuf, PathBuf)> { + self.symlink_args.borrow_mut().clone() } } From b8cac8b0f025b65e1f665a68fe5a7b47860f9f78 Mon Sep 17 00:00:00 2001 From: tommady Date: Fri, 1 Oct 2021 11:04:17 +0000 Subject: [PATCH 11/13] adding test_bind_dev --- src/rootfs.rs | 29 +++++++++++++++++++++++++++++ src/syscall/test.rs | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index d512132d2..f23eefe4b 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -770,4 +770,33 @@ mod tests { .get_symlink_args(); assert_eq!(want, got) } + + #[test] + #[serial] + fn test_bind_dev() { + let rootfs = RootFS::new(); + assert!(rootfs + .bind_dev( + Path::new("/tmp"), + &LinuxDeviceBuilder::default() + .path(PathBuf::from("/null")) + .build() + .unwrap(), + ) + .is_ok()); + let want = ( + Some(PathBuf::from("/null")), + PathBuf::from("/tmp/null"), + Some("bind".to_string()), + MsFlags::MS_BIND, + None::, + ); + let got = &rootfs + .command + .as_any() + .downcast_ref::() + .unwrap() + .get_mount_args()[0]; + assert_eq!(want, *got) + } } diff --git a/src/syscall/test.rs b/src/syscall/test.rs index a8fe56df9..78574ba18 100644 --- a/src/syscall/test.rs +++ b/src/syscall/test.rs @@ -11,6 +11,15 @@ pub struct TestHelperSyscall { set_ns_args: RefCell>, unshare_args: RefCell>, set_capability_args: RefCell>, + mount_args: RefCell< + Vec<( + Option, + std::path::PathBuf, + Option, + nix::mount::MsFlags, + Option, + )>, + >, symlink_args: RefCell>, } @@ -20,6 +29,7 @@ impl Default for TestHelperSyscall { set_ns_args: RefCell::new(vec![]), unshare_args: RefCell::new(vec![]), set_capability_args: RefCell::new(vec![]), + mount_args: RefCell::new(vec![]), symlink_args: RefCell::new(vec![]), } } @@ -73,13 +83,20 @@ impl Syscall for TestHelperSyscall { fn mount( &self, - _source: Option<&std::path::Path>, - _target: &std::path::Path, - _fstype: Option<&str>, - _flags: nix::mount::MsFlags, - _data: Option<&str>, + source: Option<&std::path::Path>, + target: &std::path::Path, + fstype: Option<&str>, + flags: nix::mount::MsFlags, + data: Option<&str>, ) -> anyhow::Result<()> { - todo!() + self.mount_args.borrow_mut().push(( + source.map(|x| x.to_owned()), + target.to_owned(), + fstype.map(|x| x.to_owned()), + flags, + data.map(|x| x.to_owned()), + )); + Ok(()) } fn symlink(&self, original: &std::path::Path, link: &std::path::Path) -> anyhow::Result<()> { @@ -103,6 +120,18 @@ impl TestHelperSyscall { self.set_capability_args.borrow_mut().clone() } + pub fn get_mount_args( + &self, + ) -> Vec<( + Option, + std::path::PathBuf, + Option, + nix::mount::MsFlags, + Option, + )> { + self.mount_args.borrow_mut().clone() + } + pub fn get_symlink_args(&self) -> Vec<(PathBuf, PathBuf)> { self.symlink_args.borrow_mut().clone() } From c7ff720f157db9bfbd12cd0d18ed2f7ab1885760 Mon Sep 17 00:00:00 2001 From: tommady Date: Fri, 1 Oct 2021 14:09:26 +0000 Subject: [PATCH 12/13] adding test_mknod_dev --- src/rootfs.rs | 121 +++++++++++++++++++++++++++++------------ src/syscall/linux.rs | 24 ++++++-- src/syscall/syscall.rs | 3 + src/syscall/test.rs | 117 +++++++++++++++++++++++++++------------ 4 files changed, 190 insertions(+), 75 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index f23eefe4b..722666a17 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -8,9 +8,9 @@ use nix::errno::Errno; use nix::fcntl::{open, OFlag}; use nix::mount::mount as nix_mount; use nix::mount::MsFlags; -use nix::sys::stat::{mknod, umask}; +use nix::sys::stat::umask; use nix::sys::stat::{Mode, SFlag}; -use nix::unistd::{chown, close}; +use nix::unistd::close; use nix::unistd::{Gid, Uid}; use nix::NixPath; use oci_spec::runtime::{Linux, LinuxDevice, LinuxDeviceBuilder, LinuxDeviceType, Mount, Spec}; @@ -186,6 +186,30 @@ impl RootFS { Ok(()) } + fn mknod_dev(&self, rootfs: &Path, dev: &LinuxDevice) -> Result<()> { + fn makedev(major: i64, minor: i64) -> u64 { + ((minor & 0xff) + | ((major & 0xfff) << 8) + | ((minor & !0xff) << 12) + | ((major & !0xfff) << 32)) as u64 + } + + let full_container_path = rootfs.join(dev.path().as_in_container()?); + self.command.mknod( + &full_container_path, + to_sflag(dev.typ()), + Mode::from_bits_truncate(dev.file_mode().unwrap_or(0)), + makedev(dev.major(), dev.minor()), + )?; + self.command.chown( + &full_container_path, + dev.uid().map(Uid::from_raw), + dev.gid().map(Gid::from_raw), + )?; + + Ok(()) + } + fn create_devices<'a, I>(&self, rootfs: &Path, devices: I, bind: bool) -> Result<()> where I: IntoIterator, @@ -196,7 +220,7 @@ impl RootFS { .into_iter() .map(|dev| { if !dev.path().starts_with("/dev") { - panic!("{} is not a valid device path", dev.path().display()); + bail!("{} is not a valid device path", dev.path().display()); } self.bind_dev(rootfs, dev) @@ -207,7 +231,7 @@ impl RootFS { .into_iter() .map(|dev| { if !dev.path().starts_with("/dev") { - panic!("{} is not a valid device path", dev.path().display()); + bail!("{} is not a valid device path", dev.path().display()); } self.mknod_dev(rootfs, dev) @@ -219,30 +243,6 @@ impl RootFS { Ok(()) } - fn mknod_dev(&self, rootfs: &Path, dev: &LinuxDevice) -> Result<()> { - fn makedev(major: i64, minor: i64) -> u64 { - ((minor & 0xff) - | ((major & 0xfff) << 8) - | ((minor & !0xff) << 12) - | ((major & !0xfff) << 32)) as u64 - } - - let full_container_path = rootfs.join(dev.path().as_in_container()?); - mknod( - &full_container_path, - to_sflag(dev.typ()), - Mode::from_bits_truncate(dev.file_mode().unwrap_or(0)), - makedev(dev.major(), dev.minor()), - )?; - chown( - &full_container_path, - dev.uid().map(Uid::from_raw), - dev.gid().map(Gid::from_raw), - )?; - - Ok(()) - } - fn mount_to_container( &self, m: &Mount, @@ -474,7 +474,7 @@ fn parse_mount(m: &Mount) -> (MsFlags, String) { #[cfg(test)] mod tests { use super::*; - use crate::syscall::test::TestHelperSyscall; + use crate::syscall::test::{ChownArgs, MknodArgs, MountArgs, TestHelperSyscall}; use anyhow::{Context, Result}; use nix::mount::MsFlags; use nix::sys::stat::SFlag; @@ -784,13 +784,14 @@ mod tests { .unwrap(), ) .is_ok()); - let want = ( - Some(PathBuf::from("/null")), - PathBuf::from("/tmp/null"), - Some("bind".to_string()), - MsFlags::MS_BIND, - None::, - ); + + let want = MountArgs { + source: Some(PathBuf::from("/null")), + target: PathBuf::from("/tmp/null"), + fstype: Some("bind".to_string()), + flags: MsFlags::MS_BIND, + data: None, + }; let got = &rootfs .command .as_any() @@ -799,4 +800,52 @@ mod tests { .get_mount_args()[0]; assert_eq!(want, *got) } + + #[test] + #[serial] + fn test_mknod_dev() { + let rootfs = RootFS::new(); + assert!(rootfs + .mknod_dev( + Path::new("/tmp"), + &LinuxDeviceBuilder::default() + .path(PathBuf::from("/null")) + .major(1) + .minor(3) + .typ(LinuxDeviceType::C) + .file_mode(0o644u32) + .uid(1000u32) + .gid(1000u32) + .build() + .unwrap(), + ) + .is_ok()); + + let want_mknod = MknodArgs { + path: PathBuf::from("/tmp/null"), + kind: SFlag::S_IFCHR, + perm: Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IROTH, + dev: 259, + }; + let got_mknod = &rootfs + .command + .as_any() + .downcast_ref::() + .unwrap() + .get_mknod_args()[0]; + assert_eq!(want_mknod, *got_mknod); + + let want_chown = ChownArgs { + path: PathBuf::from("/tmp/null"), + owner: Some(Uid::from_raw(1000)), + group: Some(Gid::from_raw(1000)), + }; + let got_chown = &rootfs + .command + .as_any() + .downcast_ref::() + .unwrap() + .get_chown_args()[0]; + assert_eq!(want_chown, *got_chown); + } } diff --git a/src/syscall/linux.rs b/src/syscall/linux.rs index 77471d3a1..22576d02c 100644 --- a/src/syscall/linux.rs +++ b/src/syscall/linux.rs @@ -13,9 +13,9 @@ use nix::{ fcntl::{open, OFlag}, mount::{mount, umount2, MntFlags, MsFlags}, sched::{unshare, CloneFlags}, - sys::stat::Mode, + sys::stat::{mknod, Mode, SFlag}, unistd, - unistd::{fchdir, pivot_root, sethostname, Gid, Uid}, + unistd::{chown, fchdir, pivot_root, sethostname, Gid, Uid}, }; use oci_spec::runtime::LinuxRlimit; @@ -216,10 +216,10 @@ impl Syscall for LinuxSyscall { flags: MsFlags, data: Option<&str>, ) -> Result<()> { - if let Err(e) = nix::mount::mount(source, target, fstype, flags, data) { - bail!("Failed to mount with flags:{:?}, err:{:?}", flags, e); + match mount(source, target, fstype, flags, data) { + Ok(_) => Ok(()), + Err(e) => bail!("Failed to mount {:?}", e), } - Ok(()) } fn symlink(&self, original: &Path, link: &Path) -> Result<()> { @@ -228,4 +228,18 @@ impl Syscall for LinuxSyscall { Err(e) => bail!("Failed to symlink {:?}", e), } } + + fn mknod(&self, path: &Path, kind: SFlag, perm: Mode, dev: u64) -> Result<()> { + match mknod(path, kind, perm, dev) { + Ok(_) => Ok(()), + Err(e) => bail!("Failed to mknod {:?}", e), + } + } + + fn chown(&self, path: &Path, owner: Option, group: Option) -> Result<()> { + match chown(path, owner, group) { + Ok(_) => Ok(()), + Err(e) => bail!("Failed to chown {:?}", e), + } + } } diff --git a/src/syscall/syscall.rs b/src/syscall/syscall.rs index f9307ad39..58b447ae5 100644 --- a/src/syscall/syscall.rs +++ b/src/syscall/syscall.rs @@ -8,6 +8,7 @@ use caps::{errors::CapsError, CapSet, CapsHashSet}; use nix::{ mount::MsFlags, sched::CloneFlags, + sys::stat::{Mode, SFlag}, unistd::{Gid, Uid}, }; @@ -37,6 +38,8 @@ pub trait Syscall { data: Option<&str>, ) -> Result<()>; fn symlink(&self, original: &Path, link: &Path) -> Result<()>; + fn mknod(&self, path: &Path, kind: SFlag, perm: Mode, dev: u64) -> Result<()>; + fn chown(&self, path: &Path, owner: Option, group: Option) -> Result<()>; } pub fn create_syscall() -> Box { diff --git a/src/syscall/test.rs b/src/syscall/test.rs index 78574ba18..d30a38f28 100644 --- a/src/syscall/test.rs +++ b/src/syscall/test.rs @@ -1,26 +1,55 @@ -use std::{any::Any, cell::RefCell, ffi::OsStr, path::PathBuf, sync::Arc}; - use caps::{errors::CapsError, CapSet, CapsHashSet}; -use nix::sched::CloneFlags; +use nix::{ + mount::MsFlags, + sched::CloneFlags, + sys::stat::{Mode, SFlag}, + unistd::{Gid, Uid}, +}; +use std::{ + any::Any, + cell::RefCell, + ffi::OsStr, + path::{Path, PathBuf}, + sync::Arc, +}; + use oci_spec::runtime::LinuxRlimit; use super::Syscall; +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct MountArgs { + pub source: Option, + pub target: PathBuf, + pub fstype: Option, + pub flags: MsFlags, + pub data: Option, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct MknodArgs { + pub path: PathBuf, + pub kind: SFlag, + pub perm: Mode, + pub dev: u64, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ChownArgs { + pub path: PathBuf, + pub owner: Option, + pub group: Option, +} + #[derive(Clone)] pub struct TestHelperSyscall { set_ns_args: RefCell>, unshare_args: RefCell>, set_capability_args: RefCell>, - mount_args: RefCell< - Vec<( - Option, - std::path::PathBuf, - Option, - nix::mount::MsFlags, - Option, - )>, - >, + mount_args: RefCell>, symlink_args: RefCell>, + mknod_args: RefCell>, + chown_args: RefCell>, } impl Default for TestHelperSyscall { @@ -31,6 +60,8 @@ impl Default for TestHelperSyscall { set_capability_args: RefCell::new(vec![]), mount_args: RefCell::new(vec![]), symlink_args: RefCell::new(vec![]), + mknod_args: RefCell::new(vec![]), + chown_args: RefCell::new(vec![]), } } } @@ -40,7 +71,7 @@ impl Syscall for TestHelperSyscall { self } - fn pivot_rootfs(&self, _path: &std::path::Path) -> anyhow::Result<()> { + fn pivot_rootfs(&self, _path: &Path) -> anyhow::Result<()> { unimplemented!() } @@ -50,7 +81,7 @@ impl Syscall for TestHelperSyscall { Ok(()) } - fn set_id(&self, _uid: nix::unistd::Uid, _gid: nix::unistd::Gid) -> anyhow::Result<()> { + fn set_id(&self, _uid: Uid, _gid: Gid) -> anyhow::Result<()> { unimplemented!() } @@ -77,34 +108,52 @@ impl Syscall for TestHelperSyscall { todo!() } - fn chroot(&self, _: &std::path::Path) -> anyhow::Result<()> { + fn chroot(&self, _: &Path) -> anyhow::Result<()> { todo!() } fn mount( &self, - source: Option<&std::path::Path>, - target: &std::path::Path, + source: Option<&Path>, + target: &Path, fstype: Option<&str>, - flags: nix::mount::MsFlags, + flags: MsFlags, data: Option<&str>, ) -> anyhow::Result<()> { - self.mount_args.borrow_mut().push(( - source.map(|x| x.to_owned()), - target.to_owned(), - fstype.map(|x| x.to_owned()), + self.mount_args.borrow_mut().push(MountArgs { + source: source.map(|x| x.to_owned()), + target: target.to_owned(), + fstype: fstype.map(|x| x.to_owned()), flags, - data.map(|x| x.to_owned()), - )); + data: data.map(|x| x.to_owned()), + }); Ok(()) } - fn symlink(&self, original: &std::path::Path, link: &std::path::Path) -> anyhow::Result<()> { + fn symlink(&self, original: &Path, link: &Path) -> anyhow::Result<()> { self.symlink_args .borrow_mut() .push((original.to_path_buf(), link.to_path_buf())); Ok(()) } + + fn mknod(&self, path: &Path, kind: SFlag, perm: Mode, dev: u64) -> anyhow::Result<()> { + self.mknod_args.borrow_mut().push(MknodArgs { + path: path.to_path_buf(), + kind, + perm, + dev, + }); + Ok(()) + } + fn chown(&self, path: &Path, owner: Option, group: Option) -> anyhow::Result<()> { + self.chown_args.borrow_mut().push(ChownArgs { + path: path.to_path_buf(), + owner, + group, + }); + Ok(()) + } } impl TestHelperSyscall { @@ -120,19 +169,19 @@ impl TestHelperSyscall { self.set_capability_args.borrow_mut().clone() } - pub fn get_mount_args( - &self, - ) -> Vec<( - Option, - std::path::PathBuf, - Option, - nix::mount::MsFlags, - Option, - )> { + pub fn get_mount_args(&self) -> Vec { self.mount_args.borrow_mut().clone() } pub fn get_symlink_args(&self) -> Vec<(PathBuf, PathBuf)> { self.symlink_args.borrow_mut().clone() } + + pub fn get_mknod_args(&self) -> Vec { + self.mknod_args.borrow_mut().clone() + } + + pub fn get_chown_args(&self) -> Vec { + self.chown_args.borrow_mut().clone() + } } From 7bff5135a79c59824a4d21ae5624701b78c2f09b Mon Sep 17 00:00:00 2001 From: tommady Date: Sat, 2 Oct 2021 14:56:15 +0000 Subject: [PATCH 13/13] adding test_create_devices --- src/rootfs.rs | 95 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/src/rootfs.rs b/src/rootfs.rs index 722666a17..18f581da5 100644 --- a/src/rootfs.rs +++ b/src/rootfs.rs @@ -2,7 +2,7 @@ //! Most systems mount another filesystem over it use crate::syscall::{syscall::create_syscall, Syscall}; -use crate::utils::PathBufExt; +use crate::utils::{create_dir_all_with_mode, PathBufExt}; use anyhow::{anyhow, bail, Context, Result}; use nix::errno::Errno; use nix::fcntl::{open, OFlag}; @@ -215,29 +215,29 @@ impl RootFS { I: IntoIterator, { let old_mode = umask(Mode::from_bits_truncate(0o000)); - if bind { - let _ = devices - .into_iter() - .map(|dev| { - if !dev.path().starts_with("/dev") { - bail!("{} is not a valid device path", dev.path().display()); - } + devices + .into_iter() + .map(|dev| { + if !dev.path().starts_with("/dev") { + bail!("{} is not a valid device path", dev.path().display()); + } - self.bind_dev(rootfs, dev) - }) - .collect::>>()?; - } else { - devices - .into_iter() - .map(|dev| { - if !dev.path().starts_with("/dev") { - bail!("{} is not a valid device path", dev.path().display()); - } + create_dir_all_with_mode( + rootfs + .join(dev.path().as_in_container()?) + .parent() + .unwrap_or_else(|| Path::new("")), + dev.uid().unwrap_or(0), + Mode::from_bits_truncate(0o755), + )?; + if bind { + self.bind_dev(rootfs, dev) + } else { self.mknod_dev(rootfs, dev) - }) - .collect::>>()?; - } + } + }) + .collect::>>()?; umask(old_mode); Ok(()) @@ -848,4 +848,57 @@ mod tests { .get_chown_args()[0]; assert_eq!(want_chown, *got_chown); } + + #[test] + #[serial] + fn test_create_devices() { + let rootfs = RootFS::new(); + let devices = vec![LinuxDeviceBuilder::default() + .path(PathBuf::from("/dev/null")) + .major(1) + .minor(3) + .typ(LinuxDeviceType::C) + .file_mode(0o644u32) + .uid(1000u32) + .gid(1000u32) + .build() + .unwrap()]; + + assert!(rootfs + .create_devices(Path::new("/tmp"), &devices, true) + .is_ok()); + + let want = MountArgs { + source: Some(PathBuf::from("/dev/null")), + target: PathBuf::from("/tmp/dev/null"), + fstype: Some("bind".to_string()), + flags: MsFlags::MS_BIND, + data: None, + }; + let got = &rootfs + .command + .as_any() + .downcast_ref::() + .unwrap() + .get_mount_args()[0]; + assert_eq!(want, *got); + + assert!(rootfs + .create_devices(Path::new("/tmp"), &devices, false) + .is_ok()); + + let want = MknodArgs { + path: PathBuf::from("/tmp/dev/null"), + kind: SFlag::S_IFCHR, + perm: Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IROTH, + dev: 259, + }; + let got = &rootfs + .command + .as_any() + .downcast_ref::() + .unwrap() + .get_mknod_args()[0]; + assert_eq!(want, *got); + } }