diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f2d075494..351f7deca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/src/sys/uio.rs b/src/sys/uio.rs index edca8cb4ff..16426ab91f 100644 --- a/src/sys/uio.rs +++ b/src/sys/uio.rs @@ -56,6 +56,85 @@ pub fn pread(fd: RawFd, buf: &mut [u8], offset: off_t) -> Result{ 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 { + 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 { + 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(libc::iovec, PhantomData); diff --git a/test/sys/test_uio.rs b/test/sys/test_uio.rs index d805b3b399..57296938da 100644 --- a/test/sys/test_uio.rs +++ b/test/sys/test_uio.rs @@ -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") + } +}