1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! Execute a child process with ptrace enabled.
//!
//! # Examples
//!
//! ```rust,no_run
//! # use std::io;
//! use spawn_ptrace::CommandPtraceSpawn;
//! use std::process::Command;
//!
//! # fn foo() -> io::Result<()> {
//! let child = Command::new("/bin/ls").spawn_ptrace()?;
//! // call `ptrace(PTRACE_CONT, child.id(), ...)` to continue execution
//! // do other ptrace things here...
//! # Ok(())
//! # }
//! ```
#![cfg(unix)]

extern crate nix;

use nix::sys::ptrace::ptrace;
use nix::sys::ptrace::ptrace::PTRACE_TRACEME;
use nix::sys::signal::Signal;
use nix::sys::wait::{waitpid, WaitStatus};
use std::io::{self, Result};
use std::os::unix::process::CommandExt;
use std::process::{Command, Child};
use std::ptr;

/// A Unix-specific extension to `std::process::Command` to spawn a process with `ptrace` enabled.
pub trait CommandPtraceSpawn {
    /// Executes the command as a child process, also enabling ptrace on it.
    ///
    /// The child process will be stopped with a `SIGTRAP` calling `exec`
    /// to execute the specified command. You can continue it with
    /// `PTRACE_CONT`.
    fn spawn_ptrace(&mut self) -> Result<Child>;
}

impl CommandPtraceSpawn for Command {
    fn spawn_ptrace(&mut self) -> Result<Child> {
        let child = self.before_exec(|| {
            // Opt-in to ptrace.
            ptrace(PTRACE_TRACEME, 0, ptr::null_mut(), ptr::null_mut())?;
            Ok(())
        }).spawn()?;
        // Ensure that the child is stopped in exec before returning.
        match waitpid(child.id() as i32, None) {
            Ok(WaitStatus::Stopped(_, Signal::SIGTRAP)) => Ok(child),
            _ => Err(io::Error::new(io::ErrorKind::Other, "Child state not correct"))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use nix::sys::ptrace::ptrace;
    use nix::sys::ptrace::ptrace::PTRACE_CONT;
    use nix::sys::wait::{waitpid, WaitStatus};

    use std::env;
    use std::path::PathBuf;
    use std::process::Command;
    use std::ptr;

    fn test_process_path() -> Option<PathBuf> {
        env::current_exe()
            .ok()
            .and_then(|p| p.parent().map(|p| p.with_file_name("test")
                                         .with_extension(env::consts::EXE_EXTENSION)))
    }

    #[test]
    fn test_spawn_ptrace() {
        let path = test_process_path().expect("Failed to get test process path");
        let child = Command::new(&path)
            .spawn_ptrace()
            .expect("Error spawning test process");
        let pid = child.id() as i32;
        // Let the child continue.
        ptrace(PTRACE_CONT, pid, ptr::null_mut(), ptr::null_mut())
            .expect("Error continuing child process");
        // Wait for the child to exit.
        match waitpid(pid, None) {
            Ok(WaitStatus::Exited(_, code)) => assert_eq!(code, 0),
            Ok(s) => panic!("Unexpected stop status: {:?}", s),
            Err(e) => panic!("Unexpected waitpid error: {:?}", e),
        }
    }
}