diff --git a/CHANGELOG.md b/CHANGELOG.md index 27ae766d88..dd39ce65a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ 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: @@ -88,6 +89,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). - 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)) - Added more accessor methods for `AioCb` ([#773](https://github.com/nix-rust/nix/pull/773)) - Add `nix::sys::fallocate` diff --git a/src/sys/ptrace.rs b/src/sys/ptrace.rs index 4fb4f5b57c..1949be75ec 100644 --- a/src/sys/ptrace.rs +++ b/src/sys/ptrace.rs @@ -1,4 +1,7 @@ -//! For detailed description of the ptrace requests, consult `man ptrace`. +//! Interface for `ptrace` +//! +//! For detailed description of the ptrace requests, consult [`ptrace`(2)]. +//! [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html use std::{mem, ptr}; use {Error, Result}; @@ -139,11 +142,20 @@ pub unsafe fn ptrace(request: Request, pid: Pid, addr: *mut c_void, data: *mut c } } -fn ptrace_peek(request: Request, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result { - let ret = unsafe { - Errno::clear(); - libc::ptrace(request as RequestType, libc::pid_t::from(pid), addr, data) - }; +unsafe fn ptrace_peek( + request: Request, + pid: Pid, + addr: *mut c_void, + data: *mut c_void +) -> Result { + + Errno::clear(); + let ret = libc::ptrace( + request as RequestType, + libc::pid_t::from(pid), + addr, + data + ); match Errno::result(ret) { Ok(..) | Err(Error::Sys(Errno::UnknownErrno)) => Ok(ret), err @ Err(..) => err, @@ -173,8 +185,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), @@ -243,12 +253,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(|_| ()) } } @@ -280,7 +285,214 @@ pub fn cont>>(pid: Pid, sig: T) -> Result<()> { } } -/// Move the stopped tracee process forward by a single step as with +/// Represents all possible ptrace-accessible registers on x86_64 +#[cfg(target_arch = "x86_64")] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, PartialEq)] +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, +} + +/// Represents all possible ptrace-accessible registers on x86 +#[cfg(target_arch = "x86")] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, PartialEq)] +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. +/// +/// [`syscall_arg32`]: macro.syscall_arg32.html +/// # 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. +/// +/// [`syscall_arg`]: macro.syscall_arg.html +/// # 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 { + 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 { + 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 libc::c_long; + + #[test] + fn test_types() { + // c_long is implementation defined, so make sure + // its width matches + assert_eq!(size_of::(), size_of::()); + } +} + +/// Move the stopped tracee process forward by a single step as with /// `ptrace(PTRACE_SINGLESTEP, ...)` /// /// Advances the execution of the process with PID `pid` by a single step optionally delivering a @@ -291,11 +503,11 @@ pub fn cont>>(pid: Pid, sig: T) -> Result<()> { /// extern crate nix; /// use nix::sys::ptrace::step; /// use nix::unistd::Pid; -/// use nix::sys::signal::Signal; +/// use nix::sys::signal::Signal; /// use nix::sys::wait::*; /// fn main() { -/// // If a process changes state to the stopped state because of a SIGUSR1 -/// // signal, this will step the process forward and forward the user +/// // If a process changes state to the stopped state because of a SIGUSR1 +/// // signal, this will step the process forward and forward the user /// // signal to the stopped process /// match waitpid(Pid::from_raw(-1), None) { /// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => { diff --git a/test/sys/test_ptrace.rs b/test/sys/test_ptrace.rs index debc451755..172f6b05e2 100644 --- a/test/sys/test_ptrace.rs +++ b/test/sys/test_ptrace.rs @@ -1,9 +1,10 @@ use nix::Error; use nix::errno::Errno; use nix::unistd::getpid; +use nix::libc; use nix::sys::ptrace::{self, Options}; -use std::mem; +use std::{mem, ptr}; #[test] fn test_ptrace() { @@ -44,6 +45,108 @@ fn test_ptrace_setsiginfo() { } } +#[test] +#[cfg(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, Options::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() {