Skip to content

Commit 9ed9a1d

Browse files
bors[bot]xd009642
andcommitted
Merge #949
949: ptrace support for BSDs r=Susurrus a=xd009642 This PR adds support to the ptrace API for BSDs to close #947. It also adds a read and write method for reading and writing to a traced processes memory. The ptrace API created for linux offers this via a deprecated function so I added this so they can be feature equivalent without replicating a deprecated part of the API. Due to the differences in ptrace on BSD and linux I've made a ptrace module to keep things readable. Still to do - revert travis config to remove my feature branch and update the changelog. Co-authored-by: xd009642 <[email protected]>
2 parents eef3a43 + f1573a7 commit 9ed9a1d

File tree

7 files changed

+257
-9
lines changed

7 files changed

+257
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2525
([#956](https://github.com/nix-rust/nix/pull/956))
2626
- Added a `fchownat` wrapper.
2727
([#955](https://github.com/nix-rust/nix/pull/955))
28+
- Added support for `ptrace` on BSD operating systems ([#949](https://github.com/nix-rust/nix/pull/949))
29+
- Added `ptrace` functions for reads and writes to tracee memory and ptrace kill
30+
([#949](https://github.com/nix-rust/nix/pull/949))
2831

2932
### Changed
3033
- Increased required Rust version to 1.22.1/

src/sys/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ pub mod mman;
3838

3939
pub mod pthread;
4040

41-
#[cfg(any(target_os = "android", target_os = "linux"))]
41+
#[cfg(any(target_os = "android",
42+
target_os = "dragonfly",
43+
target_os = "freebsd",
44+
target_os = "linux",
45+
target_os = "macos",
46+
target_os = "netbsd",
47+
target_os = "openbsd"))]
4248
pub mod ptrace;
4349

4450
#[cfg(target_os = "linux")]

src/sys/ptrace/bsd.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use errno::Errno;
2+
use libc::{self, c_int};
3+
use std::ptr;
4+
use sys::signal::Signal;
5+
use unistd::Pid;
6+
use Result;
7+
8+
pub type RequestType = c_int;
9+
10+
cfg_if! {
11+
if #[cfg(any(target_os = "dragonfly",
12+
target_os = "freebsd",
13+
target_os = "macos",
14+
target_os = "openbsd"))] {
15+
#[doc(hidden)]
16+
pub type AddressType = *mut ::libc::c_char;
17+
} else {
18+
#[doc(hidden)]
19+
pub type AddressType = *mut ::libc::c_void;
20+
}
21+
}
22+
23+
libc_enum! {
24+
#[repr(i32)]
25+
/// Ptrace Request enum defining the action to be taken.
26+
pub enum Request {
27+
PT_TRACE_ME,
28+
PT_READ_I,
29+
PT_READ_D,
30+
#[cfg(target_os = "macos")]
31+
PT_READ_U,
32+
PT_WRITE_I,
33+
PT_WRITE_D,
34+
#[cfg(target_os = "macos")]
35+
PT_WRITE_U,
36+
PT_CONTINUE,
37+
PT_KILL,
38+
#[cfg(any(any(target_os = "dragonfly",
39+
target_os = "freebsd",
40+
target_os = "macos"),
41+
all(target_os = "openbsd", target_arch = "x86_64"),
42+
all(target_os = "netbsd", any(target_arch = "x86_64",
43+
target_arch = "powerpc"))))]
44+
PT_STEP,
45+
PT_ATTACH,
46+
PT_DETACH,
47+
#[cfg(target_os = "macos")]
48+
PT_SIGEXC,
49+
#[cfg(target_os = "macos")]
50+
PT_THUPDATE,
51+
#[cfg(target_os = "macos")]
52+
PT_ATTACHEXC
53+
}
54+
}
55+
56+
unsafe fn ptrace_other(
57+
request: Request,
58+
pid: Pid,
59+
addr: AddressType,
60+
data: c_int,
61+
) -> Result<c_int> {
62+
Errno::result(libc::ptrace(
63+
request as RequestType,
64+
libc::pid_t::from(pid),
65+
addr,
66+
data,
67+
)).map(|_| 0)
68+
}
69+
70+
/// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)`
71+
///
72+
/// Indicates that this process is to be traced by its parent.
73+
/// This is the only ptrace request to be issued by the tracee.
74+
pub fn traceme() -> Result<()> {
75+
unsafe { ptrace_other(Request::PT_TRACE_ME, Pid::from_raw(0), ptr::null_mut(), 0).map(|_| ()) }
76+
}
77+
78+
/// Attach to a running process, as with `ptrace(PT_ATTACH, ...)`
79+
///
80+
/// Attaches to the process specified in pid, making it a tracee of the calling process.
81+
pub fn attach(pid: Pid) -> Result<()> {
82+
unsafe { ptrace_other(Request::PT_ATTACH, pid, ptr::null_mut(), 0).map(|_| ()) }
83+
}
84+
85+
/// Detaches the current running process, as with `ptrace(PT_DETACH, ...)`
86+
///
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(|_| ()) }
90+
}
91+
92+
/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)`
93+
///
94+
/// Continues the execution of the process with PID `pid`, optionally
95+
/// delivering a signal specified by `sig`.
96+
pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
97+
let data = match sig.into() {
98+
Some(s) => s as c_int,
99+
None => 0,
100+
};
101+
unsafe {
102+
// Ignore the useless return value
103+
ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data).map(|_| ())
104+
}
105+
}
106+
107+
/// Issues a kill request as with `ptrace(PT_KILL, ...)`
108+
///
109+
/// This request is equivalent to `ptrace(PT_CONTINUE, ..., SIGKILL);`
110+
pub fn kill(pid: Pid) -> Result<()> {
111+
unsafe {
112+
ptrace_other(Request::PT_KILL, pid, 0 as AddressType, 0).map(|_| ())
113+
}
114+
}
115+
116+
/// Move the stopped tracee process forward by a single step as with
117+
/// `ptrace(PT_STEP, ...)`
118+
///
119+
/// Advances the execution of the process with PID `pid` by a single step optionally delivering a
120+
/// signal specified by `sig`.
121+
///
122+
/// # Example
123+
/// ```rust
124+
/// extern crate nix;
125+
/// use nix::sys::ptrace::step;
126+
/// use nix::unistd::Pid;
127+
/// use nix::sys::signal::Signal;
128+
/// use nix::sys::wait::*;
129+
/// fn main() {
130+
/// // If a process changes state to the stopped state because of a SIGUSR1
131+
/// // signal, this will step the process forward and forward the user
132+
/// // signal to the stopped process
133+
/// match waitpid(Pid::from_raw(-1), None) {
134+
/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => {
135+
/// let _ = step(pid, Signal::SIGUSR1);
136+
/// }
137+
/// _ => {},
138+
/// }
139+
/// }
140+
/// ```
141+
#[cfg(
142+
any(
143+
any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"),
144+
all(target_os = "openbsd", target_arch = "x86_64"),
145+
all(target_os = "netbsd",
146+
any(target_arch = "x86_64", target_arch = "powerpc")
147+
)
148+
)
149+
)]
150+
pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
151+
let data = match sig.into() {
152+
Some(s) => s as c_int,
153+
None => 0,
154+
};
155+
unsafe { ptrace_other(Request::PT_STEP, pid, ptr::null_mut(), data).map(|_| ()) }
156+
}
157+
158+
/// Reads a word from a processes memory at the given address
159+
pub fn read(pid: Pid, addr: AddressType) -> Result<c_int> {
160+
unsafe {
161+
// Traditionally there was a difference between reading data or
162+
// instruction memory but not in modern systems.
163+
ptrace_other(Request::PT_READ_D, pid, addr, 0)
164+
}
165+
}
166+
167+
/// Writes a word into the processes memory at the given address
168+
pub fn write(pid: Pid, addr: AddressType, data: c_int) -> Result<()> {
169+
unsafe { ptrace_other(Request::PT_WRITE_D, pid, addr, data).map(|_| ()) }
170+
}

