Skip to content

Commit

Permalink
Add process_vm_readv and process_vm_writev
Browse files Browse the repository at this point in the history
  • Loading branch information
geofft committed Nov 9, 2017
1 parent d54d874 commit 3f5de94
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#602](https://github.com/nix-rust/nix/pull/774))
- Added `ptrace::Options::PTRACE_O_EXITKILL` on Linux and Android.
([#771](https://github.com/nix-rust/nix/pull/771))
- Added `nix::sys::uio::{process_vm_readv, process_vm_writev}` on Linux
([#568](https://github.com/nix-rust/nix/pull/568))

### Changed
- Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692))
Expand Down
79 changes: 79 additions & 0 deletions src/sys/uio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,85 @@ pub fn pread(fd: RawFd, buf: &mut [u8], offset: off_t) -> Result<usize>{
Errno::result(res).map(|r| r as usize)
}

/// A slice of memory in a remote process, starting at address `base`
/// and consisting of `len` bytes.
///
/// This is the same underlying C structure as [`IoVec`](struct.IoVec.html),
/// except that it refers to memory in some other process, and is
/// therefore not represented in Rust by an actual slice as IoVec is. It
/// is used with [`process_vm_readv`](fn.process_vm_readv.html)
/// and [`process_vm_writev`](fn.process_vm_writev.html).
#[cfg(target_os = "linux")]
#[repr(C)]
pub struct RemoteIoVec {
/// The starting address of this slice (`iov_base`).
pub base: usize,
/// The number of bytes in this slice (`iov_len`).
pub len: usize,
}

/// Write data directly to another process's virtual memory
/// (see [`process_vm_writev`(2)]).
///
/// `local_iov` is a list of [`IoVec`]s containing the data to be written,
/// and `remote_iov` is a list of [`RemoteIoVec`]s identifying where the
/// data should be written in the target process. On success, returns the
/// number of bytes written, which will always be a whole
/// number of remote_iov chunks.
///
/// This requires the same permissions as debugging the process using
/// [ptrace]: you must either be a privileged process (with
/// `CAP_SYS_PTRACE`), or you must be running as the same user as the
/// target process and the OS must have unprivileged debugging enabled.
///
/// This function is only available on Linux.
///
/// [`process_vm_writev`(2)]: http://man7.org/linux/man-pages/man2/process_vm_writev.2.html
/// [ptrace]: ../ptrace/index.html
/// [`IoVec`]: struct.IoVec.html
/// [`RemoteIoVec`]: struct.RemoteIoVec.html
#[cfg(target_os = "linux")]
pub fn process_vm_writev(pid: ::unistd::Pid, local_iov: &[IoVec<&[u8]>], remote_iov: &[RemoteIoVec]) -> Result<usize> {
let res = unsafe {
libc::process_vm_writev(pid.into(),
local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong,
remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0)
};

Errno::result(res).map(|r| r as usize)
}

/// Read data directly from another process's virtual memory
/// (see [`process_vm_readv`(2)]).
///
/// `local_iov` is a list of [`IoVec`]s containing the buffer to copy
/// data into, and `remote_iov` is a list of [`RemoteIoVec`]s identifying
/// where the source data is in the target process. On success,
/// returns the number of bytes written, which will always be a whole
/// number of remote_iov chunks.
///
/// This requires the same permissions as debugging the process using
/// [ptrace]: you must either be a privileged process (with
/// `CAP_SYS_PTRACE`), or you must be running as the same user as the
/// target process and the OS must have unprivileged debugging enabled.
///
/// This function is only available on Linux.
///
/// [`process_vm_readv`(2)]: http://man7.org/linux/man-pages/man2/process_vm_readv.2.html
/// [ptrace]: ../ptrace/index.html
/// [`IoVec`]: struct.IoVec.html
/// [`RemoteIoVec`]: struct.RemoteIoVec.html
#[cfg(any(target_os = "linux"))]
pub fn process_vm_readv(pid: ::unistd::Pid, local_iov: &[IoVec<&mut [u8]>], remote_iov: &[RemoteIoVec]) -> Result<usize> {
let res = unsafe {
libc::process_vm_readv(pid.into(),
local_iov.as_ptr() as *const libc::iovec, local_iov.len() as libc::c_ulong,
remote_iov.as_ptr() as *const libc::iovec, remote_iov.len() as libc::c_ulong, 0)
};

Errno::result(res).map(|r| r as usize)
}

#[repr(C)]
pub struct IoVec<T>(libc::iovec, PhantomData<T>);

Expand Down
52 changes: 52 additions & 0 deletions test/sys/test_uio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,55 @@ fn test_preadv() {
let all = buffers.concat();
assert_eq!(all, expected);
}

#[test]
#[cfg(target_os = "linux")]
// FIXME: qemu-user doesn't implement process_vm_readv/writev on most arches
#[cfg_attr(not(any(target_arch = "x86", target_arch = "x86_64")), ignore)]
fn test_process_vm_readv() {
use nix::unistd::ForkResult::*;
use nix::sys::signal::*;
use nix::sys::wait::*;
use std::str;

#[allow(unused_variables)]
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");

// Pre-allocate memory in the child, since allocation isn't safe
// post-fork (~= async-signal-safe)
let mut vector = vec![1u8, 2, 3, 4, 5];

let (r, w) = pipe().unwrap();
match fork() {
Ok(Parent { child }) => {
close(w).unwrap();
// wait for child
read(r, &mut vec![0u8]).unwrap();
close(r).unwrap();

let ptr = vector.as_ptr() as usize;
let remote_iov = RemoteIoVec { base: ptr, len: 5 };
let mut buf = vec![0u8; 5];

let ret = process_vm_readv(child,
&[IoVec::from_mut_slice(&mut buf)],
&[remote_iov]);

kill(child, SIGTERM).unwrap();
waitpid(child, None).unwrap();

assert_eq!(Ok(5), ret);
assert_eq!(20u8, buf.iter().sum());
},
Ok(Child) => {
let _ = close(r);
for i in vector.iter_mut() {
*i += 1;
}
let _ = write(w, b"\0");
let _ = close(w);
loop { let _ = pause(); }
},
Err(_) => panic!("fork failed")
}
}

0 comments on commit 3f5de94

Please sign in to comment.