From 7f3ee09eec10ef5319a7b1d9c358f50638eb1471 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Sun, 9 Jun 2019 18:54:48 -0300 Subject: [PATCH] Allow signal injection in ptrace::{syscall, detach} --- CHANGELOG.md | 3 +++ src/sys/ptrace/bsd.rs | 15 ++++++++--- src/sys/ptrace/linux.rs | 24 ++++++++++++----- test/sys/test_ptrace.rs | 60 +++++++++++++++++++++++++++++++++++++++++ test/sys/test_wait.rs | 2 +- 5 files changed, 92 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4fe67da4c..c2d89515ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)>` instead of `Result<(usize, SockAddr)>`. diff --git a/src/sys/ptrace/bsd.rs b/src/sys/ptrace/bsd.rs index 7797d10647..18265d316d 100644 --- a/src/sys/ptrace/bsd.rs +++ b/src/sys/ptrace/bsd.rs @@ -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>>(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, ...)` diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs index ffe23d7170..662425aef7 100644 --- a/src/sys/ptrace/linux.rs +++ b/src/sys/ptrace/linux.rs @@ -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>>(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( @@ -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>>(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) } } diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index b875e32389..cb2f04e9ed 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -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))); + }, + } +} diff --git a/test/sys/test_wait.rs b/test/sys/test_wait.rs index d61c2a1c78..1a189a3334 100644 --- a/test/sys/test_wait.rs +++ b/test/sys/test_wait.rs @@ -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());