src/sys/ptrace.rs renamed to src/sys/ptrace/linux.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,10 @@ libc_bitflags! {
173173
pub unsafe fn ptrace(request: Request, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> {
174174
use self::Request::*;
175175
match request {
176-
PTRACE_PEEKTEXT | PTRACE_PEEKDATA | PTRACE_PEEKUSER => ptrace_peek(request, pid, addr, data),
177-
PTRACE_GETSIGINFO | PTRACE_GETEVENTMSG | PTRACE_SETSIGINFO | PTRACE_SETOPTIONS => Err(Error::UnsupportedOperation),
176+
PTRACE_PEEKTEXT | PTRACE_PEEKDATA | PTRACE_PEEKUSER | PTRACE_GETSIGINFO |
177+
PTRACE_GETEVENTMSG | PTRACE_SETSIGINFO | PTRACE_SETOPTIONS |
178+
PTRACE_POKETEXT | PTRACE_POKEDATA | PTRACE_POKEUSER |
179+
PTRACE_KILL => Err(Error::UnsupportedOperation),
178180
_ => ptrace_other(request, pid, addr, data)
179181
}
180182
}
@@ -320,6 +322,15 @@ pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
320322
}
321323
}
322324

325+
/// Issues a kill request as with `ptrace(PTRACE_KILL, ...)`
326+
///
327+
/// This request is equivalent to `ptrace(PTRACE_CONT, ..., SIGKILL);`
328+
pub fn kill(pid: Pid) -> Result<()> {
329+
unsafe {
330+
ptrace_other(Request::PTRACE_KILL, pid, ptr::null_mut(), ptr::null_mut()).map(|_| ())
331+
}
332+
}
333+
323334
/// Move the stopped tracee process forward by a single step as with
324335
/// `ptrace(PTRACE_SINGLESTEP, ...)`
325336
///
@@ -354,3 +365,16 @@ pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
354365
ptrace_other(Request::PTRACE_SINGLESTEP, pid, ptr::null_mut(), data).map(|_| ())
355366
}
356367
}
368+
369+
370+
/// Reads a word from a processes memory at the given address
371+
pub fn read(pid: Pid, addr: *mut c_void) -> Result<c_long> {
372+
ptrace_peek(Request::PTRACE_PEEKDATA, pid, addr, ptr::null_mut())
373+
}
374+
375+
/// Writes a word into the processes memory at the given address
376+
pub fn write(pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<()> {
377+
unsafe {
378+
ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data).map(|_| ())
379+
}
380+
}

