Skip to content

Commit

Permalink
refactor: use PTRACE_SEIZE instead of TRACEME
Browse files Browse the repository at this point in the history
  • Loading branch information
kxxt committed Jan 6, 2025
1 parent fb810ff commit 7730fca
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 50 deletions.
58 changes: 56 additions & 2 deletions src/ptrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub struct RecursivePtraceEngine {
seccomp: bool,
_unsync_marker: PhantomUnsync,
_unsend_marker: PhantomUnsend,
running: bool,
}

/// A recursive ptracer that works on a tracee and all of its children.
Expand All @@ -41,6 +42,53 @@ impl RecursivePtraceEngine {
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<PtraceStopGuard<'_>, 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!(), // WSTOPPED wait for children that have been stopped by delivery of a signal.
}
}
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,
)?;

ptrace::interrupt(tracee)?;

let status = waitpid::waitpid(self, Some(tracee), Some(WaitPidFlag::WSTOPPED))?;
match status {
PtraceWaitPidEvent::Signaled { .. } | PtraceWaitPidEvent::Exited { .. } => Err(Errno::ESRCH),
PtraceWaitPidEvent::Ptrace(guard) => Ok(guard),
_ => unreachable!(),
}
}

Expand All @@ -49,11 +97,17 @@ impl RecursivePtraceEngine {
///
/// This function will wait until the child is in the signal delivery stop of SIGSTOP.
/// If any other signal is raised for the tracee, this function
#[allow(unused)]
pub unsafe fn import_traceme_child(
&self,
&mut self,
tracee: Pid,
mut options: nix::sys::ptrace::Options, // TODO: we shouldn't expose this.
) -> Result<PtraceSignalDeliveryStopGuard<'_>, Errno> {
if self.running {
return Err(Errno::EEXIST);
} else {
self.running = true;
}
loop {
let status = waitpid(tracee, Some(WaitPidFlag::WSTOPPED))?;
match status {
Expand Down Expand Up @@ -81,7 +135,7 @@ impl RecursivePtraceEngine {
)?;
Ok(PtraceSignalDeliveryStopGuard {
signal: nix::sys::signal::SIGSTOP.into(),
guard: PtraceStopInnerGuard::new(self, tracee),
guard: PtraceOpaqueStopGuard::new(self, tracee),
})
}

Expand Down
40 changes: 29 additions & 11 deletions src/ptrace/guards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,29 @@ pub trait PtraceStop: private::Sealed + Sized {
}

#[derive(Debug)]
pub(super) struct PtraceStopInnerGuard<'a> {
pub struct PtraceOpaqueStopGuard<'a> {
pub(super) pid: Pid,
pub(super) engine: &'a RecursivePtraceEngine,
}

impl PartialEq for PtraceStopInnerGuard<'_> {
impl private::Sealed for PtraceOpaqueStopGuard<'_> {}
impl PtraceStop for PtraceOpaqueStopGuard<'_> {
fn pid(&self) -> Pid {
self.pid
}

fn seccomp(&self) -> bool {
self.engine.seccomp
}
}

impl PartialEq for PtraceOpaqueStopGuard<'_> {
fn eq(&self, other: &Self) -> bool {
self.pid == other.pid && std::ptr::eq(self.engine, other.engine)
}
}

