use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
use super::{WorkerKind, ENTRY};
#[derive(Debug, Clone)]
pub(crate) struct WorkerSandbox {
pub(crate) enforcement: crate::sandbox::ShellEnforcement,
pub(crate) workdir: PathBuf,
}
impl WorkerSandbox {
pub(crate) fn enters_pid_namespace(&self) -> bool {
!matches!(
self.enforcement.tier,
crate::sandbox::EnforcementTier::EnvironmentOnly
| crate::sandbox::EnforcementTier::Unconfined
) && cfg!(target_os = "linux")
}
}
#[derive(Clone)]
pub(crate) enum Launcher {
Process { program: PathBuf },
#[cfg(test)]
Thread,
#[cfg(test)]
ThreadServing(fn(WorkerKind, UnixStream)),
#[cfg(test)]
Peer(std::sync::Arc<std::sync::Mutex<std::collections::HashMap<WorkerKind, UnixStream>>>),
}
impl Launcher {
pub(crate) fn own_image() -> Self {
#[cfg(target_os = "linux")]
let program = PathBuf::from("/proc/self/exe");
#[cfg(not(target_os = "linux"))]
let program = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("mur"));
Self::Process { program }
}
}
pub(crate) fn start_worker(
launcher: &Launcher,
kind: WorkerKind,
sandbox: Option<&WorkerSandbox>,
) -> Result<(UnixStream, Option<Child>), String> {
let (ours, theirs) =
UnixStream::pair().map_err(|error| format!("no socket pair for it: {error}"))?;
match launcher {
#[cfg(test)]
Launcher::Thread => {
std::thread::Builder::new()
.name(format!("guest-worker-{}", kind.as_str()))
.spawn(move || {
super::worker::serve(kind, theirs);
})
.map_err(|error| format!("its thread did not start: {error}"))?;
Ok((ours, None))
}
#[cfg(test)]
Launcher::ThreadServing(serve) => {
let serve = *serve;
std::thread::spawn(move || serve(kind, theirs));
Ok((ours, None))
}
#[cfg(test)]
Launcher::Peer(peers) => {
peers
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(kind, theirs);
Ok((ours, None))
}
Launcher::Process { program } => {
let child = spawn_process(program, kind, theirs, sandbox)?;
Ok((ours, Some(child)))
}
}
}
#[allow(unsafe_code)]
fn spawn_process(
program: &PathBuf,
kind: WorkerKind,
theirs: UnixStream,
sandbox: Option<&WorkerSandbox>,
) -> Result<Child, String> {
use std::os::fd::AsRawFd;
use std::os::unix::process::CommandExt;
let fd = theirs.as_raw_fd();
let mut command = Command::new(program);
if let Ok(exe) = std::env::current_exe() {
command.arg0(exe);
}
command
.args([ENTRY, kind.as_str()])
.env_clear()
.current_dir("/")
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
#[cfg(target_os = "linux")]
let parent = unsafe { libc::getpid() };
unsafe {
command.pre_exec(move || {
if libc::setpgid(0, 0) != 0 {
return Err(std::io::Error::last_os_error());
}
#[cfg(target_os = "linux")]
{
if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL, 0, 0, 0) != 0 {
return Err(std::io::Error::last_os_error());
}
if libc::getppid() != parent {
return Err(std::io::Error::from_raw_os_error(libc::ESRCH));
}
}
Ok(())
});
}
let handle = match sandbox {
Some(sandbox) => Some(crate::sandbox::prepare_enforcement(
&mut command,
&sandbox.enforcement,
&sandbox.workdir,
)?),
None => None,
};
unsafe {
command.pre_exec(move || {
if fd == 3 {
let flags = libc::fcntl(3, libc::F_GETFD);
if flags < 0 || libc::fcntl(3, libc::F_SETFD, flags & !libc::FD_CLOEXEC) != 0 {
return Err(std::io::Error::last_os_error());
}
} else if libc::dup2(fd, 3) < 0 {
return Err(std::io::Error::last_os_error());
}
close_above_three();
Ok(())
});
}
let spawned = command.spawn();
drop(command);
let child = spawned.map_err(|error| {
let detail = handle
.as_ref()
.and_then(crate::sandbox::SupervisorHandle::read_diagnostic)
.map(|detail| format!(" ({detail})"))
.unwrap_or_default();
format!("{} did not start: {error}{detail}", program.display())
})?;
drop(theirs);
Ok(child)
}
#[allow(unsafe_code)]
unsafe fn close_above_three() {
#[cfg(target_os = "linux")]
unsafe {
const CLOSE_RANGE_CLOEXEC: libc::c_uint = 1 << 2;
if libc::syscall(
libc::SYS_close_range,
4 as libc::c_uint,
libc::c_uint::MAX,
CLOSE_RANGE_CLOEXEC,
) == 0
{
return;
}
crate::cgroup::close_range(4, libc::c_uint::MAX);
}
#[cfg(not(target_os = "linux"))]
unsafe {
let mut fd = 4;
while fd < 4096 {
let flags = libc::fcntl(fd, libc::F_GETFD);
if flags >= 0 {
libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC);
}
fd += 1;
}
}
}