Skip to content

Commit

Permalink
feat: handle group-stop
Browse files Browse the repository at this point in the history
  • Loading branch information
kxxt committed Jan 6, 2025
1 parent 7730fca commit 4a68bfb
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 27 deletions.
14 changes: 8 additions & 6 deletions src/ptrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,22 @@ impl RecursivePtraceEngine {
&mut self,
tracee: Pid,
mut options: nix::sys::ptrace::Options,
) -> Result<PtraceStopGuard<'_>, Errno> {
) -> Result<PtraceGroupStopGuard<'_>, Errno> {
if self.running {
return Err(Errno::EEXIST);
} else {
self.running = true;
}
// In this loop, the tracee is not traced yet.
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)?;
WaitStatus::Stopped(_, _) => {
trace!("tracee stopped by other signal, continuing");
continue;
}
_ => unreachable!(), // WSTOPPED wait for children that have been stopped by delivery of a signal.
}
Expand All @@ -82,12 +83,13 @@ impl RecursivePtraceEngine {
| Options::PTRACE_O_TRACEVFORK,
)?;

ptrace::interrupt(tracee)?;
// Then we will observe a group stop resulting of the very SIGSTOP

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(guard) => Ok(guard),
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Group(guard)) => Ok(guard),
_ => unreachable!(),
}
}
Expand Down
40 changes: 38 additions & 2 deletions src/ptrace/guards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use nix::{
sys::ptrace::AddressType,
unistd::Pid,
};
use tracing::info;
use tracing::{info, trace};

use crate::arch::{Regs, RegsPayload, RegsRepr};

Expand Down Expand Up @@ -135,6 +135,32 @@ pub trait PtraceStop: private::Sealed + Sized {
nix::sys::ptrace::detach(self.pid(), None)
}

/// TODO: only allow this for PTRACE_SEIZE
fn listen(&self, ignore_esrch: bool) -> Result<(), Errno> {
let pid = self.pid();
trace!("put {pid} into listen state");
let result = unsafe {
Errno::result(nix::libc::ptrace(
nix::sys::ptrace::Request::PTRACE_LISTEN as nix::sys::ptrace::RequestType,
nix::libc::pid_t::from(pid.as_raw()),
std::ptr::null_mut::<AddressType>(),
0,
))
.map(|_| ())
};
if ignore_esrch {
match result {
Err(Errno::ESRCH) => {
info!("seccomp_aware_cont_syscall failed: {pid}, ESRCH, child gone!");
Ok(())
}
other => other,
}
} else {
result
}
}

fn pid(&self) -> Pid;

fn seccomp(&self) -> bool;
Expand Down Expand Up @@ -298,6 +324,12 @@ pub struct PtraceSeccompStopGuard<'a> {

#[derive(Debug)]
pub struct PtraceGroupStopGuard<'a> {
pub(super) signal: Signal,
pub(super) guard: PtraceOpaqueStopGuard<'a>,
}

#[derive(Debug)]
pub struct PtraceInterruptStopGuard<'a> {
pub(super) guard: PtraceOpaqueStopGuard<'a>,
}