impl<'a> PtraceStopInnerGuard<'a> {
impl<'a> PtraceOpaqueStopGuard<'a> {
pub(super) fn new(engine: &'a RecursivePtraceEngine, pid: Pid) -> Self {
Self { pid, engine }
}
Expand All @@ -164,13 +175,13 @@ impl<'a> PtraceStopInnerGuard<'a> {

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

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

impl PtraceSignalDeliveryStopGuard<'_> {
Expand Down Expand Up @@ -252,7 +263,7 @@ impl PtraceSignalDeliveryStopGuard<'_> {
#[derive(Debug)]
pub struct PtraceCloneParentStopGuard<'a> {
pub(super) child: Result<Pid, Errno>,
pub(super) guard: PtraceStopInnerGuard<'a>,
pub(super) guard: PtraceOpaqueStopGuard<'a>,
}

impl PtraceCloneParentStopGuard<'_> {
Expand All @@ -263,31 +274,31 @@ impl PtraceCloneParentStopGuard<'_> {

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

#[derive(Debug)]
pub struct PtraceExitStopGuard<'a> {
#[allow(unused)]
pub(super) status: Result<c_int, Errno>,
pub(super) guard: PtraceStopInnerGuard<'a>,
pub(super) guard: PtraceOpaqueStopGuard<'a>,
}

#[derive(Debug)]
pub struct PtraceExecStopGuard<'a> {
#[allow(unused)]
pub(super) former_tid: Result<Pid, Errno>,
pub(super) guard: PtraceStopInnerGuard<'a>,
pub(super) guard: PtraceOpaqueStopGuard<'a>,
}

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

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

