fix seccomp and add tracing-mode project feature

This commit is contained in:
guochao
2023-11-01 21:53:20 +08:00
parent 51064a503b
commit 3305f70f77
11 changed files with 298 additions and 1069 deletions

View File

@ -1,38 +1,149 @@
use std::{ffi::CString, ptr::null};
use nix::{
libc::{c_long, EPERM, ORIG_RAX},
sys::signal::Signal,
unistd::{getpid, getppid},
};
use std::{
ffi::{c_void, CString},
mem::size_of,
};
use std::io::Write;
use clap::*;
use libseccomp::*;
use nix::libc::EPERM;
#[derive(clap::Parser, Debug)]
struct Args {
#[cfg(feature = "tracing-mode")]
#[clap(short, long)]
log_failed_to: Option<String>,
command: Vec<String>,
}
fn main() -> anyhow::Result<()> {
env_logger::init();
log::info!("restrict myself by set_no_new_privs...");
nix::sys::prctl::set_no_new_privs()?;
let args = Args::parse();
log::info!("create filter context...");
log::trace!("args parsed: {args:?}");
let mut filter = ScmpFilterContext::new_filter(ScmpAction::Errno(EPERM))?;
log::info!("add architecture to filter context...");
filter.add_arch(ScmpArch::X8664)?;
#[cfg(feature = "tracing-mode")]
let tracing = args.log_failed_to != None;
let mut default_action = ScmpAction::Errno(EPERM);
#[cfg(feature = "tracing-mode")]
if let Some(log_fail_to) = args.log_failed_to {
use nix::sys::{
ptrace::Options,
wait::{waitpid, WaitStatus},
};
log::trace!("tracing mode. forking into a tracer...");
let fork_result = match unsafe { nix::unistd::fork() } {
Err(err) => panic!("failed to fork a child: {}", err),
Ok(pid) => pid,
};
log::trace!("fork result: {fork_result:?}");
if let nix::unistd::ForkResult::Parent { child } = fork_result {
// i'm parent, setting up ptrace
log::trace!("waiting for child to be ready...");
waitpid(child, None)?;
let mut output = match std::fs::OpenOptions::new().append(true).write(true).create(true).open(log_fail_to) {
Ok(file) => file,
Err(err) => {
nix::sys::ptrace::kill(child)?;
return Err(err.into());
}
};
nix::sys::ptrace::setoptions(child, Options::PTRACE_O_TRACESECCOMP)?;
nix::sys::ptrace::cont(child, None)?;
log::trace!("child is ready");
loop {
log::debug!("parent: waitpid...");
match waitpid(child, None)? {
WaitStatus::Exited(pid, ret) => {
log::info!("child {pid} exited with return code {ret}");
break;
}
WaitStatus::PtraceEvent(pid, sig,_) => {
let syscall_nr = nix::sys::ptrace::read_user(
pid,
(size_of::<c_long>() * ORIG_RAX as usize) as *mut c_void,
)? as i32;
let syscall = ScmpSyscall::from(syscall_nr);
let syscall_name = syscall.get_name().unwrap_or(format!("syscall({syscall_nr})"));
log::info!("parent: child {pid} received signal {sig:?} syscall: {syscall_name}({syscall_nr})");
writeln!(output, "{} {}", pid.as_raw(), syscall_name);
}
_ => {},
}
nix::sys::ptrace::cont(child, None);
}
return Ok(());
}
}
let command: Vec<_> = args
.command
.iter()
.map(|s| CString::new(s.as_str()).unwrap())
.collect();
log::trace!("command: {command:?}");
let executable = CString::new(args.command[0].clone()).unwrap();
log::trace!("executable: {executable:?}");
let env: Vec<CString> = std::env::vars()
.into_iter()
.map(|(k, v)| format!("{k}={v}"))
.map(|s| CString::new(s).unwrap())
.collect();
#[cfg(feature = "tracing-mode")]
if tracing {
// i'm child. trace me
log::debug!("child: traceme");
nix::sys::ptrace::traceme()?;
let me = getpid();
log::info!("waiting for parent to be ready...");
nix::sys::signal::kill(me, nix::sys::signal::Signal::SIGSTOP)?;
log::info!("parent is ready");
let parent = getppid();
default_action = ScmpAction::Trace(parent.as_raw() as u16);
log::info!("set default action to {default_action:?}");
}
log::trace!("create filter context...");
let mut filter = ScmpFilterContext::new_filter(default_action)?;
filter.add_rule(ScmpAction::Allow, ScmpSyscall::from(nix::libc::SYS_execve as i32))?;
x2t_sandbox_rulegen::generate! {
log::info!("accepting {}", syscall_name);
log::trace!("accepting {}({})", syscall_name, syscall_nr);
};
log::info!("load filter into kernel...");
log::debug!("restrict myself by set_no_new_privs...");
nix::sys::prctl::set_no_new_privs()?;
log::info!("loading filter into kernel...");
if let Err(err) = filter.load() {
log::error!("failed to load filter into kernel: {err}");
return Err(err.into());
}
log::trace!("loaded");
let args: Vec<_> = std::env::args().map(|s| CString::new(s).unwrap()).collect();
let command = std::env::args().next().unwrap();
let command = CString::new(command).unwrap();
let env: Vec<CString> = Vec::new();
log::info!("executing {:?}", args);
if let Err(err) = nix::unistd::execve(&command, args.as_slice(), env.as_slice()) {
log::debug!("executing {:?}", args.command);
if let Err(err) = nix::unistd::execve(&executable, command.as_slice(), env.as_slice()) {
panic!("failed to execve for {err}");
}