Skip to content

Commit 88c41d1

Browse files
committed
Allow signal injection in ptrace::{syscall, detach}
1 parent 7a5248c commit 88c41d1

File tree

5 files changed

+92
-12
lines changed

5 files changed

+92
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2020
- Added `mkfifoat`
2121
([#1133](https://github.com/nix-rust/nix/pull/1133))
2222

23+
- Added optional `Signal` argument to `ptrace::{detach, syscall}` for signal
24+
injection. ([#1083](https://github.com/nix-rust/nix/pull/1083))
25+
2326
### Changed
2427
- `sys::socket::recvfrom` now returns
2528
`Result<(usize, Option<SockAddr>)>` instead of `Result<(usize, SockAddr)>`.

src/sys/ptrace/bsd.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,23 @@ pub fn traceme() -> Result<()> {
7777

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

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

9299
/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)`

src/sys/ptrace/linux.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -289,21 +289,26 @@ pub fn traceme() -> Result<()> {
289289

290290
/// Ask for next syscall, as with `ptrace(PTRACE_SYSCALL, ...)`
291291
///
292-
/// Arranges for the tracee to be stopped at the next entry to or exit from a system call.
293-
pub fn syscall(pid: Pid) -> Result<()> {
292+
/// Arranges for the tracee to be stopped at the next entry to or exit from a system call,
293+
/// optionally delivering a signal specified by `sig`.
294+
pub fn syscall<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
295+
let data = match sig.into() {
296+
Some(s) => s as i32 as *mut c_void,
297+
None => ptr::null_mut(),
298+
};
294299
unsafe {
295300
ptrace_other(
296301
Request::PTRACE_SYSCALL,
297302
pid,
298303
ptr::null_mut(),
299-
ptr::null_mut(),
304+
data,
300305
).map(drop) // ignore the useless return value
301306
}
302307
}
303308

304309
/// Attach to a running process, as with `ptrace(PTRACE_ATTACH, ...)`
305310
///
306-
/// Attaches to the process specified in pid, making it a tracee of the calling process.
311+
/// Attaches to the process specified by `pid`, making it a tracee of the calling process.
307312
pub fn attach(pid: Pid) -> Result<()> {
308313
unsafe {
309314
ptrace_other(
@@ -317,14 +322,19 @@ pub fn attach(pid: Pid) -> Result<()> {
317322

318323
/// Detaches the current running process, as with `ptrace(PTRACE_DETACH, ...)`
319324
///
320-
/// Detaches from the process specified in pid allowing it to run freely
321-
pub fn detach(pid: Pid) -> Result<()> {
325+
/// Detaches from the process specified by `pid` allowing it to run freely, optionally delivering a
326+
/// signal specified by `sig`.
327+
pub fn detach<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
328+
let data = match sig.into() {
329+
Some(s) => s as i32 as *mut c_void,
330+
None => ptr::null_mut(),
331+
};
322332
unsafe {
323333
ptrace_other(
324334
Request::PTRACE_DETACH,
325335
pid,
326336
ptr::null_mut(),
327-
ptr::null_mut()
337+
data
328338
).map(drop)
329339
}
330340
}

test/sys/test_ptrace.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,63 @@ fn test_ptrace_cont() {
112112
},
113113
}
114114
}
115+
116+
// ptrace::{setoptions, getregs} are only available in these platforms
117+
#[cfg(all(target_os = "linux",
118+
any(target_arch = "x86_64",
119+
target_arch = "x86"),
120+
target_env = "gnu"))]
121+
#[test]
122+
fn test_ptrace_syscall() {
123+
use nix::sys::signal::kill;
124+
use nix::sys::ptrace;
125+
use nix::sys::signal::Signal;
126+
use nix::sys::wait::{waitpid, WaitStatus};
127+
use nix::unistd::fork;
128+
use nix::unistd::getpid;
129+
use nix::unistd::ForkResult::*;
130+
131+
let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
132+
133+
match fork().expect("Error: Fork Failed") {
134+
Child => {
135+
ptrace::traceme().unwrap();
136+
// first sigstop until parent is ready to continue
137+
let pid = getpid();
138+
kill(pid, Signal::SIGSTOP).unwrap();
139+
kill(pid, Signal::SIGTERM).unwrap();
140+
unsafe { ::libc::_exit(0); }
141+
},
142+
143+
Parent { child } => {
144+
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGSTOP)));
145+
146+
// set this option to recognize syscall-stops
147+
ptrace::setoptions(child, ptrace::Options::PTRACE_O_TRACESYSGOOD).unwrap();
148+
149+
#[cfg(target_pointer_width = "64")]
150+
let get_syscall_id = || ptrace::getregs(child).unwrap().orig_rax as i64;
151+
152+
#[cfg(target_pointer_width = "32")]
153+
let get_syscall_id = || ptrace::getregs(child).unwrap().orig_eax as i32;
154+
155+
// kill entry
156+
ptrace::syscall(child, None).unwrap();
157+
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
158+
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
159+
160+
// kill exit
161+
ptrace::syscall(child, None).unwrap();
162+
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
163+
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
164+
165+
// receive signal
166+
ptrace::syscall(child, None).unwrap();
167+
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTERM)));
168+
169+
// inject signal
170+
ptrace::syscall(child, Signal::SIGTERM).unwrap();
171+
assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, Signal::SIGTERM, false)));
172+
},
173+
}
174+
}

test/sys/test_wait.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ mod ptrace {
8282
assert!(ptrace::setoptions(child, Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACEEXIT).is_ok());
8383

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

0 commit comments

Comments
 (0)