macro_rules! impl_ptrace_stop {
Expand Down Expand Up @@ -336,6 +347,13 @@ pub enum PtraceStopGuard<'a> {
/// classify this signal delivery stop into a group stop
/// or the child part of a clone stop.
SignalDelivery(PtraceSignalDeliveryStopGuard<'a>),
/// The stop that happens when a newly attached child
/// gets stopped by SIGSTOP (traceme) or by PTRACE_EVENT_STOP
/// with SIGTRAP (seize).
///
/// Note that in the latter case, false positive might be reported.
/// It is not sure whether this is a kernel bug
/// or some undocumented cases for PTRACE_EVENT_STOP.
CloneChild(PtraceCloneChildStopGuard<'a>),
Group(PtraceGroupStopGuard<'a>),
CloneParent(PtraceCloneParentStopGuard<'a>),
Expand Down
81 changes: 58 additions & 23 deletions src/ptrace/waitpid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ use std::{fmt::Display, hint::black_box};
use nix::{
errno::Errno,
libc::{self, c_int, pid_t, SIGRTMIN, WSTOPSIG},
sys::wait::WaitPidFlag,
sys::{signal, wait::WaitPidFlag},
unistd::Pid,
};
use tracing::trace;

use crate::ptrace::{
guards::{
Expand All @@ -41,13 +42,13 @@ use crate::ptrace::{
};

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

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Signal {
Standard(nix::sys::signal::Signal),
Standard(signal::Signal),
Realtime(u8), // u8 is enough for Linux
}

Expand All @@ -71,7 +72,7 @@ impl Display for Signal {

impl Signal {
pub(crate) fn from_raw(raw: c_int) -> Self {
match nix::sys::signal::Signal::try_from(raw) {
match signal::Signal::try_from(raw) {
Ok(sig) => Self::Standard(sig),
// libc might reserve some RT signals for itself.
// But from a tracer's perspective we don't need to care about it.
Expand All @@ -88,8 +89,8 @@ impl Signal {
}
}

impl From<nix::sys::signal::Signal> for Signal {
fn from(value: nix::sys::signal::Signal) -> Self {
impl From<signal::Signal> for Signal {
fn from(value: signal::Signal) -> Self {
Self::Standard(value)
}
}
Expand Down Expand Up @@ -124,7 +125,7 @@ impl<'a> PtraceWaitPidEvent<'a> {
let stopsig = libc::WSTOPSIG(status);
if stopsig == libc::SIGTRAP | 0x80 {
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Syscall(PtraceSyscallStopGuard {
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
}))
} else {
let additional = status >> 16;
Expand All @@ -136,23 +137,24 @@ impl<'a> PtraceWaitPidEvent<'a> {
let signal = Signal::from_raw(stopsig);
match signal {
// Only these four signals can be group-stop
Signal::Standard(nix::sys::signal::SIGSTOP)
| Signal::Standard(nix::sys::signal::SIGTSTP)
| Signal::Standard(nix::sys::signal::SIGTTIN)
| Signal::Standard(nix::sys::signal::SIGTTOU) => {
Signal::Standard(signal::SIGSTOP)
| Signal::Standard(signal::SIGTSTP)
| Signal::Standard(signal::SIGTTIN)
| Signal::Standard(signal::SIGTTOU) => {
// Ambiguity
let siginfo = nix::sys::ptrace::getsiginfo(pid);
match siginfo {
// First, we check special SIGSTOP
Ok(siginfo)
if signal == Signal::Standard(nix::sys::signal::SIGSTOP)
if signal == Signal::Standard(signal::SIGSTOP)
&& unsafe { siginfo.si_pid() == 0 } =>
{
// This is a PTRACE event disguised under SIGSTOP
// e.g. PTRACE_O_TRACECLONE generates this event for newly cloned process
trace!("clone child event as sigstop");
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::CloneChild(
PtraceCloneChildStopGuard {
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
},
))
}
Expand All @@ -162,23 +164,23 @@ impl<'a> PtraceWaitPidEvent<'a> {
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::SignalDelivery(
PtraceSignalDeliveryStopGuard {
signal,
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
},
))
}
// Otherwise, if we see EINVAL, this is a group-stop
Err(Errno::EINVAL) => {
// group-stop
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Group(PtraceGroupStopGuard {
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
}))
}
// The child is killed before we get to run getsiginfo (very little chance)
// In such case we just report a signal delivery stop
Err(Errno::ESRCH) => PtraceWaitPidEvent::Ptrace(PtraceStopGuard::SignalDelivery(
PtraceSignalDeliveryStopGuard {
signal,
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
},
)),
// Could this ever happen?
Expand All @@ -188,38 +190,71 @@ impl<'a> PtraceWaitPidEvent<'a> {
_ => PtraceWaitPidEvent::Ptrace(PtraceStopGuard::SignalDelivery(
PtraceSignalDeliveryStopGuard {
signal,
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
},
)),
}
} else {
// A special ptrace stop
debug_assert_eq!(WSTOPSIG(status), libc::SIGTRAP);
match additional {
libc::PTRACE_EVENT_SECCOMP => {
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Seccomp(PtraceSeccompStopGuard {
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
}))
}
libc::PTRACE_EVENT_EXEC => {
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Exec(PtraceExecStopGuard {
former_tid: nix::sys::ptrace::getevent(pid).map(|x| Pid::from_raw(x as pid_t)),
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
}))
}
libc::PTRACE_EVENT_EXIT => {
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Exit(PtraceExitStopGuard {
status: nix::sys::ptrace::getevent(pid).map(|x| x as c_int),
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
}))
}
libc::PTRACE_EVENT_CLONE | libc::PTRACE_EVENT_FORK | libc::PTRACE_EVENT_VFORK => {
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::CloneParent(PtraceCloneParentStopGuard {
child: nix::sys::ptrace::getevent(pid).map(|x| Pid::from_raw(x as pid_t)),
guard: PtraceStopInnerGuard::new(engine, pid),
guard: PtraceOpaqueStopGuard::new(engine, pid),
}))
}
_ => unimplemented!(),
libc::PTRACE_EVENT_STOP => {
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
}
// Only these four signals can be group-stop
Signal::Standard(signal::SIGSTOP)
| Signal::Standard(signal::SIGTSTP)
| Signal::Standard(signal::SIGTTIN)
| Signal::Standard(signal::SIGTTOU) => {
// FIXME: could also be PTRACE_INTERRUPT
PtraceWaitPidEvent::Ptrace(PtraceStopGuard::Group(PtraceGroupStopGuard {
guard: PtraceOpaqueStopGuard::new(engine, pid),
}))
}
_ => unimplemented!("ptrace_interrupt"),
}
}
_ => unreachable!(),
}
}
}
Expand Down
Loading

0 comments on commit 7730fca

Please sign in to comment.