Skip to content

Commit

Permalink
Allow signal injection in ptrace::{syscall, detach}
Browse files Browse the repository at this point in the history
  • Loading branch information
frangio committed Dec 1, 2019
1 parent f3bf1df commit 7f3ee09
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 12 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -33,6 +33,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added `sched_getaffinity`.
([#1148](https://github.com/nix-rust/nix/pull/1148))

- Added optional `Signal` argument to `ptrace::{detach, syscall}` for signal
injection. ([#1083](https://github.com/nix-rust/nix/pull/1083))

### Changed
- `sys::socket::recvfrom` now returns
`Result<(usize, Option<SockAddr>)>` instead of `Result<(usize, SockAddr)>`.
Expand Down
15 changes: 11 additions & 4 deletions src/sys/ptrace/bsd.rs
Expand Up @@ -77,16 +77,23 @@ pub fn traceme() -> Result<()> {

/// Attach to a running process, as with `ptrace(PT_ATTACH, ...)`
///
/// Attaches to the process specified in pid, making it a tracee of the calling process.
/// Attaches to the process specified by `pid`, making it a tracee of the calling process.
pub fn attach(pid: Pid) -> Result<()> {
unsafe { ptrace_other(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(drop) }
}

/// Detaches the current running process, as with `ptrace(PT_DETACH, ...)`
///
/// Detaches from the process specified in pid allowing it to run freely
pub fn detach(pid: Pid) -> Result<()> {
unsafe { ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), 0).map(drop) }
/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a
/// signal specified by `sig`.
pub fn detach<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as c_int,
None => 0,
};
unsafe {
ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), data).map(drop)
}
}

/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)`
Expand Down
24 changes: 17 additions & 7 deletions src/sys/ptrace/linux.rs
Expand Up @@ -289,21 +289,26 @@ pub fn traceme() -> Result<()> {

/// Ask for next syscall, as with `ptrace(PTRACE_SYSCALL, ...)`
///
/// Arranges for the tracee to be stopped at the next entry to or exit from a system call.
pub fn syscall(pid: Pid) -> Result<()> {
/// Arranges for the tracee to be stopped at the next entry to or exit from a system call,
/// optionally delivering a signal specified by `sig`.
pub fn syscall<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as i32 as *mut c_void,
None => ptr::null_mut(),
};
unsafe {
ptrace_other(
Request::PTRACE_SYSCALL,
pid,
ptr::null_mut(),
ptr::null_mut(),
data,
).map(drop) // ignore the useless return value
}
}

/// Attach to a running process, as with `ptrace(PTRACE_ATTACH, ...)`
///
/// Attaches to the process specified in pid, making it a tracee of the calling process.
/// Attaches to the process specified by `pid`, making it a tracee of the calling process.
pub fn attach(pid: Pid) -> Result<()> {
unsafe {
ptrace_other(
Expand Down Expand Up @@ -332,14 +337,19 @@ pub fn seize(pid: Pid, options: Options) -> Result<()> {

/// Detaches the current running process, as with `ptrace(PTRACE_DETACH, ...)`
///
/// Detaches from the process specified in pid allowing it to run freely
pub fn detach(pid: Pid) -> Result<()> {
/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a
/// signal specified by `sig`.
pub fn detach<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
let data = match sig.into() {
Some(s) => s as i32 as *mut c_void,
None => ptr::null_mut(),
};
unsafe {
ptrace_other(
Request::PTRACE_DETACH,
pid,
ptr::null_mut(),
ptr::null_mut()
data
).map(drop)
}
}
Expand Down
60 changes: 60 additions & 0 deletions test/sys/test_ptrace.rs
Expand Up @@ -112,3 +112,63 @@ fn test_ptrace_cont() {
},
}
}

// ptrace::{setoptions, getregs} are only available in these platforms
#[cfg(all(target_os = "linux",
any(target_arch = "x86_64",
target_arch = "x86"),
target_env = "gnu"))]
#[test]
fn test_ptrace_syscall() {
use nix::sys::signal::kill;
use nix::sys::ptrace;
use nix::sys::signal::Signal;
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::fork;
use nix::unistd::getpid;
use nix::unistd::ForkResult::*;

let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

match fork().expect("Error: Fork Failed") {
Child => {
ptrace::traceme().unwrap();
// first sigstop until parent is ready to continue
let pid = getpid();
kill(pid, Signal::SIGSTOP).unwrap();
kill(pid, Signal::SIGTERM).unwrap();
unsafe { ::libc::_exit(0); }
},

Parent { child } => {
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGSTOP)));

// set this option to recognize syscall-stops
ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap();

#[cfg(target_pointer_width = "64")]
let get_syscall_id = || ptrace::getregs(child).unwrap().orig_rax as i64;

#[cfg(target_pointer_width = "32")]
let get_syscall_id = || ptrace::getregs(child).unwrap().orig_eax as i32;

// kill entry
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
assert_eq!(get_syscall_id(), ::libc::SYS_kill);

// kill exit
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
assert_eq!(get_syscall_id(), ::libc::SYS_kill);

// receive signal
ptrace::syscall(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTERM)));

// inject signal
ptrace::syscall(child, Signal::SIGTERM).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false)));
},
}
}
2 changes: 1 addition & 1 deletion test/sys/test_wait.rs
Expand Up @@ -82,7 +82,7 @@ mod ptrace {
assert!(ptrace::setoptions(child, Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT).is_ok());

// First, stop on the next system call, which will be exit()
assert!(ptrace::syscall(child).is_ok());
assert!(ptrace::syscall(child, None).is_ok());
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
// Then get the ptrace event for the process exiting
assert!(ptrace::cont(child, None).is_ok());
Expand Down

0 comments on commit 7f3ee09

Please sign in to comment.