building the void
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing

This commit is contained in:
Jake Hillion 2022-03-27 16:24:07 +01:00
parent 87e873b1ff
commit 23f5761d05
6 changed files with 193 additions and 64 deletions

51
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -4,6 +4,7 @@ pub mod clone;
mod error;
mod spawner;
mod specification;
mod void;
use error::Error;
use spawner::Spawner;

View File

@ -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<Item = &Permission>) -> 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
}
}

View File

@ -20,9 +20,6 @@ pub struct Entrypoint {
#[serde(default = "Arg::default_vec")]
pub args: Vec<Arg>,
#[serde(default)]
pub permissions: HashSet<Permission>,
}
#[derive(Serialize, Deserialize, Debug)]

100
src/void.rs Normal file
View File

@ -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<PathBuf, PathBuf>,
}
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<VoidHandle, Error> {
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(())
}
}