Expand Down Expand Up @@ -335,7 +367,8 @@ impl_ptrace_stop!(
CloneParent(PtraceCloneParentStopGuard<'a>),
Exec(PtraceExecStopGuard<'a>),
Exit(PtraceExitStopGuard<'a>),
Seccomp(PtraceSeccompStopGuard<'a>)
Seccomp(PtraceSeccompStopGuard<'a>),
Interrupt(PtraceInterruptStopGuard<'a>)
);

#[derive(Debug)]
Expand All @@ -360,6 +393,7 @@ pub enum PtraceStopGuard<'a> {
Exec(PtraceExecStopGuard<'a>),
Exit(PtraceExitStopGuard<'a>),
Seccomp(PtraceSeccompStopGuard<'a>),
Interrupt(PtraceInterruptStopGuard<'a>),
}

impl private::Sealed for PtraceStopGuard<'_> {}
Expand All @@ -376,6 +410,7 @@ impl PtraceStop for PtraceStopGuard<'_> {
PtraceStopGuard::Exec(guard) => guard.pid(),
PtraceStopGuard::Exit(guard) => guard.pid(),
PtraceStopGuard::Seccomp(guard) => guard.pid(),
PtraceStopGuard::Interrupt(guard) => guard.pid(),
}
}

Expand All @@ -390,6 +425,7 @@ impl PtraceStop for PtraceStopGuard<'_> {
PtraceStopGuard::Exec(guard) => guard.seccomp(),
PtraceStopGuard::Exit(guard) => guard.seccomp(),
PtraceStopGuard::Seccomp(guard) => guard.seccomp(),
PtraceStopGuard::Interrupt(guard) => guard.seccomp(),
}
}
}
Expand Down
47 changes: 29 additions & 18 deletions src/ptrace/waitpid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ use crate::ptrace::{
};

use super::{
guards::{PtraceStopGuard, PtraceOpaqueStopGuard, PtraceSyscallStopGuard},
RecursivePtraceEngine,
guards::{PtraceOpaqueStopGuard, PtraceStopGuard, PtraceSyscallStopGuard},
PtraceInterruptStopGuard, RecursivePtraceEngine,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -171,7 +171,9 @@ impl<'a> PtraceWaitPidEvent<'a> {
// Otherwise, if we see EINVAL, this is a group-stop
Err(Errno::EINVAL) => {
// group-stop
trace!("group stop, unseized");
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Group(PtraceGroupStopGuard {
signal,
guard: PtraceOpaqueStopGuard::new(engine, pid),
}))
}
Expand Down Expand Up @@ -224,22 +226,30 @@ impl<'a> PtraceWaitPidEvent<'a> {
let sig = Signal::from_raw(WSTOPSIG(status));
match sig {
Signal::Standard(signal::SIGTRAP) => {
// Newly cloned child
trace!(
"unsure child {pid}, eventmsg: {:?}, siginfo: {:?}",
nix::sys::ptrace::getevent(pid),
nix::sys::ptrace::getsiginfo(pid)
);
// println!(
// "/proc/{pid}/status: {}",
// std::fs::read_to_string(format!("/proc/{pid}/status")).unwrap()
// );
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::CloneChild(
PtraceCloneChildStopGuard {
guard: PtraceOpaqueStopGuard::new(engine, pid),
},
))
// FIXME: could also be PTRACE_INTERRUPT
if nix::sys::ptrace::getsiginfo(pid) == Err(Errno::EINVAL) {
// PTRACE_INTERRUPT
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Interrupt(
PtraceInterruptStopGuard {
guard: PtraceOpaqueStopGuard::new(engine, pid),
},
))
} else {
// Newly cloned child
trace!(
"unsure child {pid}, eventmsg: {:?}, siginfo: {:?}",
nix::sys::ptrace::getevent(pid),
nix::sys::ptrace::getsiginfo(pid)
);
// println!(
// "/proc/{pid}/status: {}",
// std::fs::read_to_string(format!("/proc/{pid}/status")).unwrap()
// );
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::CloneChild(
PtraceCloneChildStopGuard {
guard: PtraceOpaqueStopGuard::new(engine, pid),
},
))
}
}
// Only these four signals can be group-stop
Signal::Standard(signal::SIGSTOP)
Expand All @@ -248,6 +258,7 @@ impl<'a> PtraceWaitPidEvent<'a> {
| Signal::Standard(signal::SIGTTOU) => {
// FIXME: could also be PTRACE_INTERRUPT
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Group(PtraceGroupStopGuard {
signal: sig,
guard: PtraceOpaqueStopGuard::new(engine, pid),
}))
}
Expand Down
4 changes: 3 additions & 1 deletion src/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,9 @@ impl Tracer {
}
}
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Group(guard)) => {
// TODO: switch to PTRACE_SEIZE and handle group-stop.
guard.listen(true)?;
}
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Interrupt(guard)) => {
guard.seccomp_aware_cont_syscall(true)?;
}
PtraceWaitPidEvent::Signaled { pid, signal: sig } => {
Expand Down

0 comments on commit 4a68bfb

Please sign in to comment.