From 23f5761d05f65cc4d6f8d0388ea72cec30ea5ed2 Mon Sep 17 00:00:00 2001 From: Jake Hillion Date: Sun, 27 Mar 2022 16:24:07 +0100 Subject: [PATCH] building the void --- Cargo.lock | 51 ++++++++++++++++++++++ Cargo.toml | 1 + src/lib.rs | 1 + src/spawner.rs | 101 +++++++++++++++++-------------------------- src/specification.rs | 3 -- src/void.rs | 100 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 193 insertions(+), 64 deletions(-) create mode 100644 src/void.rs diff --git a/Cargo.lock b/Cargo.lock index e88edfa..ed61c66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,7 @@ dependencies = [ "nix", "serde", "serde_json", + "tempfile", "thiserror", ] @@ -255,6 +256,15 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "half" version = "1.8.2" @@ -292,6 +302,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnetwork" version = "0.18.0" @@ -485,6 +504,15 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_syscall" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.5.4" @@ -508,6 +536,15 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -602,6 +639,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.2" diff --git a/Cargo.toml b/Cargo.toml index 8d211a2..d10cffd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ libc = "0.2.117" nix = "0.23.1" close_fds = "0.3.2" +tempfile = "3.3" [dev-dependencies] criterion = "0.3" diff --git a/src/lib.rs b/src/lib.rs index 31501d1..9499203 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod clone; mod error; mod spawner; mod specification; +mod void; use error::Error; use spawner::Spawner; diff --git a/src/spawner.rs b/src/spawner.rs index aa2a24a..93f0040 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -1,8 +1,8 @@ use log::{debug, error, info}; -use super::specification::{Arg, Entrypoint, Permission, Pipe, Specification, Trigger}; +use super::specification::{Arg, Entrypoint, Pipe, Specification, Trigger}; use super::PipePair; -use crate::clone::{clone3, CloneArgs, CloneFlags}; +use crate::void::VoidBuilder; use crate::Error; use std::collections::HashMap; @@ -12,7 +12,7 @@ use std::io::Read; use std::os::unix::io::AsRawFd; use close_fds::CloseFdsBuilder; -use nix::unistd::{self, Pid}; +use nix::unistd; const BUFFER_SIZE: usize = 1024; @@ -30,34 +30,30 @@ impl<'a> Spawner<'a> { match &entrypoint.trigger { Trigger::Startup => { - if clone3(CloneArgs::new(Self::clone_flags( - &mut entrypoint.permissions.iter(), - ))) - .map_err(|e| Error::Nix { - msg: "clone3", - src: e, - })? == Pid::from_raw(0) - { + let closure = || { let args = self.prepare_args(name, &entrypoint.args, None); - unistd::execv(&CString::new(self.binary).unwrap(), &args).map_err(|e| { - Error::Nix { + if let Err(e) = unistd::execv(&CString::new(self.binary).unwrap(), &args) + .map_err(|e| Error::Nix { msg: "execv", src: e, - } - })?; - } + }) + { + error!("error: {}", e); + 1 + } else { + 0 + } + }; + + let mut builder = VoidBuilder::new(); + builder.spawn(closure)?; } Trigger::Pipe(s) => { - // take the pipe in the initiating thread so the File isn't dropped let pipe = self.pipes.get_mut(s).unwrap().take_read(); - if clone3(CloneArgs::new(CloneFlags::empty())).map_err(|e| Error::Nix { - msg: "clone3", - src: e, - })? == Pid::from_raw(0) - { + let closure = || { let mut closer = CloseFdsBuilder::new(); let keep = [pipe.as_raw_fd()]; closer.keep_fds(&keep); @@ -72,7 +68,10 @@ impl<'a> Spawner<'a> { std::process::exit(1) } } - } + }; + + let mut builder = VoidBuilder::new(); + builder.spawn(closure)?; } } } @@ -85,31 +84,32 @@ impl<'a> Spawner<'a> { loop { let read_bytes = pipe.read(&mut buf)?; - if read_bytes == 0 { return Ok(()); } debug!("triggering from pipe read"); - if clone3(CloneArgs::new(Self::clone_flags( - &mut spec.permissions.iter(), - ))) - .map_err(|e| Error::Nix { - msg: "clone3", - src: e, - })? == Pid::from_raw(0) - { - let pipe_trigger = std::str::from_utf8(&buf[0..read_bytes]).unwrap(); - let args = self.prepare_args_ref(name, &spec.args, Some(pipe_trigger)); + let closure = + || { + let pipe_trigger = std::str::from_utf8(&buf[0..read_bytes]).unwrap(); + let args = self.prepare_args_ref(name, &spec.args, Some(pipe_trigger)); - unistd::execv(&CString::new(self.binary).unwrap(), &args).map_err(|e| { - Error::Nix { - msg: "execv", - src: e, + if let Err(e) = unistd::execv(&CString::new(self.binary).unwrap(), &args) + .map_err(|e| Error::Nix { + msg: "execv", + src: e, + }) + { + error!("error: {}", e); + 1 + } else { + 0 } - })?; - } + }; + + let mut builder = VoidBuilder::new(); + builder.spawn(closure)?; } } @@ -180,25 +180,4 @@ impl<'a> Spawner<'a> { out } - - fn clone_flags(perms: &mut dyn Iterator) -> CloneFlags { - let mut flags = CloneFlags::empty(); - - flags |= CloneFlags::CLONE_NEWCGROUP; // new cgroup namespace - flags |= CloneFlags::CLONE_NEWIPC; // new IPC namespace - flags |= CloneFlags::CLONE_NEWNET; // new empty network namespace - flags |= CloneFlags::CLONE_NEWNS; // new separate mount namespace - flags |= CloneFlags::CLONE_NEWPID; // new PID namespace - flags |= CloneFlags::CLONE_NEWUSER; // new user namespace - flags |= CloneFlags::CLONE_NEWUTS; // new UTS namespace - - for perm in perms { - match perm { - Permission::PropagateFiles => flags |= CloneFlags::CLONE_FILES, - _ => unimplemented!(), - } - } - - flags - } } diff --git a/src/specification.rs b/src/specification.rs index 4e0a589..759c28d 100644 --- a/src/specification.rs +++ b/src/specification.rs @@ -20,9 +20,6 @@ pub struct Entrypoint { #[serde(default = "Arg::default_vec")] pub args: Vec, - - #[serde(default)] - pub permissions: HashSet, } #[derive(Serialize, Deserialize, Debug)] diff --git a/src/void.rs b/src/void.rs new file mode 100644 index 0000000..e70398e --- /dev/null +++ b/src/void.rs @@ -0,0 +1,100 @@ +use crate::clone::{clone3, CloneArgs, CloneFlags}; +use crate::Error; + +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; + +use nix::mount::{mount, umount, MsFlags}; +use nix::sys::signal::Signal; +use nix::unistd::{pivot_root, Pid}; + +pub struct VoidHandle {} + +pub struct VoidBuilder { + #[allow(dead_code)] + mounts: HashMap, +} + +impl VoidBuilder { + pub fn new() -> VoidBuilder { + VoidBuilder { + mounts: HashMap::new(), + } + } + + #[allow(dead_code)] + pub fn mount(&mut self, src: PathBuf, dst: PathBuf) -> &mut Self { + self.mounts.insert(src, dst); + self + } + + pub fn spawn(&mut self, child_fn: impl FnOnce() -> i32) -> Result { + let mut args = CloneArgs::new( + CloneFlags::CLONE_NEWCGROUP + | CloneFlags::CLONE_NEWIPC + | CloneFlags::CLONE_NEWNET + | CloneFlags::CLONE_NEWNS + | CloneFlags::CLONE_NEWPID + | CloneFlags::CLONE_NEWUSER + | CloneFlags::CLONE_NEWUTS, + ); + args.exit_signal = Some(Signal::SIGCHLD); + + let child = clone3(args).map_err(|e| Error::Nix { + msg: "clone3", + src: e, + })?; + + if child == Pid::from_raw(0) { + self.newns_post().unwrap(); + + std::process::exit(child_fn()) + } + + // Leak the child function's resources in the parent process. + // This avoids closing files that have been "moved" into the child. + // It is also an over-approximation, and may cause actual memory leaks. + // As the spawning process is normally short lived, this shouldn't + // be a problem. + std::mem::forget(child_fn); + + Ok(VoidHandle {}) + } + + // per-namespace void creation + fn newns_post(&self) -> Result<(), Error> { + // consume the TempDir so it doesn't get deleted + let new_root = tempfile::tempdir()?.into_path(); + + mount( + Option::<&str>::None, + &new_root, + Some("tmpfs"), + MsFlags::empty(), + Option::<&str>::None, + ) + .map_err(|e| Error::Nix { + msg: "mount", + src: e, + })?; + + // TODO: Mount mounts + + let old_root = new_root.join("old_root/"); + fs::create_dir(&old_root)?; + + pivot_root(&new_root, &old_root).map_err(|e| Error::Nix { + msg: "pivot_root", + src: e, + })?; + std::env::set_current_dir("/")?; + + umount("old_root/").map_err(|e| Error::Nix { + msg: "umount", + src: e, + })?; + + Ok(()) + } +}