use crate::jailer::common;
use crate::runtime::advanced_options::ResourceLimits;
use crate::util::{PidFileWriter, ShimPidRecord};
use std::os::fd::RawFd;
use std::process::Command;
pub fn add_pre_exec_hook(
cmd: &mut Command,
resource_limits: ResourceLimits,
pid_writer: Option<PidFileWriter>,
preserved_fds: Vec<(RawFd, i32)>,
detach: bool,
) {
use std::os::unix::process::CommandExt;
if !detach {
cmd.process_group(0);
}
unsafe {
cmd.pre_exec(move || {
if let Some(ref writer) = pid_writer {
writer
.write_shim(&ShimPidRecord::current())
.map_err(std::io::Error::from_raw_os_error)?;
}
if !preserved_fds.is_empty() {
for &(source, target) in &preserved_fds {
if source != target {
libc::dup2(source, target);
}
}
let first_close = preserved_fds.iter().map(|(_, t)| *t).max().unwrap() + 1;
common::fd::close_fds_from(first_close)
.map_err(std::io::Error::from_raw_os_error)?;
} else {
common::fd::close_inherited_fds_raw().map_err(std::io::Error::from_raw_os_error)?;
}
common::rlimit::apply_limits_raw(&resource_limits)
.map_err(std::io::Error::from_raw_os_error)?;
if detach && libc::setsid() == -1 {
return Err(std::io::Error::last_os_error());
}
Ok(())
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_hook_compiles() {
let mut cmd = Command::new("/bin/echo");
let limits = ResourceLimits::default();
add_pre_exec_hook(&mut cmd, limits, None, vec![], false);
}
#[test]
fn test_add_hook_with_pid_file() {
let mut cmd = Command::new("/bin/echo");
let limits = ResourceLimits::default();
let writer = PidFileWriter::at(std::path::Path::new("/tmp/test.pid")).ok();
add_pre_exec_hook(&mut cmd, limits, writer, vec![], false);
}
#[test]
fn pre_exec_pid_record_declares_runtime_port_control() {
use std::os::fd::AsRawFd;
let dir = tempfile::tempdir().expect("create temp directory");
let pid_path = dir.path().join("shim.pid");
let writer = PidFileWriter::at(&pid_path).expect("create PID writer");
let keepalive = std::fs::File::open("/dev/null").expect("open preserved descriptor");
let mut cmd = Command::new("/bin/sh");
cmd.args(["-c", "true"]);
add_pre_exec_hook(
&mut cmd,
ResourceLimits::default(),
Some(writer),
vec![(keepalive.as_raw_fd(), 1023)],
false,
);
let status = cmd.status().expect("spawn child with pre-exec hook");
assert!(status.success());
let contents = std::fs::read_to_string(pid_path).expect("read shim PID record");
assert_eq!(
contents.lines().nth(2),
Some("services-mux-v1"),
"the spawn boundary must identify ServicesMux ownership before the parent can recover \
the live shim"
);
}
#[test]
fn pid_record_is_complete_before_spawn_returns() {
use std::os::fd::AsRawFd;
struct HookShape {
name: &'static str,
preserved_fds: Vec<(RawFd, i32)>,
detach: bool,
}
let keepalive = std::fs::File::open("/dev/null").expect("stand in for the watchdog pipe");
let shapes = [
HookShape {
name: "default",
preserved_fds: vec![(keepalive.as_raw_fd(), 3)],
detach: false,
},
HookShape {
name: "detached",
preserved_fds: Vec::new(),
detach: true,
},
];
for HookShape {
name: shape,
preserved_fds,
detach,
} in shapes
{
for attempt in 0..32 {
let dir = tempfile::tempdir().expect("create temp directory");
let pid_path = dir.path().join("shim.pid");
let writer = PidFileWriter::at(&pid_path).expect("create PID writer");
let mut cmd = Command::new("/bin/sh");
cmd.args(["-c", "exec sleep 5"]);
add_pre_exec_hook(
&mut cmd,
ResourceLimits::default(),
Some(writer),
preserved_fds.clone(),
detach,
);
let mut child = cmd.spawn().expect("spawn child with pre-exec hook");
let observed = crate::util::PidFileReader::at(&pid_path).read_shim();
let _ = child.kill();
let _ = child.wait();
let record = observed.unwrap_or_else(|error| {
panic!("{shape} attempt {attempt}: shim.pid unreadable when spawn() returned: {error}")
});
assert_eq!(record.identity().pid, child.id());
assert!(
record.has_runtime_port_control(),
"{shape} attempt {attempt}: record was truncated before its \
capability line, which production reads as a legacy shim"
);
}
}
}
#[test]
fn test_add_hook_with_preserved_fds() {
let mut cmd = Command::new("/bin/echo");
let limits = ResourceLimits::default();
add_pre_exec_hook(&mut cmd, limits, None, vec![(5, 3)], false);
}
}