use std::collections::HashMap;
use std::io::Read;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::path::Path;
use anyhow::{bail, Result};
use crate::config::NetMode;
use crate::policy::Policy;
use crate::sandbox;
pub struct ProbeOutput {
pub stdout: String,
pub stderr: String,
}
const PROBE_SCRIPT: &str = r##"for p in "$@"; do
case "$p" in
NETCHECK:*)
port=${p#NETCHECK:}
if command -v bash >/dev/null 2>&1; then
if (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then
echo "NET|reachable"
else
echo "NET|unreachable"
fi
else
echo "NET|nobash"
fi
;;
WRITECHECK:*)
target=${p#WRITECHECK:}
if dd if=/dev/null of="$target" bs=1 count=1 2>/dev/null; then
echo "WRITE|allowed"
else
echo "WRITE|denied"
fi
;;
*)
if [ -d "$p" ]; then
leak=0
for f in "$p"/* "$p"/.[!.]* "$p"/..?*; do
[ -f "$f" ] || continue
if dd if="$f" of=/dev/null bs=1 count=1 >/dev/null 2>&1; then leak=1; break; fi
done
if [ "$leak" -eq 0 ]; then
echo "D|$p|contents-denied"
else
echo "D|$p|content-readable"
fi
else
n=$(wc -c <"$p" 2>/dev/null) || { echo "F|$p|unreadable"; continue; }
echo "F|$p|$n"
fi
;;
esac
done"##;
pub fn run_probe_script(
pol: &Policy,
project: &Path,
script_args: Vec<String>,
) -> Result<ProbeOutput> {
let backend = sandbox::Backend::detect(NetMode::Off, false)?;
let mut agent_cmd = vec![
"/bin/sh".to_string(),
"-c".to_string(),
PROBE_SCRIPT.to_string(),
"vetto-probe".to_string(),
];
agent_cmd.extend(script_args);
let (out_r, out_w) = pipe2()?;
let (err_r, err_w) = pipe2()?;
let opts = sandbox::SpawnOptions {
stdio: sandbox::StdioMode::Captured {
stdout_w: out_w.as_raw_fd(),
stderr_w: err_w.as_raw_fd(),
},
agent_cmd,
cwd: project.to_path_buf(),
env_extra: HashMap::new(),
};
let sandbox::Spawned { mut handle, .. } = backend.spawn(pol, opts)?;
drop(out_w);
drop(err_w);
let mut output = String::new();
let mut out_file: std::fs::File = out_r.into();
let _ = out_file.read_to_string(&mut output);
let mut eout = String::new();
let mut err_file: std::fs::File = err_r.into();
let _ = err_file.read_to_string(&mut eout);
let _exit = handle.wait();
Ok(ProbeOutput {
stdout: output,
stderr: eout,
})
}
fn pipe2() -> Result<(OwnedFd, OwnedFd)> {
let mut fds = [0 as libc::c_int; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
bail!("pipe: {}", std::io::Error::last_os_error());
}
for fd in fds {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if flags < 0 {
let error = std::io::Error::last_os_error();
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
bail!("fcntl(F_GETFD): {error}");
}
if unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) } < 0 {
let error = std::io::Error::last_os_error();
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
bail!("fcntl(F_SETFD, FD_CLOEXEC): {error}");
}
}
Ok((unsafe { OwnedFd::from_raw_fd(fds[0]) }, unsafe {
OwnedFd::from_raw_fd(fds[1])
}))
}