summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-12-01 04:45:41 +0000
committerGitHub <noreply@github.com>2019-12-01 04:45:41 +0000
commit0ce45876eced99438f89631f63831e75012539c5 (patch)
treedc604391947282743dbac3d06679c965a0c3c73e
parentf3bf1df774a6cb89cf04dced385ec073908508b0 (diff)
parent7f3ee09eec10ef5319a7b1d9c358f50638eb1471 (diff)
downloadnix-0ce45876eced99438f89631f63831e75012539c5.zip
Merge #1083
1083: Allow signal injection in ptrace::syscall and ptrace::detach r=asomers a=frangio Fixes #1049 Should I add tests for this functionality? By the way, I noticed that the BSD module doesn't have `ptrace::syscall`. I couldn't find a reason behind this in #949. Should we add it? Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
-rw-r--r--CHANGELOG.md3
-rw-r--r--src/sys/ptrace/bsd.rs15
-rw-r--r--src/sys/ptrace/linux.rs24
-rw-r--r--test/sys/test_ptrace.rs60
-rw-r--r--test/sys/test_wait.rs2
5 files changed, 92 insertions, 12 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4fe67da..c2d89515 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<SockAddr>)>` instead of `Result<(usize, SockAddr)>`.
diff --git a/src/sys/ptrace/bsd.rs b/src/sys/ptrace/bsd.rs
index 7797d106..18265d31 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<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, ...)`
diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs
index ffe23d71..662425ae 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<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(
@@ -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)
}
}
diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs
index b875e323..cb2f04e9 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 d61c2a1c..1a189a33 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());