Skip to content

Commit 95d39a1

Browse files
committed
Add high-level wrappers for peeking and poking with ptrace
1 parent 0fff824 commit 95d39a1

File tree

3 files changed

+322
-12
lines changed

3 files changed

+322
-12
lines changed

CHANGELOG.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,19 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2222
([#739](https://github.com/nix-rust/nix/pull/739))
2323
- Expose `signalfd` module on Android as well.
2424
([#739](https://github.com/nix-rust/nix/pull/739))
25-
- Added nix::sys::ptrace::detach.
25+
- Added nix::sys::ptrace::detach.
2626
([#749](https://github.com/nix-rust/nix/pull/749))
2727
- Added timestamp socket control message variant:
2828
`nix::sys::socket::ControlMessage::ScmTimestamp`
2929
([#663](https://github.com/nix-rust/nix/pull/663))
3030
- Added socket option variant that enables the timestamp socket
3131
control message: `nix::sys::socket::sockopt::ReceiveTimestamp`
3232
([#663](https://github.com/nix-rust/nix/pull/663))
33-
33+
- Added specialized wrappers: `sys::ptrace::{peek, poke}{user, data}`
34+
and macros: `syscall_arg`, `syscall_arg32` for register-to-argument
35+
mappings. Using the matching routines
36+
with `sys::ptrace::ptrace` is now deprecated.
37+
([#666](https://github.com/nix-rust/nix/pull/666))
3438
### Changed
3539
- Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692))
3640
- Marked `sys::ptrace::ptrace` as `unsafe`.
@@ -55,7 +59,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
5559
([#731](https://github.com/nix-rust/nix/pull/731))
5660
- Marked `pty::ptsname` function as `unsafe`
5761
([#744](https://github.com/nix-rust/nix/pull/744))
58-
- Moved constants ptrace request, event and options to enums and updated ptrace functions and argument types accordingly.
62+
- Moved constants ptrace request, event and options to enums and updated ptrace functions and argument types accordingly.
5963
([#749](https://github.com/nix-rust/nix/pull/749))
6064
- `AioCb::Drop` will now panic if the `AioCb` is still in-progress ([#715](https://github.com/nix-rust/nix/pull/715))
6165

src/sys/ptrace.rs

Lines changed: 205 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,6 @@ unsafe fn ptrace_other(request: Request, pid: Pid, addr: *mut c_void, data: *mut
168168

169169
/// Set options, as with `ptrace(PTRACE_SETOPTIONS,...)`.
170170
pub fn setoptions(pid: Pid, options: Options) -> Result<()> {
171-
use std::ptr;
172-
173171
let res = unsafe {
174172
libc::ptrace(Request::PTRACE_SETOPTIONS as RequestType,
175173
libc::pid_t::from(pid),
@@ -238,12 +236,7 @@ pub fn syscall(pid: Pid) -> Result<()> {
238236
/// Attaches to the process specified in pid, making it a tracee of the calling process.
239237
pub fn attach(pid: Pid) -> Result<()> {
240238
unsafe {
241-
ptrace_other(
242-
Request::PTRACE_ATTACH,
243-
pid,
244-
ptr::null_mut(),
245-
ptr::null_mut(),
246-
).map(|_| ()) // ignore the useless return value
239+
ptrace_other(Request::PTRACE_ATTACH, pid, ptr::null_mut(), ptr::null_mut()).map(|_| ()) // ignore the useless return value
247240
}
248241
}
249242

@@ -275,3 +268,207 @@ pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
275268
}
276269
}
277270

271+
#[cfg(target_arch = "x86_64")]
272+
#[allow(non_camel_case_types)]
273+
#[derive(Debug, PartialEq)]
274+
/// Represents all possible ptrace-accessible registers on x86_64
275+
pub enum Register {
276+
R15 = 8 * ::libc::R15 as isize,
277+
R14 = 8 * ::libc::R14 as isize,
278+
R13 = 8 * ::libc::R13 as isize,
279+
R12 = 8 * ::libc::R12 as isize,
280+
RBP = 8 * ::libc::RBP as isize,
281+
RBX = 8 * ::libc::RBX as isize,
282+
R11 = 8 * ::libc::R11 as isize,
283+
R10 = 8 * ::libc::R10 as isize,
284+
R9 = 8 * ::libc::R9 as isize,
285+
R8 = 8 * ::libc::R8 as isize,
286+
RAX = 8 * ::libc::RAX as isize,
287+
RCX = 8 * ::libc::RCX as isize,
288+
RDX = 8 * ::libc::RDX as isize,
289+
RSI = 8 * ::libc::RSI as isize,
290+
RDI = 8 * ::libc::RDI as isize,
291+
ORIG_RAX = 8 * ::libc::ORIG_RAX as isize,
292+
RIP = 8 * ::libc::RIP as isize,
293+
CS = 8 * ::libc::CS as isize,
294+
EFLAGS = 8 * ::libc::EFLAGS as isize,
295+
RSP = 8 * ::libc::RSP as isize,
296+
SS = 8 * ::libc::SS as isize,
297+
FS_BASE = 8 * ::libc::FS_BASE as isize,
298+
GS_BASE = 8 * ::libc::GS_BASE as isize,
299+
DS = 8 * ::libc::DS as isize,
300+
ES = 8 * ::libc::ES as isize,
301+
FS = 8 * ::libc::FS as isize,
302+
GS = 8 * ::libc::GS as isize,
303+
}
304+
305+
#[cfg(target_arch = "x86")]
306+
#[allow(non_camel_case_types)]
307+
#[derive(Debug, PartialEq)]
308+
/// Represents all possible ptrace-accessible registers on x86
309+
pub enum Register {
310+
EBX = 4 * ::libc::EBX as isize,
311+
ECX = 4 * ::libc::ECX as isize,
312+
EDX = 4 * ::libc::EDX as isize,
313+
ESI = 4 * ::libc::ESI as isize,
314+
EDI = 4 * ::libc::EDI as isize,
315+
EBP = 4 * ::libc::EBP as isize,
316+
EAX = 4 * ::libc::EAX as isize,
317+
DS = 4 * ::libc::DS as isize,
318+
ES = 4 * ::libc::ES as isize,
319+
FS = 4 * ::libc::FS as isize,
320+
GS = 4 * ::libc::GS as isize,
321+
ORIG_EAX = 4 * ::libc::ORIG_EAX as isize,
322+
EIP = 4 * ::libc::EIP as isize,
323+
CS = 4 * ::libc::CS as isize,
324+
EFL = 4 * ::libc::EFL as isize,
325+
UESP = 4 * ::libc::UESP as isize,
326+
SS = 4 * ::libc::SS as isize,
327+
}
328+
329+
/// Returns the register containing nth register argument.
330+
///
331+
/// 0th argument is considered to be the syscall number.
332+
/// Please note that these mappings are only valid for 64-bit programs.
333+
/// Use syscall_arg32 for tracing 32-bit programs instead.
334+
///
335+
/// # Examples
336+
///
337+
/// ```
338+
/// # #[macro_use] extern crate nix;
339+
/// # fn main() {
340+
/// assert_eq!(syscall_arg!(1), nix::sys::ptrace::Register::RDI);
341+
/// # }
342+
#[cfg(target_arch = "x86_64")]
343+
#[macro_export]
344+
macro_rules! syscall_arg {
345+
(0) => ($crate::sys::ptrace::Register::ORIG_RAX);
346+
(1) => ($crate::sys::ptrace::Register::RDI);
347+
(2) => ($crate::sys::ptrace::Register::RSI);
348+
(3) => ($crate::sys::ptrace::Register::RDX);
349+
(4) => ($crate::sys::ptrace::Register::R10);
350+
(5) => ($crate::sys::ptrace::Register::R8);
351+
(6) => ($crate::sys::ptrace::Register::R9);
352+
}
353+
354+
/// Returns the register containing nth register argument for 32-bit programs
355+
///
356+
/// 0th argument is considered to be the syscall number.
357+
/// Please note that these mappings are only valid for 32-bit programs.
358+
/// Use syscall_arg for tracing 64-bit programs instead.
359+
///
360+
/// # Examples
361+
///
362+
/// ```
363+
/// # #[macro_use] extern crate nix;
364+
/// # fn main() {
365+
/// assert_eq!(syscall_arg32!(1), nix::sys::ptrace::Register::RBX);
366+
/// # }
367+
#[cfg(target_arch = "x86_64")]
368+
#[macro_export]
369+
macro_rules! syscall_arg32 {
370+
(0) => ($crate::sys::ptrace::Register::ORIG_RAX);
371+
(1) => ($crate::sys::ptrace::Register::RBX);
372+
(2) => ($crate::sys::ptrace::Register::RCX);
373+
(3) => ($crate::sys::ptrace::Register::RDX);
374+
(4) => ($crate::sys::ptrace::Register::RSI);
375+
(5) => ($crate::sys::ptrace::Register::RDI);
376+
(6) => ($crate::sys::ptrace::Register::RBP);
377+
}
378+
379+
/// Returns the register containing nth register argument.
380+
///
381+
/// 0th argument is considered to be the syscall number.
382+
///
383+
/// # Examples
384+
///
385+
/// ```
386+
/// # #[macro_use] extern crate nix;
387+
/// # fn main() {
388+
/// assert_eq!(syscall_arg!(1), nix::sys::ptrace::Register::RDI);
389+
/// # }
390+
#[cfg(target_arch = "x86")]
391+
#[macro_export]
392+
macro_rules! syscall_arg {
393+
(0) => ($crate::sys::ptrace::Register::ORIG_EAX);
394+
(1) => ($crate::sys::ptrace::Register::EBX);
395+
(2) => ($crate::sys::ptrace::Register::ECX);
396+
(3) => ($crate::sys::ptrace::Register::EDX);
397+
(4) => ($crate::sys::ptrace::Register::ESI);
398+
(5) => ($crate::sys::ptrace::Register::EDI);
399+
(6) => ($crate::sys::ptrace::Register::EBP);
400+
}
401+
402+
/// An integer type, whose size equals a machine word
403+
///
404+
/// `ptrace` always returns a machine word. This type provides an abstraction
405+
/// of the fact that on *nix systems, `c_long` is always a machine word,
406+
/// so as to prevent the library from leaking C implementation-dependent types.
407+
type Word = usize;
408+
409+
/// Peeks a user-accessible register, as with `ptrace(PTRACE_PEEKUSER, ...)`
410+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
411+
pub fn peekuser(pid: Pid, reg: Register) -> Result<Word> {
412+
let reg_arg = (reg as i32) as *mut c_void;
413+
unsafe {
414+
ptrace_peek(Request::PTRACE_PEEKUSER, pid, reg_arg, ptr::null_mut()).map(|r| r as Word)
415+
}
416+
}
417+
418+
/// Sets the value of a user-accessible register, as with `ptrace(PTRACE_POKEUSER, ...)`
419+
///
420+
/// # Safety
421+
/// When incorrectly used, may change the registers to bad values,
422+
/// causing e.g. memory being corrupted by a syscall, thus is marked unsafe
423+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
424+
pub unsafe fn pokeuser(pid: Pid, reg: Register, val: Word) -> Result<()> {
425+
let reg_arg = (reg as u64) as *mut c_void;
426+
ptrace_other(Request::PTRACE_POKEUSER, pid, reg_arg, val as *mut c_void).map(|_| ()) // ignore the useless return value
427+
}
428+
429+
/// Peeks the memory of a process, as with `ptrace(PTRACE_PEEKDATA, ...)`
430+
///
431+
/// A memory chunk of a size of a machine word is returned.
432+
/// # Safety
433+
/// This function allows for accessing arbitrary data in the traced process
434+
/// and may crash the inferior if used incorrectly and is thus marked `unsafe`.
435+
pub unsafe fn peekdata(pid: Pid, addr: usize) -> Result<Word> {
436+
ptrace_peek(
437+
Request::PTRACE_PEEKDATA,
438+
pid,
439+
addr as *mut c_void,
440+
ptr::null_mut(),
441+
).map(|r| r as Word)
442+
}
443+
444+
/// Modifies the memory of a process, as with `ptrace(PTRACE_POKEUSER, ...)`
445+
///
446+
/// A memory chunk of a size of a machine word is overwriten in the requested
447+
/// place in the memory of a process.
448+
///
449+
/// # Safety
450+
/// This function allows for accessing arbitrary data in the traced process
451+
/// and may crash the inferior or introduce race conditions if used
452+
/// incorrectly and is thus marked `unsafe`.
453+
pub unsafe fn pokedata(pid: Pid, addr: usize, val: Word) -> Result<()> {
454+
ptrace_other(
455+
Request::PTRACE_POKEDATA,
456+
pid,
457+
addr as *mut c_void,
458+
val as *mut c_void,
459+
).map(|_| ()) // ignore the useless return value
460+
}
461+
462+
#[cfg(test)]
463+
mod tests {
464+
use super::Word;
465+
use std::mem::size_of;
466+
use super::libc::c_long;
467+
468+
#[test]
469+
fn test_types() {
470+
// c_long is implementation defined, so make sure
471+
// its width matches
472+
assert_eq!(size_of::<Word>(), size_of::<c_long>());
473+
}
474+
}

test/sys/test_ptrace.rs

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
#![cfg(all(target_os = "linux", any(target_arch = "x86",
2+
target_arch = "x86_64",
3+
target_arch = "arm")))]
4+
15
use nix::Error;
26
use nix::errno::Errno;
37
use nix::unistd::getpid;
48
use nix::sys::ptrace;
9+
use nix::libc;
510

6-
use std::mem;
11+
use std::{mem, ptr};
712

813
#[test]
914
fn test_ptrace() {
@@ -46,6 +51,108 @@ fn test_ptrace_setsiginfo() {
4651
}
4752
}
4853

54+
#[test]
55+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
56+
fn test_ptrace_peekpoke() {
57+
use nix::sys::ptrace;
58+
use nix::sys::signal::{raise, Signal};
59+
use nix::sys::wait::{waitpid, WaitStatus};
60+
use nix::unistd::fork;
61+
use nix::unistd::ForkResult::*;
62+
63+
let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
64+
65+
// FIXME: qemu-user doesn't implement ptrace on all architectures
66+
// and retunrs ENOSYS in this case.
67+
// We (ab)use this behavior to detect the affected platforms
68+
// and skip the test then.
69+
// On valid platforms the ptrace call should return Errno::EPERM, this
70+
// is already tested by `test_ptrace`.
71+
let err = ptrace::attach(getpid()).unwrap_err();
72+
if err == Error::Sys(Errno::ENOSYS) {
73+
return;
74+
}
75+
76+
match fork() {
77+
Ok(Child) => {
78+
ptrace::traceme().unwrap();
79+
// As recommended by ptrace(2), raise SIGTRAP to pause the child
80+
// until the parent is ready to continue
81+
raise(Signal::SIGTRAP).unwrap();
82+
unsafe {
83+
let size = 10000;
84+
let ptr = libc::calloc(size, 1);
85+
libc::getcwd(ptr as *mut i8, size);
86+
libc::free(ptr);
87+
libc::getpriority(0, 42);
88+
libc::_exit(0);
89+
}
90+
}
91+
Ok(Parent { child }) => {
92+
assert_eq!(
93+
waitpid(child, None),
94+
Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
95+
);
96+
97+
let mut syscall_no = None;
98+
let mut getpriority_checked = false;
99+
let mut getcwd_checked = false;
100+
101+
ptrace::setoptions(child, ptrace::PTRACE_O_TRACESYSGOOD).unwrap();
102+
103+
loop {
104+
ptrace::syscall(child).unwrap();
105+
match waitpid(child, None).unwrap() {
106+
WaitStatus::PtraceSyscall(child) => {
107+
match syscall_no {
108+
None => {
109+
let no = ptrace::peekuser(child, syscall_arg!(0)).unwrap();
110+
syscall_no = Some(no);
111+
if no as i64 == libc::SYS_getpriority as i64 {
112+
let arg2 = ptrace::peekuser(child, syscall_arg!(2)).unwrap();
113+
assert_eq!(arg2, 42);
114+
unsafe {
115+
ptrace::pokeuser(child, syscall_arg!(2), 0).unwrap();
116+
}
117+
let arg2 = ptrace::peekuser(child, syscall_arg!(2)).unwrap();
118+
assert_eq!(arg2, 0);
119+
120+
getpriority_checked = true;
121+
}
122+
}
123+
Some(no) => {
124+
syscall_no = None;
125+
if no as i64 == libc::SYS_getcwd as i64 {
126+
let ret = ptrace::peekuser(child, syscall_arg!(0)).unwrap();
127+
assert!(ret != 0); // no error occured
128+
let buf = ptrace::peekuser(child, syscall_arg!(1)).unwrap();
129+
unsafe {
130+
let word = ptrace::peekdata(child, buf).unwrap();
131+
assert!(word != 0); // something was written to the buffer
132+
ptrace::pokedata(child, buf, 0).unwrap();
133+
let new_word = ptrace::peekdata(child, buf).unwrap();
134+
assert_eq!(new_word, 0);
135+
}
136+
137+
getcwd_checked = true;
138+
}
139+
}
140+
}
141+
}
142+
WaitStatus::Exited(_, code) => {
143+
assert_eq!(code, 0);
144+
break;
145+
}
146+
_ => {}
147+
}
148+
}
149+
150+
assert!(getpriority_checked);
151+
assert!(getcwd_checked);
152+
}
153+
Err(_) => panic!("Error: Fork Failed"),
154+
}
155+
}
49156

50157
#[test]
51158
fn test_ptrace_cont() {
@@ -55,6 +162,8 @@ fn test_ptrace_cont() {
55162
use nix::unistd::fork;
56163
use nix::unistd::ForkResult::*;
57164

165+
let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
166+
58167
// FIXME: qemu-user doesn't implement ptrace on all architectures
59168
// and retunrs ENOSYS in this case.
60169
// We (ab)use this behavior to detect the affected platforms

0 commit comments

Comments
 (0)