Skip to content

Commit

Permalink
Add high-level wrappers for peeking and poking with ptrace
Browse files Browse the repository at this point in the history
  • Loading branch information
marmistrz committed Sep 18, 2017
1 parent 0fff824 commit 95d39a1
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 12 deletions.
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#739](https://github.com/nix-rust/nix/pull/739))
- Expose `signalfd` module on Android as well.
([#739](https://github.com/nix-rust/nix/pull/739))
- Added nix::sys::ptrace::detach.
- Added nix::sys::ptrace::detach.
([#749](https://github.com/nix-rust/nix/pull/749))
- Added timestamp socket control message variant:
`nix::sys::socket::ControlMessage::ScmTimestamp`
([#663](https://github.com/nix-rust/nix/pull/663))
- Added socket option variant that enables the timestamp socket
control message: `nix::sys::socket::sockopt::ReceiveTimestamp`
([#663](https://github.com/nix-rust/nix/pull/663))

- Added specialized wrappers: `sys::ptrace::{peek, poke}{user, data}`
and macros: `syscall_arg`, `syscall_arg32` for register-to-argument
mappings. Using the matching routines
with `sys::ptrace::ptrace` is now deprecated.
([#666](https://github.com/nix-rust/nix/pull/666))
### Changed
- Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692))
- Marked `sys::ptrace::ptrace` as `unsafe`.
Expand All @@ -55,7 +59,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#731](https://github.com/nix-rust/nix/pull/731))
- Marked `pty::ptsname` function as `unsafe`
([#744](https://github.com/nix-rust/nix/pull/744))
- Moved constants ptrace request, event and options to enums and updated ptrace functions and argument types accordingly.
- Moved constants ptrace request, event and options to enums and updated ptrace functions and argument types accordingly.
([#749](https://github.com/nix-rust/nix/pull/749))
- `AioCb::Drop` will now panic if the `AioCb` is still in-progress ([#715](https://github.com/nix-rust/nix/pull/715))

Expand Down
213 changes: 205 additions & 8 deletions src/sys/ptrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,6 @@ unsafe fn ptrace_other(request: Request, pid: Pid, addr: *mut c_void, data: *mut

/// Set options, as with `ptrace(PTRACE_SETOPTIONS,...)`.
pub fn setoptions(pid: Pid, options: Options) -> Result<()> {
use std::ptr;

let res = unsafe {
libc::ptrace(Request::PTRACE_SETOPTIONS as RequestType,
libc::pid_t::from(pid),
Expand Down Expand Up @@ -238,12 +236,7 @@ pub fn syscall(pid: Pid) -> Result<()> {
/// Attaches to the process specified in pid, making it a tracee of the calling process.
pub fn attach(pid: Pid) -> Result<()> {
unsafe {
ptrace_other(
Request::PTRACE_ATTACH,
pid,
ptr::null_mut(),
ptr::null_mut(),
).map(|_| ()) // ignore the useless return value
ptrace_other(Request::PTRACE_ATTACH, pid, ptr::null_mut(), ptr::null_mut()).map(|_| ()) // ignore the useless return value
}
}

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

#[cfg(target_arch = "x86_64")]
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq)]
/// Represents all possible ptrace-accessible registers on x86_64
pub enum Register {
R15 = 8 * ::libc::R15 as isize,
R14 = 8 * ::libc::R14 as isize,
R13 = 8 * ::libc::R13 as isize,
R12 = 8 * ::libc::R12 as isize,
RBP = 8 * ::libc::RBP as isize,
RBX = 8 * ::libc::RBX as isize,
R11 = 8 * ::libc::R11 as isize,
R10 = 8 * ::libc::R10 as isize,
R9 = 8 * ::libc::R9 as isize,
R8 = 8 * ::libc::R8 as isize,
RAX = 8 * ::libc::RAX as isize,
RCX = 8 * ::libc::RCX as isize,
RDX = 8 * ::libc::RDX as isize,
RSI = 8 * ::libc::RSI as isize,
RDI = 8 * ::libc::RDI as isize,
ORIG_RAX = 8 * ::libc::ORIG_RAX as isize,
RIP = 8 * ::libc::RIP as isize,
CS = 8 * ::libc::CS as isize,
EFLAGS = 8 * ::libc::EFLAGS as isize,
RSP = 8 * ::libc::RSP as isize,
SS = 8 * ::libc::SS as isize,
FS_BASE = 8 * ::libc::FS_BASE as isize,
GS_BASE = 8 * ::libc::GS_BASE as isize,
DS = 8 * ::libc::DS as isize,
ES = 8 * ::libc::ES as isize,
FS = 8 * ::libc::FS as isize,
GS = 8 * ::libc::GS as isize,
}

#[cfg(target_arch = "x86")]
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq)]
/// Represents all possible ptrace-accessible registers on x86
pub enum Register {
EBX = 4 * ::libc::EBX as isize,
ECX = 4 * ::libc::ECX as isize,
EDX = 4 * ::libc::EDX as isize,
ESI = 4 * ::libc::ESI as isize,
EDI = 4 * ::libc::EDI as isize,
EBP = 4 * ::libc::EBP as isize,
EAX = 4 * ::libc::EAX as isize,
DS = 4 * ::libc::DS as isize,
ES = 4 * ::libc::ES as isize,
FS = 4 * ::libc::FS as isize,
GS = 4 * ::libc::GS as isize,
ORIG_EAX = 4 * ::libc::ORIG_EAX as isize,
EIP = 4 * ::libc::EIP as isize,
CS = 4 * ::libc::CS as isize,
EFL = 4 * ::libc::EFL as isize,
UESP = 4 * ::libc::UESP as isize,
SS = 4 * ::libc::SS as isize,
}

/// Returns the register containing nth register argument.
///
/// 0th argument is considered to be the syscall number.
/// Please note that these mappings are only valid for 64-bit programs.
/// Use syscall_arg32 for tracing 32-bit programs instead.
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate nix;
/// # fn main() {
/// assert_eq!(syscall_arg!(1), nix::sys::ptrace::Register::RDI);
/// # }
#[cfg(target_arch = "x86_64")]
#[macro_export]
macro_rules! syscall_arg {
(0) => ($crate::sys::ptrace::Register::ORIG_RAX);
(1) => ($crate::sys::ptrace::Register::RDI);
(2) => ($crate::sys::ptrace::Register::RSI);
(3) => ($crate::sys::ptrace::Register::RDX);
(4) => ($crate::sys::ptrace::Register::R10);
(5) => ($crate::sys::ptrace::Register::R8);
(6) => ($crate::sys::ptrace::Register::R9);
}

/// Returns the register containing nth register argument for 32-bit programs
///
/// 0th argument is considered to be the syscall number.
/// Please note that these mappings are only valid for 32-bit programs.
/// Use syscall_arg for tracing 64-bit programs instead.
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate nix;
/// # fn main() {
/// assert_eq!(syscall_arg32!(1), nix::sys::ptrace::Register::RBX);
/// # }
#[cfg(target_arch = "x86_64")]
#[macro_export]
macro_rules! syscall_arg32 {
(0) => ($crate::sys::ptrace::Register::ORIG_RAX);
(1) => ($crate::sys::ptrace::Register::RBX);
(2) => ($crate::sys::ptrace::Register::RCX);
(3) => ($crate::sys::ptrace::Register::RDX);
(4) => ($crate::sys::ptrace::Register::RSI);
(5) => ($crate::sys::ptrace::Register::RDI);
(6) => ($crate::sys::ptrace::Register::RBP);
}

/// Returns the register containing nth register argument.
///
/// 0th argument is considered to be the syscall number.
///
/// # Examples
///
/// ```
/// # #[macro_use] extern crate nix;
/// # fn main() {
/// assert_eq!(syscall_arg!(1), nix::sys::ptrace::Register::RDI);
/// # }
#[cfg(target_arch = "x86")]
#[macro_export]
macro_rules! syscall_arg {
(0) => ($crate::sys::ptrace::Register::ORIG_EAX);
(1) => ($crate::sys::ptrace::Register::EBX);
(2) => ($crate::sys::ptrace::Register::ECX);
(3) => ($crate::sys::ptrace::Register::EDX);
(4) => ($crate::sys::ptrace::Register::ESI);
(5) => ($crate::sys::ptrace::Register::EDI);
(6) => ($crate::sys::ptrace::Register::EBP);
}

/// An integer type, whose size equals a machine word
///
/// `ptrace` always returns a machine word. This type provides an abstraction
/// of the fact that on *nix systems, `c_long` is always a machine word,
/// so as to prevent the library from leaking C implementation-dependent types.
type Word = usize;

/// Peeks a user-accessible register, as with `ptrace(PTRACE_PEEKUSER, ...)`
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn peekuser(pid: Pid, reg: Register) -> Result<Word> {
let reg_arg = (reg as i32) as *mut c_void;
unsafe {
ptrace_peek(Request::PTRACE_PEEKUSER, pid, reg_arg, ptr::null_mut()).map(|r| r as Word)
}
}

/// Sets the value of a user-accessible register, as with `ptrace(PTRACE_POKEUSER, ...)`
///
/// # Safety
/// When incorrectly used, may change the registers to bad values,
/// causing e.g. memory being corrupted by a syscall, thus is marked unsafe
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub unsafe fn pokeuser(pid: Pid, reg: Register, val: Word) -> Result<()> {
let reg_arg = (reg as u64) as *mut c_void;
ptrace_other(Request::PTRACE_POKEUSER, pid, reg_arg, val as *mut c_void).map(|_| ()) // ignore the useless return value
}

/// Peeks the memory of a process, as with `ptrace(PTRACE_PEEKDATA, ...)`
///
/// A memory chunk of a size of a machine word is returned.
/// # Safety
/// This function allows for accessing arbitrary data in the traced process
/// and may crash the inferior if used incorrectly and is thus marked `unsafe`.
pub unsafe fn peekdata(pid: Pid, addr: usize) -> Result<Word> {
ptrace_peek(
Request::PTRACE_PEEKDATA,
pid,
addr as *mut c_void,
ptr::null_mut(),
).map(|r| r as Word)
}

/// Modifies the memory of a process, as with `ptrace(PTRACE_POKEUSER, ...)`
///
/// A memory chunk of a size of a machine word is overwriten in the requested
/// place in the memory of a process.
///
/// # Safety
/// This function allows for accessing arbitrary data in the traced process
/// and may crash the inferior or introduce race conditions if used
/// incorrectly and is thus marked `unsafe`.
pub unsafe fn pokedata(pid: Pid, addr: usize, val: Word) -> Result<()> {
ptrace_other(
Request::PTRACE_POKEDATA,
pid,
addr as *mut c_void,
val as *mut c_void,
).map(|_| ()) // ignore the useless return value
}

#[cfg(test)]
mod tests {
use super::Word;
use std::mem::size_of;
use super::libc::c_long;

#[test]
fn test_types() {
// c_long is implementation defined, so make sure
// its width matches
assert_eq!(size_of::<Word>(), size_of::<c_long>());
}
}
111 changes: 110 additions & 1 deletion test/sys/test_ptrace.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#![cfg(all(target_os = "linux", any(target_arch = "x86",
target_arch = "x86_64",
target_arch = "arm")))]

use nix::Error;
use nix::errno::Errno;
use nix::unistd::getpid;
use nix::sys::ptrace;
use nix::libc;

use std::mem;
use std::{mem, ptr};

#[test]
fn test_ptrace() {
Expand Down Expand Up @@ -46,6 +51,108 @@ fn test_ptrace_setsiginfo() {
}
}

#[test]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn test_ptrace_peekpoke() {
use nix::sys::ptrace;
use nix::sys::signal::{raise, Signal};
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::fork;
use nix::unistd::ForkResult::*;

let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

// FIXME: qemu-user doesn't implement ptrace on all architectures
// and retunrs ENOSYS in this case.
// We (ab)use this behavior to detect the affected platforms
// and skip the test then.
// On valid platforms the ptrace call should return Errno::EPERM, this
// is already tested by `test_ptrace`.
let err = ptrace::attach(getpid()).unwrap_err();
if err == Error::Sys(Errno::ENOSYS) {
return;
}

match fork() {
Ok(Child) => {
ptrace::traceme().unwrap();
// As recommended by ptrace(2), raise SIGTRAP to pause the child
// until the parent is ready to continue
raise(Signal::SIGTRAP).unwrap();
unsafe {
let size = 10000;
let ptr = libc::calloc(size, 1);
libc::getcwd(ptr as *mut i8, size);
libc::free(ptr);
libc::getpriority(0, 42);
libc::_exit(0);
}
}
Ok(Parent { child }) => {
assert_eq!(
waitpid(child, None),
Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
);

let mut syscall_no = None;
let mut getpriority_checked = false;
let mut getcwd_checked = false;

ptrace::setoptions(child, ptrace::PTRACE_O_TRACESYSGOOD).unwrap();

loop {
ptrace::syscall(child).unwrap();
match waitpid(child, None).unwrap() {
WaitStatus::PtraceSyscall(child) => {
match syscall_no {
None => {
let no = ptrace::peekuser(child, syscall_arg!(0)).unwrap();
syscall_no = Some(no);
if no as i64 == libc::SYS_getpriority as i64 {
let arg2 = ptrace::peekuser(child, syscall_arg!(2)).unwrap();
assert_eq!(arg2, 42);
unsafe {
ptrace::pokeuser(child, syscall_arg!(2), 0).unwrap();
}
let arg2 = ptrace::peekuser(child, syscall_arg!(2)).unwrap();
assert_eq!(arg2, 0);

getpriority_checked = true;
}
}
Some(no) => {
syscall_no = None;
if no as i64 == libc::SYS_getcwd as i64 {
let ret = ptrace::peekuser(child, syscall_arg!(0)).unwrap();
assert!(ret != 0); // no error occured
let buf = ptrace::peekuser(child, syscall_arg!(1)).unwrap();
unsafe {
let word = ptrace::peekdata(child, buf).unwrap();
assert!(word != 0); // something was written to the buffer
ptrace::pokedata(child, buf, 0).unwrap();
let new_word = ptrace::peekdata(child, buf).unwrap();
assert_eq!(new_word, 0);
}

getcwd_checked = true;
}
}
}
}
WaitStatus::Exited(_, code) => {
assert_eq!(code, 0);
break;
}
_ => {}
}
}

assert!(getpriority_checked);
assert!(getcwd_checked);
}
Err(_) => panic!("Error: Fork Failed"),
}
}

#[test]
fn test_ptrace_cont() {
Expand All @@ -55,6 +162,8 @@ fn test_ptrace_cont() {
use nix::unistd::fork;
use nix::unistd::ForkResult::*;

let _m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

// FIXME: qemu-user doesn't implement ptrace on all architectures
// and retunrs ENOSYS in this case.
// We (ab)use this behavior to detect the affected platforms
Expand Down

0 comments on commit 95d39a1

Please sign in to comment.