diff --git a/src/lib.rs b/src/lib.rs index 8ddfad9bfc..f9e9b94df7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,7 @@ pub mod unistd; use libc::c_char; use std::{ptr, result}; -use std::ffi::{CStr, OsStr}; +use std::ffi::{CStr, CString, OsStr}; use std::path::{Path, PathBuf}; use std::os::unix::ffi::OsStrExt; use std::fmt; @@ -193,6 +193,17 @@ impl NixPath for CStr { } } +impl NixPath for CString { + fn len(&self) -> usize { + self.to_bytes().len() + } + + fn with_nix_path(&self, f: F) -> Result + where F: FnOnce(&CStr) -> T { + self.as_c_str().with_nix_path(f) + } +} + impl NixPath for [u8] { fn len(&self) -> usize { self.len() @@ -254,3 +265,334 @@ impl<'a, NP: ?Sized + NixPath> NixPath for Option<&'a NP> { } } } + +/* + * + * ===== Null terminated slices for exec ===== + * + */ + +use std::ops::{Deref, DerefMut}; +use std::mem::transmute; +use std::{iter, io}; + +/// An error returned from the [`TerminatedSlice::from_slice`] family of +/// functions when the provided data is not terminated by `None`. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct NotTerminatedError { + // private initializers only + _inner: (), +} + +impl NotTerminatedError { + fn not_terminated() -> Self { + NotTerminatedError { + _inner: (), + } + } +} + +impl error::Error for NotTerminatedError { + fn description(&self) -> &str { "data not terminated by None" } +} + +impl fmt::Display for NotTerminatedError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", error::Error::description(self)) + } +} + +impl From for io::Error { + fn from(e: NotTerminatedError) -> io::Error { + io::Error::new(io::ErrorKind::InvalidInput, + error::Error::description(&e)) + } +} + +/// An error returned from [`TerminatedVec::from_vec`] when the provided data is +/// not terminated by `None`. +#[derive(Clone, PartialEq, Eq)] +pub struct NotTerminatedVecError { + inner: Vec>, +} + +impl NotTerminatedVecError { + fn not_terminated(vec: Vec>) -> Self { + NotTerminatedVecError { + inner: vec, + } + } + + pub fn into_vec(self) -> Vec> { + self.inner + } +} + +impl fmt::Debug for NotTerminatedVecError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("NotTerminatedVecError").finish() + } +} + +impl error::Error for NotTerminatedVecError { + fn description(&self) -> &str { "data not terminated by None" } +} + +impl fmt::Display for NotTerminatedVecError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", error::Error::description(self)) + } +} + +impl From> for io::Error { + fn from(e: NotTerminatedVecError) -> io::Error { + io::Error::new(io::ErrorKind::InvalidInput, + error::Error::description(&e)) + } +} + +/// A conversion trait that may borrow or allocate memory depending on the input. +/// Used to convert between terminated slices and `Vec`s. +pub trait IntoRef<'a, T: ?Sized> { + type Target: 'a + AsRef + Deref; + + fn into_ref(self) -> Self::Target; +} + +/// A slice of references terminated by `None`. Used by API calls that accept +/// null-terminated arrays such as the `exec` family of functions. +pub struct TerminatedSlice { + inner: [Option], +} + +impl TerminatedSlice { + /// Instantiate a `TerminatedSlice` from a slice ending in `None`. Fails if + /// the provided slice is not properly terminated. + pub fn from_slice(slice: &[Option]) -> result::Result<&Self, NotTerminatedError> { + if slice.last().map(Option::is_none).unwrap_or(false) { + Ok(unsafe { Self::from_slice_unchecked(slice) }) + } else { + Err(NotTerminatedError::not_terminated()) + } + } + + /// Instantiate a `TerminatedSlice` from a mutable slice ending in `None`. + /// Fails if the provided slice is not properly terminated. + pub fn from_slice_mut(slice: &mut [Option]) -> result::Result<&mut Self, NotTerminatedError> { + if slice.last().map(Option::is_none).unwrap_or(false) { + Ok(unsafe { Self::from_slice_mut_unchecked(slice) }) + } else { + Err(NotTerminatedError::not_terminated()) + } + } + + /// Instantiate a `TerminatedSlice` from a slice ending in `None`. + /// + /// ## Unsafety + /// + /// This assumes that the slice is properly terminated, and can cause + /// undefined behaviour if that invariant is not upheld. + pub unsafe fn from_slice_unchecked(slice: &[Option]) -> &Self { + transmute(slice) + } + + /// Instantiate a `TerminatedSlice` from a mutable slice ending in `None`. + /// + /// ## Unsafety + /// + /// This assumes that the slice is properly terminated, and can cause + /// undefined behaviour if that invariant is not upheld. + pub unsafe fn from_slice_mut_unchecked(slice: &mut [Option]) -> &mut Self { + transmute(slice) + } +} + +impl<'a, U: Sized> TerminatedSlice<&'a U> { + pub fn as_ptr(&self) -> *const *const U { + self.inner.as_ptr() as *const _ + } +} + +impl Deref for TerminatedSlice { + type Target = [Option]; + + fn deref(&self) -> &Self::Target { + &self.inner[..self.inner.len() - 1] + } +} + +impl DerefMut for TerminatedSlice { + fn deref_mut(&mut self) -> &mut Self::Target { + let len = self.inner.len(); + &mut self.inner[..len - 1] + } +} + +impl AsRef> for TerminatedSlice { + fn as_ref(&self) -> &Self { + self + } +} + +/// Coercion into an argument that can be passed to `exec`. +impl<'a, T: 'a> IntoRef<'a, TerminatedSlice<&'a T>> for &'a TerminatedSlice<&'a T> { + type Target = &'a TerminatedSlice<&'a T>; + + fn into_ref(self) -> Self::Target { + self + } +} + +/// A `Vec` of references terminated by `None`. Used by API calls that accept +/// null-terminated arrays such as the `exec` family of functions. Owned variant +/// of [`TerminatedSlice`]. +pub struct TerminatedVec { + inner: Vec>, +} + +impl TerminatedVec { + /// Instantiates a `TerminatedVec` from a `None` terminated `Vec`. Fails if + /// the provided `Vec` is not properly terminated. + pub fn from_vec(vec: Vec>) -> result::Result> { + if vec.last().map(Option::is_none).unwrap_or(false) { + Ok(unsafe { Self::from_vec_unchecked(vec) }) + } else { + Err(NotTerminatedVecError::not_terminated(vec)) + } + } + + /// Instantiates a `TerminatedVec` from a `None` terminated `Vec`. + /// + /// ## Unsafety + /// + /// This assumes that the `Vec` is properly terminated, and can cause + /// undefined behaviour if that invariant is not upheld. + pub unsafe fn from_vec_unchecked(vec: Vec>) -> Self { + TerminatedVec { + inner: vec, + } + } + + /// Consume `self` to return the inner wrapped `Vec`. + pub fn into_inner(self) -> Vec> { + self.inner + } + + /// Converts an iterator into a `None` terminated `Vec`. + pub fn terminate>(iter: I) -> Self { + let terminated = iter.into_iter() + .map(Some).chain(iter::once(None)).collect(); + + unsafe { + Self::from_vec_unchecked(terminated) + } + } +} + +impl<'a> TerminatedVec<&'a c_char> { + /// Converts an iterator of `AsRef` into a `TerminatedVec` to + /// be used by the `exec` functions. This allows for preallocation of the + /// array when memory allocation could otherwise cause problems (such as + /// when combined with `fork`). + /// + /// ``` + /// use std::iter; + /// use std::ffi::CString; + /// use nix::{TerminatedVec, unistd}; + /// use nix::sys::wait; + /// use nix::libc::_exit; + /// + /// # #[cfg(target_os = "android")] + /// # fn exe_path() -> CString { + /// # CString::new("/system/bin/sh").unwrap() + /// # } + /// # #[cfg(not(target_os = "android"))] + /// # fn exe_path() -> CString { + /// let exe = CString::new("/bin/sh").unwrap(); + /// # exe + /// # } + /// # let exe = exe_path(); + /// let args = [ + /// exe.clone(), + /// CString::new("-c").unwrap(), + /// CString::new("echo hi").unwrap(), + /// ]; + /// let args_p = TerminatedVec::terminate_cstr(&args); + /// let env = TerminatedVec::terminate(iter::empty()); + /// + /// match unistd::fork().unwrap() { + /// unistd::ForkResult::Child => { + /// match unistd::execve(exe.as_c_str(), &args_p, &env) { + /// Err(..) => { + /// // Panicking or returning will drop locals and + /// // deallocate memory, so let's avoid that here. + /// unsafe { _exit(1); } + /// }, + /// Ok(..) => unreachable!(), + /// } + /// }, + /// unistd::ForkResult::Parent { child } => { + /// let status = wait::waitpid(child, None).unwrap(); + /// assert_eq!(status, wait::WaitStatus::Exited(child, 0)); + /// }, + /// } + /// ``` + pub fn terminate_cstr + 'a, I: IntoIterator>(iter: I) -> Self { + fn cstr_char<'a, S: AsRef + 'a>(s: S) -> &'a c_char { + unsafe { + &*s.as_ref().as_ptr() + } + } + + Self::terminate(iter.into_iter().map(cstr_char)) + } +} + +impl Deref for TerminatedVec { + type Target = TerminatedSlice; + + fn deref(&self) -> &Self::Target { + unsafe { + TerminatedSlice::from_slice_unchecked(&self.inner) + } + } +} + +impl DerefMut for TerminatedVec { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { + TerminatedSlice::from_slice_mut_unchecked(&mut self.inner) + } + } +} + +impl AsRef> for TerminatedVec { + fn as_ref(&self) -> &TerminatedSlice { + self + } +} + +impl<'a, T: 'a> IntoRef<'a, TerminatedSlice> for TerminatedVec { + type Target = TerminatedVec; + + fn into_ref(self) -> Self::Target { + self + } +} + +impl<'a, T> IntoRef<'a, TerminatedSlice> for &'a TerminatedVec { + type Target = &'a TerminatedSlice; + + fn into_ref(self) -> Self::Target { + self + } +} + +/// Coercion of `CStr` iterators into an argument that can be passed to `exec`. +impl<'a, T: AsRef + 'a, I: IntoIterator> IntoRef<'a, TerminatedSlice<&'a c_char>> for I { + type Target = TerminatedVec<&'a c_char>; + + fn into_ref(self) -> Self::Target { + TerminatedVec::terminate_cstr(self) + } +} diff --git a/src/unistd.rs b/src/unistd.rs index cb2a29f6ed..5d2e90e2c0 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1,13 +1,13 @@ //! Safe wrappers around functions found in libc "unistd.h" header use errno::{self, Errno}; -use {Error, Result, NixPath}; +use {Error, Result, NixPath, IntoRef, TerminatedSlice}; use fcntl::{fcntl, FdFlag, OFlag}; use fcntl::FcntlArg::F_SETFD; use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t, uid_t, gid_t, mode_t}; -use std::{fmt, mem, ptr}; -use std::ffi::{CString, CStr, OsString, OsStr}; +use std::{fmt, mem}; +use std::ffi::{CStr, OsString, OsStr}; use std::os::unix::ffi::{OsStringExt, OsStrExt}; use std::os::unix::io::RawFd; use std::path::{PathBuf}; @@ -552,12 +552,6 @@ pub fn chown(path: &P, owner: Option, group: Option Vec<*const c_char> { - let mut args_p: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect(); - args_p.push(ptr::null()); - args_p -} - /// Replace the current process image with a new one (see /// [exec(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html)). /// @@ -565,12 +559,12 @@ fn to_exec_array(args: &[CString]) -> Vec<*const c_char> { /// performs the same action but does not allow for customization of the /// environment for the new process. #[inline] -pub fn execv(path: &CString, argv: &[CString]) -> Result { - let args_p = to_exec_array(argv); +pub fn execv<'a, P: ?Sized + NixPath, A: IntoRef<'a, TerminatedSlice<&'a c_char>>>(path: &P, argv: A) -> Result { + let args_p = argv.into_ref(); - unsafe { - libc::execv(path.as_ptr(), args_p.as_ptr()) - }; + try!(path.with_nix_path(|cstr| { + unsafe { libc::execv(cstr.as_ptr(), args_p.as_ptr()) } + })); Err(Error::Sys(Errno::last())) } @@ -589,13 +583,13 @@ pub fn execv(path: &CString, argv: &[CString]) -> Result { /// in the `args` list is an argument to the new process. Each element in the /// `env` list should be a string in the form "key=value". #[inline] -pub fn execve(path: &CString, args: &[CString], env: &[CString]) -> Result { - let args_p = to_exec_array(args); - let env_p = to_exec_array(env); +pub fn execve<'a, 'e, P: ?Sized + NixPath, A: IntoRef<'a, TerminatedSlice<&'a c_char>>, E: IntoRef<'e, TerminatedSlice<&'e c_char>>>(path: &P, args: A, env: E) -> Result { + let args_p = args.into_ref(); + let env_p = env.into_ref(); - unsafe { - libc::execve(path.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) - }; + try!(path.with_nix_path(|cstr| { + unsafe { libc::execve(cstr.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) } + })); Err(Error::Sys(Errno::last())) } @@ -610,12 +604,14 @@ pub fn execve(path: &CString, args: &[CString], env: &[CString]) -> Result /// would not work if "bash" was specified for the path argument, but `execvp` /// would assuming that a bash executable was on the system `PATH`. #[inline] -pub fn execvp(filename: &CString, args: &[CString]) -> Result { - let args_p = to_exec_array(args); +pub fn execvp<'a, P: ?Sized + NixPath, A: IntoRef<'a, TerminatedSlice<&'a c_char>>>(filename: &P, args: A) -> Result { + let args_p = args.into_ref(); - unsafe { - libc::execvp(filename.as_ptr(), args_p.as_ptr()) - }; + try!(filename.with_nix_path(|cstr| { + unsafe { + libc::execvp(cstr.as_ptr(), args_p.as_ptr()) + } + })); Err(Error::Sys(Errno::last())) } @@ -633,9 +629,9 @@ pub fn execvp(filename: &CString, args: &[CString]) -> Result { #[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "linux"))] #[inline] -pub fn fexecve(fd: RawFd, args: &[CString], env: &[CString]) -> Result { - let args_p = to_exec_array(args); - let env_p = to_exec_array(env); +pub fn fexecve<'a, 'e, A: IntoRef<'a, TerminatedSlice<&'a c_char>>, E: IntoRef<'e, TerminatedSlice<&'e c_char>>>(fd: RawFd, args: A, env: E) -> Result { + let args_p = args.into_ref(); + let env_p = env.into_ref(); unsafe { libc::fexecve(fd, args_p.as_ptr(), env_p.as_ptr()) @@ -656,15 +652,17 @@ pub fn fexecve(fd: RawFd, args: &[CString], env: &[CString]) -> Result { /// is referenced as a file descriptor to the base directory plus a path. #[cfg(any(target_os = "android", target_os = "linux"))] #[inline] -pub fn execveat(dirfd: RawFd, pathname: &CString, args: &[CString], - env: &[CString], flags: super::fcntl::AtFlags) -> Result { - let args_p = to_exec_array(args); - let env_p = to_exec_array(env); +pub fn execveat<'a, 'e, A: IntoRef<'a, TerminatedSlice<&'a c_char>>, E: IntoRef<'e, TerminatedSlice<&'e c_char>>, P: ?Sized + NixPath>(dirfd: RawFd, pathname: &P, args: A, + env: E, flags: super::fcntl::AtFlags) -> Result { + let args_p = args.into_ref(); + let env_p = env.into_ref(); - unsafe { - libc::syscall(libc::SYS_execveat, dirfd, pathname.as_ptr(), - args_p.as_ptr(), env_p.as_ptr(), flags); - }; + pathname.with_nix_path(|cstr| { + unsafe { + libc::syscall(libc::SYS_execveat, dirfd, cstr.as_ptr(), + args_p.as_ptr(), env_p.as_ptr(), flags) + } + })?; Err(Error::Sys(Errno::last())) } @@ -1096,6 +1094,8 @@ pub fn setgid(gid: Gid) -> Result<()> { /// with the `opendirectoryd` service. #[cfg(not(any(target_os = "ios", target_os = "macos")))] pub fn getgroups() -> Result> { + use std::ptr; + // First get the number of groups so we can size our Vec let ret = unsafe { libc::getgroups(0, ptr::null_mut()) }; diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 3fe123f056..32e011e972 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -5,6 +5,7 @@ use nix::unistd::*; use nix::unistd::ForkResult::*; use nix::sys::wait::*; use nix::sys::stat::{self, Mode, SFlag}; +use nix::TerminatedVec; use std::{self, env, iter}; use std::ffi::CString; use std::fs::File; @@ -191,7 +192,7 @@ fn test_initgroups() { } macro_rules! execve_test_factory( - ($test_name:ident, $syscall:ident, $exe: expr $(, $pathname:expr, $flags:expr)*) => ( + ($test_name:ident, $syscall:ident, $exeident:ident = $exeinit:expr => $exe:expr $(, $pathident:ident = $pathname:expr, $flags:expr)*) => ( #[test] fn $test_name() { #[allow(unused_variables)] @@ -200,6 +201,18 @@ macro_rules! execve_test_factory( // data from `reader`. let (reader, writer) = pipe().unwrap(); + // Preallocate the `CString` arrays + let $exeident = $exeinit; + $(let $pathident = CString::new($pathname).unwrap();)* + let arg = [CString::new(b"".as_ref()).unwrap(), + CString::new(b"-c".as_ref()).unwrap(), + CString::new(b"echo nix!!! && echo foo=$foo && echo baz=$baz" + .as_ref()).unwrap()]; + let env = [CString::new(b"foo=bar".as_ref()).unwrap(), + CString::new(b"baz=quux".as_ref()).unwrap()]; + let arg_p = TerminatedVec::terminate_cstr(&arg); + let env_p = TerminatedVec::terminate_cstr(&env); + // Safe: Child calls `exit`, `dup`, `close` and the provided `exec*` family function. // NOTE: Technically, this makes the macro unsafe to use because you could pass anything. // The tests make sure not to do that, though. @@ -212,13 +225,9 @@ macro_rules! execve_test_factory( // exec! $syscall( $exe, - $(&CString::new($pathname).unwrap(), )* - &[CString::new(b"".as_ref()).unwrap(), - CString::new(b"-c".as_ref()).unwrap(), - CString::new(b"echo nix!!! && echo foo=$foo && echo baz=$baz" - .as_ref()).unwrap()], - &[CString::new(b"foo=bar".as_ref()).unwrap(), - CString::new(b"baz=quux".as_ref()).unwrap()] + $($pathident.as_c_str(), )* + &arg_p, + &env_p $(, $flags)*).unwrap(); }, Parent { child } => { @@ -240,38 +249,44 @@ macro_rules! execve_test_factory( cfg_if!{ if #[cfg(target_os = "android")] { - execve_test_factory!(test_execve, execve, &CString::new("/system/bin/sh").unwrap()); - execve_test_factory!(test_fexecve, fexecve, File::open("/system/bin/sh").unwrap().into_raw_fd()); + execve_test_factory!(test_execve, execve, exe = CString::new("/system/bin/sh").unwrap() => exe.as_c_str()); + execve_test_factory!(test_fexecve, fexecve, exe = File::open("/system/bin/sh").unwrap() => exe.into_raw_fd()); } else if #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "linux", ))] { - execve_test_factory!(test_execve, execve, &CString::new("/bin/sh").unwrap()); - execve_test_factory!(test_fexecve, fexecve, File::open("/bin/sh").unwrap().into_raw_fd()); + execve_test_factory!(test_execve, execve, exe = CString::new("/bin/sh").unwrap() => exe.as_c_str()); + execve_test_factory!(test_fexecve, fexecve, exe = File::open("/bin/sh").unwrap() => exe.into_raw_fd()); } else if #[cfg(any(target_os = "ios", target_os = "macos", ))] { - execve_test_factory!(test_execve, execve, &CString::new("/bin/sh").unwrap()); + execve_test_factory!(test_execve, execve, exe = CString::new("/bin/sh").unwrap() => exe.as_c_str()); // No fexecve() on macos/ios. } } cfg_if!{ if #[cfg(target_os = "android")] { - use nix::fcntl::AtFlags; - execve_test_factory!(test_execveat_empty, execveat, File::open("/system/bin/sh").unwrap().into_raw_fd(), - "", AtFlags::AT_EMPTY_PATH); - execve_test_factory!(test_execveat_relative, execveat, File::open("/system/bin/").unwrap().into_raw_fd(), - "./sh", AtFlags::empty()); - execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(), - "/system/bin/sh", AtFlags::empty()); + use nix::fcntl::{AtFlags, open}; + execve_test_factory!(test_execveat_empty, execveat, exe = CString::new("/system/bin/sh").unwrap() + => open(exe.as_c_str(), OFlag::O_PATH, Mode::empty()).unwrap(), + path = "", AtFlags::AT_EMPTY_PATH); + execve_test_factory!(test_execveat_relative, execveat, exe = CString::new("/system/bin/").unwrap() + => open(exe.as_c_str(), OFlag::O_PATH, Mode::empty()).unwrap(), + path = "./sh", AtFlags::empty()); + execve_test_factory!(test_execveat_absolute, execveat, exe = CString::new("/").unwrap() + => open(exe.as_c_str(), OFlag::O_PATH, Mode::empty()).unwrap(), + path = "/system/bin/sh", AtFlags::empty()); } else if #[cfg(all(target_os = "linux"), any(target_arch ="x86_64", target_arch ="x86"))] { - use nix::fcntl::AtFlags; - execve_test_factory!(test_execveat_empty, execveat, File::open("/bin/sh").unwrap().into_raw_fd(), - "", AtFlags::AT_EMPTY_PATH); - execve_test_factory!(test_execveat_relative, execveat, File::open("/bin/").unwrap().into_raw_fd(), - "./sh", AtFlags::empty()); - execve_test_factory!(test_execveat_absolute, execveat, File::open("/").unwrap().into_raw_fd(), - "/bin/sh", AtFlags::empty()); + use nix::fcntl::{AtFlags, open}; + execve_test_factory!(test_execveat_empty, execveat, exe = CString::new("/bin/sh").unwrap() + => open(exe.as_c_str(), OFlag::O_PATH, Mode::empty()).unwrap(), + path = "", AtFlags::AT_EMPTY_PATH); + execve_test_factory!(test_execveat_relative, execveat, exe = CString::new("/bin/").unwrap() + => open(exe.as_c_str(), OFlag::O_PATH, Mode::empty()).unwrap(), + path = "./sh", AtFlags::empty()); + execve_test_factory!(test_execveat_absolute, execveat, exe = CString::new("/").unwrap() + => open(exe.as_c_str(), OFlag::O_PATH, Mode::empty()).unwrap(), + path = "/bin/sh", AtFlags::empty()); } }