use std::{cell::Cell, marker::PhantomData, sync::MutexGuard};
use nix::{
errno::Errno,
sys::{
ptrace::{self},
wait::{waitpid, WaitPidFlag, WaitStatus},
},
unistd::Pid,
};
use tracing::trace;
mod guards;
mod syscall;
mod waitpid;
pub use guards::*;
pub use waitpid::*;
pub type PhantomUnsync = PhantomData<Cell<()>>;
pub type PhantomUnsend = PhantomData<MutexGuard<'static, ()>>;
#[derive(Debug)]
pub struct RecursivePtraceEngine {
seccomp: bool,
_unsync_marker: PhantomUnsync,
_unsend_marker: PhantomUnsend,
running: bool,
}
impl RecursivePtraceEngine {
pub fn new(seccomp: bool) -> Self {
Self {
seccomp,
_unsync_marker: PhantomData,
_unsend_marker: PhantomData,
running: false,
}
}
pub fn seize_children_recursive(
&mut self,
tracee: Pid,
mut options: nix::sys::ptrace::Options,
) -> Result<PtraceGroupStopGuard<'_>, Errno> {
if self.running {
return Err(Errno::EEXIST);
} else {
self.running = true;
}
loop {
let status = waitpid(tracee, Some(WaitPidFlag::WSTOPPED))?;
match status {
WaitStatus::Stopped(_, nix::sys::signal::SIGSTOP) => {
break;
}
WaitStatus::Stopped(_, _) => {
trace!("tracee stopped by other signal, continuing");
continue;
}
_ => unreachable!(), }
}
trace!("tracee stopped, setting options");
use nix::sys::ptrace::Options;
if self.seccomp {
options |= Options::PTRACE_O_TRACESECCOMP;
}
ptrace::seize(
tracee,
options
| Options::PTRACE_O_TRACEFORK
| Options::PTRACE_O_TRACECLONE
| Options::PTRACE_O_TRACEVFORK,
)?;
let status = waitpid::waitpid(self, Some(tracee), Some(WaitPidFlag::WSTOPPED))?;
trace!("waitpid event: {status:?}");
match status {
PtraceWaitPidEvent::Signaled { .. } | PtraceWaitPidEvent::Exited { .. } => Err(Errno::ESRCH),
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Group(guard)) => Ok(guard),
_ => unreachable!(),
}
}
#[allow(unused)]
pub unsafe fn import_traceme_child(
&mut self,
tracee: Pid,
mut options: nix::sys::ptrace::Options, ) -> Result<PtraceSignalDeliveryStopGuard<'_>, Errno> {
if self.running {
return Err(Errno::EEXIST);
} else {
self.running = true;
}
loop {
let status = waitpid(tracee, Some(WaitPidFlag::WSTOPPED))?;
match status {
WaitStatus::Stopped(_, nix::sys::signal::SIGSTOP) => {
break;
}
WaitStatus::Stopped(_, signal) => {
trace!("tracee stopped by other signal, delivering it...");
ptrace::cont(tracee, signal)?;
}
_ => unreachable!(), }
}
trace!("tracee stopped, setting options");
use nix::sys::ptrace::Options;
if self.seccomp {
options |= Options::PTRACE_O_TRACESECCOMP;
}
ptrace::setoptions(
tracee,
options
| Options::PTRACE_O_TRACEFORK
| Options::PTRACE_O_TRACECLONE
| Options::PTRACE_O_TRACEVFORK,
)?;
Ok(PtraceSignalDeliveryStopGuard {
signal: nix::sys::signal::SIGSTOP.into(),
guard: PtraceOpaqueStopGuard::new(self, tracee),
})
}
pub fn next_event(
&self,
waitpid_flags: Option<WaitPidFlag>,
) -> Result<PtraceWaitPidEvent<'_>, Errno> {
let event = waitpid::waitpid(self, None, waitpid_flags);
if !matches!(event, Ok::<_, Errno>(PtraceWaitPidEvent::StillAlive)) {
trace!("waitpid event: {:?}", event);
}
event
}
}