#![cfg(target_os = "linux")]
use std::ffi::CString;
use std::os::unix::io::RawFd;
pub fn run_helper(args: &[String]) -> ! {
let parsed = match parse_args(args) {
Ok(p) => p,
Err(msg) => {
eprintln!("envseal __sandbox_helper: {msg}");
std::process::exit(127);
}
};
if let Err(msg) = setup_private_mounts() {
eprintln!("envseal __sandbox_helper: mount setup failed: {msg}");
std::process::exit(127);
}
match exec_target(&parsed) {
Err(msg) => {
eprintln!("envseal __sandbox_helper: exec failed: {msg}");
std::process::exit(127);
}
}
}
struct ParsedArgs {
target_fd: RawFd,
arg0: String,
rest_argv: Vec<String>,
}
fn parse_args(args: &[String]) -> Result<ParsedArgs, String> {
let sep = args
.iter()
.position(|a| a == "--")
.ok_or_else(|| "missing `--` separator before target argv".to_string())?;
let head = &args[..sep];
let tail = &args[sep + 1..];
let mut found_target_fd: Option<RawFd> = None;
let mut found_arg0: Option<String> = None;
let mut iter = head.iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--target-fd" => {
let v = iter
.next()
.ok_or_else(|| "--target-fd requires a value".to_string())?;
found_target_fd = Some(
v.parse::<RawFd>()
.map_err(|e| format!("invalid --target-fd {v:?}: {e}"))?,
);
}
"--arg0" => {
found_arg0 = Some(
iter.next()
.ok_or_else(|| "--arg0 requires a value".to_string())?
.clone(),
);
}
other => return Err(format!("unrecognized helper flag: {other}")),
}
}
Ok(ParsedArgs {
target_fd: found_target_fd.ok_or_else(|| "--target-fd is required".to_string())?,
arg0: found_arg0.ok_or_else(|| "--arg0 is required".to_string())?,
rest_argv: tail.to_vec(),
})
}
fn setup_private_mounts() -> Result<(), String> {
let root = CString::new("/").map_err(|e| e.to_string())?;
let none = CString::new("none").map_err(|e| e.to_string())?;
let tmp = CString::new("/tmp").map_err(|e| e.to_string())?;
let tmpfs = CString::new("tmpfs").map_err(|e| e.to_string())?;
let dev_shm = CString::new("/dev/shm").map_err(|e| e.to_string())?;
let var_tmp = CString::new("/var/tmp").map_err(|e| e.to_string())?;
let opts = CString::new("size=256m,mode=1777").map_err(|e| e.to_string())?;
unsafe {
let ret = libc::mount(
none.as_ptr(),
root.as_ptr(),
std::ptr::null(),
libc::MS_REC | libc::MS_PRIVATE,
std::ptr::null(),
);
if ret != 0 {
let err = std::io::Error::last_os_error();
return Err(format!("MS_PRIVATE on /: {err}"));
}
let ret = libc::mount(
none.as_ptr(),
root.as_ptr(),
std::ptr::null(),
libc::MS_REMOUNT | libc::MS_RDONLY | libc::MS_BIND,
std::ptr::null(),
);
if ret != 0 {
let err = std::io::Error::last_os_error();
return Err(format!("MS_REMOUNT | MS_RDONLY on /: {err}"));
}
for mount_point in [&tmp, &dev_shm, &var_tmp] {
let ret = libc::mount(
tmpfs.as_ptr(),
mount_point.as_ptr(),
tmpfs.as_ptr(),
0,
opts.as_ptr().cast::<libc::c_void>(),
);
if ret != 0 {
let err = std::io::Error::last_os_error();
return Err(format!(
"mount tmpfs on {}: {err}",
mount_point.to_string_lossy()
));
}
}
}
Ok(())
}
fn exec_target(parsed: &ParsedArgs) -> Result<std::convert::Infallible, String> {
let exec_path = format!("/proc/self/fd/{}", parsed.target_fd);
let exec_path_c = CString::new(exec_path.as_bytes()).map_err(|e| e.to_string())?;
let arg0_c = CString::new(parsed.arg0.as_bytes()).map_err(|e| e.to_string())?;
let mut argv_cstrings: Vec<CString> = Vec::with_capacity(parsed.rest_argv.len() + 1);
argv_cstrings.push(arg0_c);
for a in &parsed.rest_argv {
argv_cstrings.push(CString::new(a.as_bytes()).map_err(|e| e.to_string())?);
}
let mut argv_ptrs: Vec<*const libc::c_char> =
argv_cstrings.iter().map(|c| c.as_ptr()).collect();
argv_ptrs.push(std::ptr::null());
unsafe {
libc::execv(exec_path_c.as_ptr(), argv_ptrs.as_ptr());
}
let err = std::io::Error::last_os_error();
Err(format!("execv({exec_path}): {err}"))
}