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}