spawn_ptrace/
lib.rs

1//! This crate allows you to spawn a child process with [`ptrace`] enabled.
2//! It provides a single trait—[`CommandPtraceSpawn`]—that is implemented for
3//! `std::process::Command`, giving you access to a [`spawn_ptrace`] method.
4//!
5//! Processes spawned this way will be stopped with `SIGTRAP` from their
6//! `exec`, so you can perform any early intervention you require prior to the
7//! process running any code and then use `PTRACE_CONT` to resume its execution.
8//!
9//! # Examples
10//!
11//! ```rust,no_run
12//! # use std::io;
13//! use spawn_ptrace::CommandPtraceSpawn;
14//! use std::process::Command;
15//!
16//! # fn foo() -> io::Result<()> {
17//! let child = Command::new("/bin/ls").spawn_ptrace()?;
18//! // call `ptrace(PTRACE_CONT, child.id(), ...)` to continue execution
19//! // do other ptrace things here...
20//! # Ok(())
21//! # }
22//! ```
23//!
24//! [`ptrace`]: https://man7.org/linux/man-pages/man2/ptrace.2.html
25//! [`CommandPtraceSpawn`]: trait.CommandPtraceSpawn.html
26//! [`spawn_ptrace`]: trait.CommandPtraceSpawn.html#tymethod.spawn_ptrace
27#![cfg(unix)]
28
29#[cfg(doctest)]
30doc_comment::doctest!("../README.md");
31
32use nix::sys::ptrace;
33use nix::sys::signal::Signal;
34use nix::sys::wait::{waitpid, WaitStatus};
35use nix::unistd::Pid;
36use std::io::{self, Result};
37use std::os::unix::process::CommandExt;
38use std::process::{Child, Command};
39
40/// A Unix-specific extension to `std::process::Command` to spawn a process with `ptrace` enabled.
41///
42/// See [the crate-level documentation](index.html) for an example.
43pub trait CommandPtraceSpawn {
44    /// Executes the command as a child process, also enabling ptrace on it.
45    ///
46    /// The child process will be stopped with a `SIGTRAP` calling `exec`
47    /// to execute the specified command. You can continue it with
48    /// `PTRACE_CONT`.
49    fn spawn_ptrace(&mut self) -> Result<Child>;
50}
51
52impl CommandPtraceSpawn for Command {
53    fn spawn_ptrace(&mut self) -> Result<Child> {
54        let child = unsafe {
55            self.pre_exec(|| {
56                // Opt-in to ptrace.
57                ptrace::traceme().map_err(|e| match e {
58                    nix::Error::Sys(e) => io::Error::from_raw_os_error(e as i32),
59                    _ => io::Error::new(io::ErrorKind::Other, "unknown PTRACE_TRACEME error"),
60                })
61            })
62            .spawn()?
63        };
64        // Ensure that the child is stopped in exec before returning.
65        match waitpid(Some(Pid::from_raw(child.id() as i32)), None) {
66            Ok(WaitStatus::Stopped(_, Signal::SIGTRAP)) => Ok(child),
67            _ => Err(io::Error::new(
68                io::ErrorKind::Other,
69                "Child state not correct",
70            )),
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    use std::env;
80    use std::path::PathBuf;
81
82    fn test_process_path() -> Option<PathBuf> {
83        env::current_exe().ok().and_then(|p| {
84            p.parent().map(|p| {
85                p.with_file_name("test")
86                    .with_extension(env::consts::EXE_EXTENSION)
87            })
88        })
89    }
90
91    #[test]
92    fn test_spawn_ptrace() {
93        let path = test_process_path().expect("Failed to get test process path");
94        let child = Command::new(&path)
95            .spawn_ptrace()
96            .expect("Error spawning test process");
97        let pid = Pid::from_raw(child.id() as i32);
98        // Let the child continue.
99        ptrace::cont(pid, None).expect("Error continuing child process");
100        // Wait for the child to exit.
101        match waitpid(pid, None) {
102            Ok(WaitStatus::Exited(_, code)) => assert_eq!(code, 0),
103            Ok(s) => panic!("Unexpected stop status: {:?}", s),
104            Err(e) => panic!("Unexpected waitpid error: {:?}", e),
105        }
106    }
107}