src/sys/ptrace/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
///! Provides helpers for making ptrace system calls
2+
3+
#[cfg(any(target_os = "android", target_os = "linux"))]
4+
mod linux;
5+
6+
#[cfg(any(target_os = "android", target_os = "linux"))]
7+
pub use self::linux::*;
8+
9+
#[cfg(any(target_os = "dragonfly",
10+
target_os = "freebsd",
11+
target_os = "macos",
12+
target_os = "netbsd",
13+
target_os = "openbsd"))]
14+
mod bsd;
15+
16+
#[cfg(any(target_os = "dragonfly",
17+
target_os = "freebsd",
18+
target_os = "macos",
19+
target_os = "netbsd",
20+
target_os = "openbsd"
21+
))]
22+
pub use self::bsd::*;

test/sys/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,10 @@ mod test_uio;
2727
mod test_epoll;
2828
mod test_pthread;
2929
#[cfg(any(target_os = "android",
30-
target_os = "linux"))]
30+
target_os = "dragonfly",
31+
target_os = "freebsd",
32+
target_os = "linux",
33+
target_os = "macos",
34+
target_os = "netbsd",
35+
target_os = "openbsd"))]
3136
mod test_ptrace;

test/sys/test_ptrace.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use nix::Error;
22
use nix::errno::Errno;
33
use nix::unistd::getpid;
4-
use nix::sys::ptrace::{self, Options};
4+
use nix::sys::ptrace;
5+
#[cfg(any(target_os = "android", target_os = "linux"))]
6+
use nix::sys::ptrace::Options;
57

