diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-10-21 17:23:58 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-10-21 17:23:58 +0000 |
commit | 9ed9a1dccb98e6a2f91269c09e49641c4ff395b8 (patch) | |
tree | 588c18b2f92397d9593ef1726ad7b6b2514952a4 /src/sys/ptrace | |
parent | eef3a432d57e8f830e05fede6e3099dcb689aa6b (diff) | |
parent | f1573a7efa8368507d581adeb3bff9bbf5aa0ead (diff) | |
download | nix-9ed9a1dccb98e6a2f91269c09e49641c4ff395b8.zip |
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 <danielmckenna93@gmail.com>
Diffstat (limited to 'src/sys/ptrace')
-rw-r--r-- | src/sys/ptrace/bsd.rs | 170 | ||||
-rw-r--r-- | src/sys/ptrace/linux.rs | 380 | ||||
-rw-r--r-- | src/sys/ptrace/mod.rs | 22 |
3 files changed, 572 insertions, 0 deletions
diff --git a/src/sys/ptrace/bsd.rs b/src/sys/ptrace/bsd.rs new file mode 100644 index 00000000..71cfd80a --- /dev/null +++ b/src/sys/ptrace/bsd.rs @@ -0,0 +1,170 @@ +use errno::Errno; +use libc::{self, c_int}; +use std::ptr; +use sys::signal::Signal; +use unistd::Pid; +use Result; + +pub type RequestType = c_int; + +cfg_if! { + if #[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "openbsd"))] { + #[doc(hidden)] + pub type AddressType = *mut ::libc::c_char; + } else { + #[doc(hidden)] + pub type AddressType = *mut ::libc::c_void; + } +} + +libc_enum! { + #[repr(i32)] + /// Ptrace Request enum defining the action to be taken. + pub enum Request { + PT_TRACE_ME, + PT_READ_I, + PT_READ_D, + #[cfg(target_os = "macos")] + PT_READ_U, + PT_WRITE_I, + PT_WRITE_D, + #[cfg(target_os = "macos")] + PT_WRITE_U, + PT_CONTINUE, + PT_KILL, + #[cfg(any(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos"), + all(target_os = "openbsd", target_arch = "x86_64"), + all(target_os = "netbsd", any(target_arch = "x86_64", + target_arch = "powerpc"))))] + PT_STEP, + PT_ATTACH, + PT_DETACH, + #[cfg(target_os = "macos")] + PT_SIGEXC, + #[cfg(target_os = "macos")] + PT_THUPDATE, + #[cfg(target_os = "macos")] + PT_ATTACHEXC + } +} + +unsafe fn ptrace_other( + request: Request, + pid: Pid, + addr: AddressType, + data: c_int, +) -> Result<c_int> { + Errno::result(libc::ptrace( + request as RequestType, + libc::pid_t::from(pid), + addr, + data, + )).map(|_| 0) +} + +/// Sets the process as traceable, as with `ptrace(PT_TRACEME, ...)` +/// +/// Indicates that this process is to be traced by its parent. +/// This is the only ptrace request to be issued by the tracee. +pub fn traceme() -> Result<()> { + unsafe { ptrace_other(Request::PT_TRACE_ME, Pid::from_raw(0), ptr::null_mut(), 0).map(|_| ()) } +} + +/// Attach to a running process, as with `ptrace(PT_ATTACH, ...)` +/// +/// 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::PT_ATTACH, pid, ptr::null_mut(), 0).map(|_| ()) } +} + +/// Detaches the current running process, as with `ptrace(PT_DETACH, ...)` +/// +/// Detaches from the process specified in pid allowing it to run freely +pub fn detach(pid: Pid) -> Result<()> { + unsafe { ptrace_other(Request::PT_DETACH, pid, ptr::null_mut(), 0).map(|_| ()) } +} + +/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)` +/// +/// Continues the execution of the process with PID `pid`, optionally +/// delivering a signal specified by `sig`. +pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as c_int, + None => 0, + }; + unsafe { + // Ignore the useless return value + ptrace_other(Request::PT_CONTINUE, pid, 1 as AddressType, data).map(|_| ()) + } +} + +/// Issues a kill request as with `ptrace(PT_KILL, ...)` +/// +/// This request is equivalent to `ptrace(PT_CONTINUE, ..., SIGKILL);` +pub fn kill(pid: Pid) -> Result<()> { + unsafe { + ptrace_other(Request::PT_KILL, pid, 0 as AddressType, 0).map(|_| ()) + } +} + +/// Move the stopped tracee process forward by a single step as with +/// `ptrace(PT_STEP, ...)` +/// +/// Advances the execution of the process with PID `pid` by a single step optionally delivering a +/// signal specified by `sig`. +/// +/// # Example +/// ```rust +/// extern crate nix; +/// use nix::sys::ptrace::step; +/// use nix::unistd::Pid; +/// 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 +/// // signal to the stopped process +/// match waitpid(Pid::from_raw(-1), None) { +/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => { +/// let _ = step(pid, Signal::SIGUSR1); +/// } +/// _ => {}, +/// } +/// } +/// ``` +#[cfg( + any( + any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"), + all(target_os = "openbsd", target_arch = "x86_64"), + all(target_os = "netbsd", + any(target_arch = "x86_64", target_arch = "powerpc") + ) + ) +)] +pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as c_int, + None => 0, + }; + unsafe { ptrace_other(Request::PT_STEP, pid, ptr::null_mut(), data).map(|_| ()) } +} + +/// Reads a word from a processes memory at the given address +pub fn read(pid: Pid, addr: AddressType) -> Result<c_int> { + unsafe { + // Traditionally there was a difference between reading data or + // instruction memory but not in modern systems. + ptrace_other(Request::PT_READ_D, pid, addr, 0) + } +} + +/// Writes a word into the processes memory at the given address +pub fn write(pid: Pid, addr: AddressType, data: c_int) -> Result<()> { + unsafe { ptrace_other(Request::PT_WRITE_D, pid, addr, data).map(|_| ()) } +} diff --git a/src/sys/ptrace/linux.rs b/src/sys/ptrace/linux.rs new file mode 100644 index 00000000..70404ade --- /dev/null +++ b/src/sys/ptrace/linux.rs @@ -0,0 +1,380 @@ +//! For detailed description of the ptrace requests, consult `man ptrace`. + +use std::{mem, ptr}; +use {Error, Result}; +use errno::Errno; +use libc::{self, c_void, c_long, siginfo_t}; +use ::unistd::Pid; +use sys::signal::Signal; + + +cfg_if! { + if #[cfg(any(all(target_os = "linux", arch = "s390x"), + all(target_os = "linux", target_env = "gnu")))] { + #[doc(hidden)] + pub type RequestType = ::libc::c_uint; + } else { + #[doc(hidden)] + pub type RequestType = ::libc::c_int; + } +} + +libc_enum!{ + #[cfg_attr(not(any(target_env = "musl", target_os = "android")), repr(u32))] + #[cfg_attr(any(target_env = "musl", target_os = "android"), repr(i32))] + /// Ptrace Request enum defining the action to be taken. + pub enum Request { + PTRACE_TRACEME, + PTRACE_PEEKTEXT, + PTRACE_PEEKDATA, + PTRACE_PEEKUSER, + PTRACE_POKETEXT, + PTRACE_POKEDATA, + PTRACE_POKEUSER, + PTRACE_CONT, + PTRACE_KILL, + PTRACE_SINGLESTEP, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "s390x", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_GETREGS, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "s390x", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_SETREGS, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "s390x", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_GETFPREGS, + #[cfg(any(all(target_os = "android", target_pointer_width = "32"), + all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "s390x", + target_arch = "x86_64", + target_pointer_width = "32"))))] + PTRACE_SETFPREGS, + PTRACE_ATTACH, + PTRACE_DETACH, + #[cfg(all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "arm", + target_arch = "x86", + target_arch = "x86_64")))] + PTRACE_GETFPXREGS, + #[cfg(all(target_os = "linux", any(target_env = "musl", + target_arch = "mips", + target_arch = "mips64", + target_arch = "arm", + target_arch = "x86", + target_arch = "x86_64")))] + PTRACE_SETFPXREGS, + PTRACE_SYSCALL, + PTRACE_SETOPTIONS, + PTRACE_GETEVENTMSG, + PTRACE_GETSIGINFO, + PTRACE_SETSIGINFO, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_GETREGSET, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_SETREGSET, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_SEIZE, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_INTERRUPT, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_LISTEN, + #[cfg(all(target_os = "linux", not(any(target_arch = "mips", + target_arch = "mips64"))))] + PTRACE_PEEKSIGINFO, + } +} + +libc_enum!{ + #[repr(i32)] + /// Using the ptrace options the tracer can configure the tracee to stop + /// at certain events. This enum is used to define those events as defined + /// in `man ptrace`. + pub enum Event { + /// Event that stops before a return from fork or clone. + PTRACE_EVENT_FORK, + /// Event that stops before a return from vfork or clone. + PTRACE_EVENT_VFORK, + /// Event that stops before a return from clone. + PTRACE_EVENT_CLONE, + /// Event that stops before a return from execve. + PTRACE_EVENT_EXEC, + /// Event for a return from vfork. + PTRACE_EVENT_VFORK_DONE, + /// Event for a stop before an exit. Unlike the waitpid Exit status program. + /// registers can still be examined + PTRACE_EVENT_EXIT, + /// STop triggered by a seccomp rule on a tracee. + PTRACE_EVENT_SECCOMP, + // PTRACE_EVENT_STOP not provided by libc because it's defined in glibc 2.26 + } +} + +libc_bitflags! { + /// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request. + /// See `man ptrace` for more details. + pub struct Options: libc::c_int { + /// When delivering system call traps set a bit to allow tracer to + /// distinguish between normal stops or syscall stops. May not work on + /// all systems. + PTRACE_O_TRACESYSGOOD; + /// Stop tracee at next fork and start tracing the forked process. + PTRACE_O_TRACEFORK; + /// Stop tracee at next vfork call and trace the vforked process. + PTRACE_O_TRACEVFORK; + /// Stop tracee at next clone call and trace the cloned process. + PTRACE_O_TRACECLONE; + /// Stop tracee at next execve call. + PTRACE_O_TRACEEXEC; + /// Stop tracee at vfork completion. + PTRACE_O_TRACEVFORKDONE; + /// Stop tracee at next exit call. Stops before exit commences allowing + /// tracer to see location of exit and register states. + PTRACE_O_TRACEEXIT; + /// Stop tracee when a SECCOMP_RET_TRACE rule is triggered. See `man seccomp` for more + /// details. + PTRACE_O_TRACESECCOMP; + /// Send a SIGKILL to the tracee if the tracer exits. This is useful + /// for ptrace jailers to prevent tracees from escaping their control. + #[cfg(any(target_os = "android", target_os = "linux"))] + PTRACE_O_EXITKILL; + } +} + +/// Performs a ptrace request. If the request in question is provided by a specialised function +/// this function will return an unsupported operation error. +#[deprecated( + since="0.10.0", + note="usages of `ptrace()` should be replaced with the specialized helper functions instead" +)] +pub unsafe fn ptrace(request: Request, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> { + use self::Request::*; + match request { + PTRACE_PEEKTEXT | PTRACE_PEEKDATA | PTRACE_PEEKUSER | PTRACE_GETSIGINFO | + PTRACE_GETEVENTMSG | PTRACE_SETSIGINFO | PTRACE_SETOPTIONS | + PTRACE_POKETEXT | PTRACE_POKEDATA | PTRACE_POKEUSER | + PTRACE_KILL => Err(Error::UnsupportedOperation), + _ => ptrace_other(request, pid, addr, data) + } +} + +fn ptrace_peek(request: Request, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> { + let ret = unsafe { + Errno::clear(); + 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, + } +} + +/// Function for ptrace requests that return values from the data field. +/// Some ptrace get requests populate structs or larger elements than `c_long` +/// and therefore use the data field to return values. This function handles these +/// requests. +fn ptrace_get_data<T>(request: Request, pid: Pid) -> Result<T> { + // Creates an uninitialized pointer to store result in + let data: T = unsafe { mem::uninitialized() }; + let res = unsafe { + libc::ptrace(request as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::<T>(), + &data as *const _ as *const c_void) + }; + Errno::result(res)?; + Ok(data) +} + +unsafe fn ptrace_other(request: Request, pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<c_long> { + Errno::result(libc::ptrace(request as RequestType, libc::pid_t::from(pid), addr, data)).map(|_| 0) +} + +/// 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), + ptr::null_mut::<c_void>(), + options.bits() as *mut c_void) + }; + Errno::result(res).map(|_| ()) +} + +/// Gets a ptrace event as described by `ptrace(PTRACE_GETEVENTMSG,...)` +pub fn getevent(pid: Pid) -> Result<c_long> { + ptrace_get_data::<c_long>(Request::PTRACE_GETEVENTMSG, pid) +} + +/// Get siginfo as with `ptrace(PTRACE_GETSIGINFO,...)` +pub fn getsiginfo(pid: Pid) -> Result<siginfo_t> { + ptrace_get_data::<siginfo_t>(Request::PTRACE_GETSIGINFO, pid) +} + +/// Set siginfo as with `ptrace(PTRACE_SETSIGINFO,...)` +pub fn setsiginfo(pid: Pid, sig: &siginfo_t) -> Result<()> { + let ret = unsafe{ + Errno::clear(); + libc::ptrace(Request::PTRACE_SETSIGINFO as RequestType, + libc::pid_t::from(pid), + ptr::null_mut::<c_void>(), + sig as *const _ as *const c_void) + }; + match Errno::result(ret) { + Ok(_) => Ok(()), + Err(e) => Err(e), + } +} + +/// Sets the process as traceable, as with `ptrace(PTRACE_TRACEME, ...)` +/// +/// Indicates that this process is to be traced by its parent. +/// This is the only ptrace request to be issued by the tracee. +pub fn traceme() -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_TRACEME, + Pid::from_raw(0), + ptr::null_mut(), + ptr::null_mut(), + ).map(|_| ()) // ignore the useless return value + } +} + +/// Ask for next syscall, as with `ptrace(PTRACE_SYSCALL, ...)` +/// +/// Arranges for the tracee to be stopped at the next entry to or exit from a system call. +pub fn syscall(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_SYSCALL, + pid, + ptr::null_mut(), + ptr::null_mut(), + ).map(|_| ()) // ignore the useless return value + } +} + +/// Attach to a running process, as with `ptrace(PTRACE_ATTACH, ...)` +/// +/// 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 + } +} + +/// Detaches the current running process, as with `ptrace(PTRACE_DETACH, ...)` +/// +/// Detaches from the process specified in pid allowing it to run freely +pub fn detach(pid: Pid) -> Result<()> { + unsafe { + ptrace_other( + Request::PTRACE_DETACH, + pid, + ptr::null_mut(), + ptr::null_mut() + ).map(|_| ()) + } +} + +/// Restart the stopped tracee process, as with `ptrace(PTRACE_CONT, ...)` +/// +/// Continues the execution of the process with PID `pid`, optionally +/// delivering a signal specified by `sig`. +pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_CONT, pid, ptr::null_mut(), data).map(|_| ()) // ignore the useless return value + } +} + +/// Issues a kill request as with `ptrace(PTRACE_KILL, ...)` +/// +/// This request is equivalent to `ptrace(PTRACE_CONT, ..., SIGKILL);` +pub fn kill(pid: Pid) -> Result<()> { + unsafe { + ptrace_other(Request::PTRACE_KILL, pid, ptr::null_mut(), ptr::null_mut()).map(|_| ()) + } +} + +/// 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 +/// signal specified by `sig`. +/// +/// # Example +/// ```rust +/// extern crate nix; +/// use nix::sys::ptrace::step; +/// use nix::unistd::Pid; +/// 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 +/// // signal to the stopped process +/// match waitpid(Pid::from_raw(-1), None) { +/// Ok(WaitStatus::Stopped(pid, Signal::SIGUSR1)) => { +/// let _ = step(pid, Signal::SIGUSR1); +/// } +/// _ => {}, +/// } +/// } +/// ``` +pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> { + let data = match sig.into() { + Some(s) => s as i32 as *mut c_void, + None => ptr::null_mut(), + }; + unsafe { + ptrace_other(Request::PTRACE_SINGLESTEP, pid, ptr::null_mut(), data).map(|_| ()) + } +} + + +/// Reads a word from a processes memory at the given address +pub fn read(pid: Pid, addr: *mut c_void) -> Result<c_long> { + ptrace_peek(Request::PTRACE_PEEKDATA, pid, addr, ptr::null_mut()) +} + +/// Writes a word into the processes memory at the given address +pub fn write(pid: Pid, addr: *mut c_void, data: *mut c_void) -> Result<()> { + unsafe { + ptrace_other(Request::PTRACE_POKEDATA, pid, addr, data).map(|_| ()) + } +} diff --git a/src/sys/ptrace/mod.rs b/src/sys/ptrace/mod.rs new file mode 100644 index 00000000..782c3040 --- /dev/null +++ b/src/sys/ptrace/mod.rs @@ -0,0 +1,22 @@ +///! Provides helpers for making ptrace system calls + +#[cfg(any(target_os = "android", target_os = "linux"))] +mod linux; + +#[cfg(any(target_os = "android", target_os = "linux"))] +pub use self::linux::*; + +#[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd"))] +mod bsd; + +#[cfg(any(target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd" + ))] +pub use self::bsd::*; |