Skip to content

Commit

Permalink
Merge branch 'master' into acct-api
Browse files Browse the repository at this point in the history
  • Loading branch information
jabedude committed Oct 21, 2018
2 parents 3d56c27 + 9ed9a1d commit c773ea4
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 9 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -27,6 +27,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#955](https://github.com/nix-rust/nix/pull/955))
- Added a `acct` wrapper module for enabling and disabling process accounting
([#952](https://github.com/nix-rust/nix/pull/952))
- Added support for `ptrace` on BSD operating systems ([#949](https://github.com/nix-rust/nix/pull/949))
- Added `ptrace` functions for reads and writes to tracee memory and ptrace kill
([#949](https://github.com/nix-rust/nix/pull/949))
- Added a `acct` wrapper module for enabling and disabling process accounting
([#952](https://github.com/nix-rust/nix/pull/952))


### Changed
- Increased required Rust version to 1.22.1/
Expand Down
8 changes: 7 additions & 1 deletion src/sys/mod.rs
Expand Up @@ -38,7 +38,13 @@ pub mod mman;

pub mod pthread;

#[cfg(any(target_os = "android", target_os = "linux"))]
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
pub mod ptrace;

#[cfg(target_os = "linux")]
Expand Down
170 changes: 170 additions & 0 deletions 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(|_| ()) }
}
28 changes: 26 additions & 2 deletions src/sys/ptrace.rs → src/sys/ptrace/linux.rs
Expand Up @@ -173,8 +173,10 @@ libc_bitflags! {
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_peek(request, pid, addr, data),
PTRACE_GETSIGINFO | PTRACE_GETEVENTMSG | PTRACE_SETSIGINFO | PTRACE_SETOPTIONS => Err(Error::UnsupportedOperation),
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)
}
}
Expand Down Expand Up @@ -320,6 +322,15 @@ pub fn cont<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
}
}

/// 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, ...)`
///
Expand Down Expand Up @@ -354,3 +365,16 @@ pub fn step<T: Into<Option<Signal>>>(pid: Pid, sig: T) -> Result<()> {
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(|_| ())
}
}
22 changes: 22 additions & 0 deletions 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::*;
7 changes: 6 additions & 1 deletion test/sys/mod.rs
Expand Up @@ -27,5 +27,10 @@ mod test_uio;
mod test_epoll;
mod test_pthread;
#[cfg(any(target_os = "android",
target_os = "linux"))]
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
mod test_ptrace;
28 changes: 23 additions & 5 deletions test/sys/test_ptrace.rs
@@ -1,7 +1,9 @@
use nix::Error;
use nix::errno::Errno;
use nix::unistd::getpid;
use nix::sys::ptrace::{self, Options};
use nix::sys::ptrace;
#[cfg(any(target_os = "android", target_os = "linux"))]
use nix::sys::ptrace::Options;

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

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

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

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

// Just make sure ptrace_setsiginfo can be called at all, for now.
#[test]
#[cfg(any(target_os = "android", target_os = "linux"))]
fn test_ptrace_setsiginfo() {
let siginfo = unsafe { mem::uninitialized() };
if let Err(Error::UnsupportedOperation) = ptrace::setsiginfo(getpid(), &siginfo) {
Expand All @@ -50,10 +57,12 @@ fn test_ptrace_setsiginfo() {
fn test_ptrace_cont() {
use nix::sys::ptrace;
use nix::sys::signal::{raise, Signal};
use nix::sys::wait::{waitpid, WaitStatus};
use nix::sys::wait::{waitpid, WaitPidFlag, 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
Expand All @@ -79,9 +88,18 @@ fn test_ptrace_cont() {
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
ptrace::cont(child, None).unwrap();
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, Signal::SIGTRAP)));
ptrace::cont(child, Signal::SIGKILL).unwrap();
ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
match waitpid(child, None) {
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {}
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _)) if pid == child => {
// FIXME It's been observed on some systems (apple) the
// tracee may not be killed but remain as a zombie process
// affecting other wait based tests. Add an extra kill just
// to make sure there are no zombies.
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
while ptrace::cont(child, Some(Signal::SIGKILL)).is_ok() {
let _ = waitpid(child, Some(WaitPidFlag::WNOHANG));
}
}
_ => panic!("The process should have been killed"),
}
},
Expand Down

0 comments on commit c773ea4

Please sign in to comment.