68
#[cfg(any(target_os = "android", target_os = "linux"))]
79
use std::mem;
@@ -11,25 +13,29 @@ fn test_ptrace() {
1113
// Just make sure ptrace can be called at all, for now.
1214
// FIXME: qemu-user doesn't implement ptrace on all arches, so permit ENOSYS
1315
let err = ptrace::attach(getpid()).unwrap_err();
14-
assert!(err == Error::Sys(Errno::EPERM) || err == Error::Sys(Errno::ENOSYS));
16+
assert!(err == Error::Sys(Errno::EPERM) || err == Error::Sys(Errno::EINVAL) ||
17+
err == Error::Sys(Errno::ENOSYS));
1518
}
1619

1720
// Just make sure ptrace_setoptions can be called at all, for now.
1821
#[test]
22+
#[cfg(any(target_os = "android", target_os = "linux"))]
1923
fn test_ptrace_setoptions() {
2024
let err = ptrace::setoptions(getpid(), Options::PTRACE_O_TRACESYSGOOD).unwrap_err();
2125
assert!(err != Error::UnsupportedOperation);
2226
}
2327

2428
// Just make sure ptrace_getevent can be called at all, for now.
2529
#[test]
30+
#[cfg(any(target_os = "android", target_os = "linux"))]
2631
fn test_ptrace_getevent() {
2732
let err = ptrace::getevent(getpid()).unwrap_err();
2833
assert!(err != Error::UnsupportedOperation);
2934
}
3035

3136
// Just make sure ptrace_getsiginfo can be called at all, for now.
3237
#[test]
38+
#[cfg(any(target_os = "android", target_os = "linux"))]
3339
fn test_ptrace_getsiginfo() {
3440
if let Err(Error::UnsupportedOperation) = ptrace::getsiginfo(getpid()) {
3541
panic!("ptrace_getsiginfo returns Error::UnsupportedOperation!");
@@ -38,6 +44,7 @@ fn test_ptrace_getsiginfo() {
3844

3945
// Just make sure ptrace_setsiginfo can be called at all, for now.
4046
#[test]
47+
#[cfg(any(target_os = "android", target_os = "linux"))]
4148
fn test_ptrace_setsiginfo() {
4249
let siginfo = unsafe { mem::uninitialized() };
4350
if let Err(Error::UnsupportedOperation) = ptrace::setsiginfo(getpid(), &siginfo) {
@@ -50,10 +57,12 @@ fn test_ptrace_setsiginfo() {
5057
fn test_ptrace_cont() {
5158
use nix::sys::ptrace;
5259
use nix::sys::signal::{raise, Signal};
53-
use nix::sys::wait::{waitpid, WaitStatus};
60+
use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
5461
use nix::unistd::fork;
5562
use nix::unistd::ForkResult::*;
5663

64+
let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
65+
5766
// FIXME: qemu-user doesn't implement ptrace on all architectures
5867
// and retunrs ENOSYS in this case.
5968
// We (ab)use this behavior to detect the affected platforms
@@ -79,9 +88,18 @@ fn test_ptrace_cont() {
7988
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
8089
ptrace::cont(child, None).unwrap();
8190
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
82-
ptrace::cont(child, Signal::SIGKILL).unwrap();
91+
ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
8392
match waitpid(child, None) {
84-
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {}
93+
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {
94+
// FIXME It's been observed on some systems (apple) the
95+
// tracee may not be killed but remain as a zombie process
96+
// affecting other wait based tests. Add an extra kill just
97+
// to make sure there are no zombies.
98+
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
99+
while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
100+
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
101+
}
102+
}
85103
_ => panic!("The process should have been killed"),
86104
}
87105
},

0 commit comments

Comments
 (0)