diff options
Diffstat (limited to 'rust/minijail/src/lib.rs')
-rw-r--r-- | rust/minijail/src/lib.rs | 356 |
1 files changed, 291 insertions, 65 deletions
diff --git a/rust/minijail/src/lib.rs b/rust/minijail/src/lib.rs index ba59075..5028041 100644 --- a/rust/minijail/src/lib.rs +++ b/rust/minijail/src/lib.rs @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use libc::pid_t; -use minijail_sys::*; use std::ffi::CString; use std::fmt::{self, Display}; use std::fs; @@ -12,6 +10,154 @@ use std::os::raw::{c_char, c_ulong, c_ushort}; use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; use std::path::{Path, PathBuf}; use std::ptr::{null, null_mut}; +use std::result::Result as StdResult; + +use libc::pid_t; +use minijail_sys::*; + +enum Program { + Filename(PathBuf), + FileDescriptor(RawFd), +} + +/// Configuration of a command to be run in a jail. +pub struct Command { + program: Program, + preserve_fds: Vec<(RawFd, RawFd)>, + + // Ownership of the backing data of args_cptr is provided by args_cstr. + args_cstr: Vec<CString>, + args_cptr: Vec<*const c_char>, + + // Ownership of the backing data of env_cptr is provided by env_cstr. + env_cstr: Option<Vec<CString>>, + env_cptr: Option<Vec<*const c_char>>, +} + +impl Command { + /// This exposes a subset of what Command can do, before we are ready to commit to a stable + /// API. + pub fn new_for_path<P: AsRef<Path>, S: AsRef<str>, A: AsRef<str>>( + path: P, + keep_fds: &[RawFd], + args: &[S], + env_vars: Option<&[A]>, + ) -> Result<Command> { + let mut cmd = Command::new(Program::Filename(path.as_ref().to_path_buf())) + .keep_fds(keep_fds) + .args(args)?; + if let Some(env_vars) = env_vars { + cmd = cmd.envs(env_vars)?; + } + + Ok(cmd) + } + + fn new(program: Program) -> Command { + Command { + program, + preserve_fds: Vec::new(), + args_cstr: Vec::new(), + args_cptr: Vec::new(), + env_cstr: None, + env_cptr: None, + } + } + + fn keep_fds(mut self, keep_fds: &[RawFd]) -> Command { + self.preserve_fds = keep_fds + .iter() + .map(|&a| (a, a)) + .collect::<Vec<(RawFd, RawFd)>>(); + self + } + + fn remap_fds(mut self, remap_fds: &[(RawFd, RawFd)]) -> Command { + self.preserve_fds = remap_fds.to_vec(); + self + } + + fn args<S: AsRef<str>>(mut self, args: &[S]) -> Result<Command> { + let (args_cstr, args_cptr) = to_execve_cstring_array(args)?; + self.args_cstr = args_cstr; + self.args_cptr = args_cptr; + Ok(self) + } + + fn envs<S: AsRef<str>>(mut self, vars: &[S]) -> Result<Command> { + let (env_cstr, env_cptr) = to_execve_cstring_array(vars)?; + self.env_cstr = Some(env_cstr); + self.env_cptr = Some(env_cptr); + Ok(self) + } + + fn argv(&self) -> *const *mut c_char { + self.args_cptr.as_ptr() as *const *mut c_char + } + + fn envp(&self) -> *const *mut c_char { + (match self.env_cptr { + Some(ref env_cptr) => env_cptr.as_ptr(), + None => null_mut(), + }) as *const *mut c_char + } +} + +/// Abstracts paths and executable file descriptors in a way that the run implementation can cover +/// both. +trait Runnable { + fn run_command(&self, jail: &Minijail, cmd: &Command) -> Result<pid_t>; +} + +impl Runnable for &Path { + fn run_command(&self, jail: &Minijail, cmd: &Command) -> Result<pid_t> { + let path_str = self + .to_str() + .ok_or_else(|| Error::PathToCString(self.to_path_buf()))?; + let path_cstr = + CString::new(path_str).map_err(|_| Error::StrToCString(path_str.to_owned()))?; + + let mut pid: pid_t = 0; + let ret = unsafe { + minijail_run_env_pid_pipes( + jail.jail, + path_cstr.as_ptr(), + cmd.argv(), + cmd.envp(), + &mut pid, + null_mut(), + null_mut(), + null_mut(), + ) + }; + if ret < 0 { + return Err(Error::ForkingMinijail(ret)); + } + Ok(pid) + } +} + +impl Runnable for RawFd { + fn run_command(&self, jail: &Minijail, cmd: &Command) -> Result<pid_t> { + let mut pid: pid_t = 0; + let ret = unsafe { + minijail_run_fd_env_pid_pipes( + jail.jail, + *self, + cmd.argv(), + cmd.envp(), + &mut pid, + null_mut(), + null_mut(), + null_mut(), + ) + }; + if ret < 0 { + return Err(Error::ForkingMinijail(ret)); + } + Ok(pid) + } +} #[derive(Debug)] pub enum Error { @@ -83,6 +229,8 @@ pub enum Error { Killed(u8), /// Process finished returning a non-zero code. ReturnCode(u8), + /// Failed to wait the process. + Wait(i32), } impl Display for Error { @@ -171,13 +319,14 @@ impl Display for Error { SeccompViolation(s) => write!(f, "seccomp violation syscall #{}", s), Killed(s) => write!(f, "killed with signal number {}", s), ReturnCode(e) => write!(f, "exited with code {}", e), + Wait(errno) => write!(f, "failed to wait: {}", io::Error::from_raw_os_error(*errno)), } } } impl std::error::Error for Error {} -pub type Result<T> = std::result::Result<T, Error>; +pub type Result<T> = StdResult<T, Error>; /// Configuration to jail a process based on wrapping libminijail. /// @@ -233,6 +382,9 @@ fn translate_wait_error(ret: libc::c_int) -> Result<()> { if ret == 0 { return Ok(()); } + if ret < 0 { + return Err(Error::Wait(ret)); + } if ret == MINIJAIL_ERR_NO_COMMAND as libc::c_int { return Err(Error::NoCommand); } @@ -249,7 +401,7 @@ fn translate_wait_error(ret: libc::c_int) -> Result<()> { if ret > 0 && ret <= 0xff { return Err(Error::ReturnCode(ret as u8)); } - unreachable!(); + unreachable!("Unexpected returned value from wait: {}", ret); } impl Minijail { @@ -313,7 +465,7 @@ impl Minijail { } pub fn set_supplementary_gids(&mut self, ids: &[libc::gid_t]) { unsafe { - minijail_set_supplementary_gids(self.jail, ids.len(), ids.as_ptr()); + minijail_set_supplementary_gids(self.jail, ids.len() as size_t, ids.as_ptr()); } } pub fn keep_supplementary_gids(&mut self) { @@ -358,6 +510,9 @@ impl Minijail { } let buffer = fs::read(path).map_err(Error::ReadProgram)?; + self.parse_seccomp_bytes(&buffer) + } + pub fn parse_seccomp_bytes(&mut self, buffer: &[u8]) -> Result<()> { if buffer.len() % std::mem::size_of::<sock_filter>() != 0 { return Err(Error::WrongProgramSize); } @@ -612,7 +767,7 @@ impl Minijail { } pub fn mount_tmp_size(&mut self, size: usize) { unsafe { - minijail_mount_tmp_size(self.jail, size); + minijail_mount_tmp_size(self.jail, size as size_t); } } pub fn mount_bind<P1: AsRef<Path>, P2: AsRef<Path>>( @@ -662,13 +817,10 @@ impl Minijail { inheritable_fds: &[RawFd], args: &[S], ) -> Result<pid_t> { - self.run_remap( - cmd, - &inheritable_fds - .iter() - .map(|&a| (a, a)) - .collect::<Vec<(RawFd, RawFd)>>(), - args, + self.run_internal( + Command::new(Program::Filename(cmd.as_ref().to_path_buf())) + .keep_fds(inheritable_fds) + .args(args)?, ) } @@ -680,25 +832,49 @@ impl Minijail { inheritable_fds: &[(RawFd, RawFd)], args: &[S], ) -> Result<pid_t> { - let cmd_os = cmd - .as_ref() - .to_str() - .ok_or_else(|| Error::PathToCString(cmd.as_ref().to_owned()))?; - let cmd_cstr = CString::new(cmd_os).map_err(|_| Error::StrToCString(cmd_os.to_owned()))?; + self.run_internal( + Command::new(Program::Filename(cmd.as_ref().to_path_buf())) + .remap_fds(inheritable_fds) + .args(args)?, + ) + } - // Converts each incoming `args` string to a `CString`, and then puts each `CString` pointer - // into a null terminated array, suitable for use as an argv parameter to `execve`. - let mut args_cstr = Vec::with_capacity(args.len()); - let mut args_array = Vec::with_capacity(args.len()); - for arg in args { - let arg_cstr = CString::new(arg.as_ref()) - .map_err(|_| Error::StrToCString(arg.as_ref().to_owned()))?; - args_array.push(arg_cstr.as_ptr()); - args_cstr.push(arg_cstr); - } - args_array.push(null()); + /// Behaves the same as `run()` except cmd is a file descriptor to the executable. + pub fn run_fd<F: AsRawFd, S: AsRef<str>>( + &self, + cmd: &F, + inheritable_fds: &[RawFd], + args: &[S], + ) -> Result<pid_t> { + self.run_internal( + Command::new(Program::FileDescriptor(cmd.as_raw_fd())) + .keep_fds(inheritable_fds) + .args(args)?, + ) + } - for (src_fd, dst_fd) in inheritable_fds { + /// Behaves the same as `run()` except cmd is a file descriptor to the executable, and + /// `inheritable_fds` is a list of fd mappings rather than just a list of fds to preserve. + pub fn run_fd_remap<F: AsRawFd, S: AsRef<str>>( + &self, + cmd: &F, + inheritable_fds: &[(RawFd, RawFd)], + args: &[S], + ) -> Result<pid_t> { + self.run_internal( + Command::new(Program::FileDescriptor(cmd.as_raw_fd())) + .remap_fds(inheritable_fds) + .args(args)?, + ) + } + + /// A generic version of `run()` that is a super set of all variants. + pub fn run_command(&self, cmd: Command) -> Result<pid_t> { + self.run_internal(cmd) + } + + fn run_internal(&self, cmd: Command) -> Result<pid_t> { + for (src_fd, dst_fd) in cmd.preserve_fds.iter() { let ret = unsafe { minijail_preserve_fd(self.jail, *src_fd, *dst_fd) }; if ret < 0 { return Err(Error::PreservingFd(ret)); @@ -713,7 +889,7 @@ impl Minijail { // Set stdin, stdout, and stderr to /dev/null unless they are in the inherit list. // These will only be closed when this process exits. for io_fd in &[libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] { - if !inheritable_fds.iter().any(|(_, fd)| *fd == *io_fd) { + if !cmd.preserve_fds.iter().any(|(_, fd)| *fd == *io_fd) { let ret = unsafe { minijail_preserve_fd(self.jail, dev_null.as_raw_fd(), *io_fd) }; if ret < 0 { return Err(Error::PreservingFd(ret)); @@ -725,22 +901,10 @@ impl Minijail { minijail_close_open_fds(self.jail); } - let mut pid = 0; - let ret = unsafe { - minijail_run_pid_pipes( - self.jail, - cmd_cstr.as_ptr(), - args_array.as_ptr() as *const *mut c_char, - &mut pid, - null_mut(), - null_mut(), - null_mut(), - ) - }; - if ret < 0 { - return Err(Error::ForkingMinijail(ret)); + match cmd.program { + Program::Filename(ref path) => path.as_path().run_command(&self, &cmd), + Program::FileDescriptor(fd) => fd.run_command(&self, &cmd), } - Ok(pid) } /// Forks a child and puts it in the previously configured minijail. @@ -860,15 +1024,54 @@ fn is_single_threaded() -> io::Result<bool> { } } +fn to_execve_cstring_array<S: AsRef<str>>( + slice: &[S], +) -> Result<(Vec<CString>, Vec<*const c_char>)> { + // Converts each incoming `str` to a `CString`, and then puts each `CString` pointer into a + // null terminated array, suitable for use as an argv or envp parameter to `execve`. + let mut vec_cstr = Vec::with_capacity(slice.len()); + let mut vec_cptr = Vec::with_capacity(slice.len() + 1); + for s in slice { + let cstr = + CString::new(s.as_ref()).map_err(|_| Error::StrToCString(s.as_ref().to_owned()))?; + + vec_cstr.push(cstr); + vec_cptr.push(vec_cstr.last().unwrap().as_ptr()); + } + + vec_cptr.push(null()); + + Ok((vec_cstr, vec_cptr)) +} + #[cfg(test)] mod tests { - use std::process::exit; - use super::*; + use std::fs::File; + + use libc::{FD_CLOEXEC, F_GETFD, F_SETFD}; + const SHELL: &str = "/bin/sh"; const EMPTY_STRING_SLICE: &[&str] = &[]; + fn clear_cloexec<A: AsRawFd>(fd_owner: &A) -> StdResult<(), io::Error> { + let fd = fd_owner.as_raw_fd(); + // Safe because fd is read only. + let flags = unsafe { libc::fcntl(fd, F_GETFD) }; + if flags == -1 { + return Err(io::Error::last_os_error()); + } + + let masked_flags = flags & !FD_CLOEXEC; + // Safe because this has no side effect(s) on the current process. + if masked_flags != flags && unsafe { libc::fcntl(fd, F_SETFD, masked_flags) } == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } + #[test] fn create_and_free() { unsafe { @@ -889,9 +1092,7 @@ mod tests { j.no_new_privs(); j.parse_seccomp_filters("src/test_filter.policy").unwrap(); j.use_seccomp_filter(); - if unsafe { j.fork(None).unwrap() } == 0 { - exit(0); - } + j.run("/bin/true", &[], &EMPTY_STRING_SLICE).unwrap(); } #[test] @@ -903,14 +1104,28 @@ mod tests { let j = Minijail::new().unwrap(); let first = libc::open(FILE_PATH.as_ptr() as *const c_char, libc::O_RDONLY); assert!(first >= 0); + // This appears unused but its function is to be a file descriptor that is closed + // inside run_remap after the fork. If it is not closed, the script will fail. let second = libc::open(FILE_PATH.as_ptr() as *const c_char, libc::O_RDONLY); assert!(second >= 0); - let fds: Vec<RawFd> = vec![0, 1, 2, first]; - if j.fork(Some(&fds)).unwrap() == 0 { - assert!(libc::close(second) < 0); // Should fail as second should be closed already. - assert_eq!(libc::close(first), 0); // Should succeed as first should be untouched. - exit(0); - } + + let fds: Vec<(RawFd, RawFd)> = vec![(first, 0), (1, 1), (2, 2)]; + j.run_remap( + SHELL, + &fds, + &[ + SHELL, + "-c", + r#" +if [ `ls -l /proc/self/fd/ | grep '/dev/null' | wc -l` != '1' ]; then + ls -l /proc/self/fd/ # Included to make debugging easier. + exit 1 +fi +"#, + ], + ) + .unwrap(); + j.wait().unwrap(); } } @@ -964,14 +1179,29 @@ mod tests { let j = Minijail::new().unwrap(); j.run("/bin/does not exist", &[1, 2], &EMPTY_STRING_SLICE) .unwrap(); - expect_result!(j.wait(), Err(Error::NoCommand)); + // TODO(b/194221986) Fix libminijail so that Error::NoAccess is not sometimes returned. + assert!(matches!( + j.wait(), + Err(Error::NoCommand) | Err(Error::NoAccess) + )); + } + + #[test] + fn runnable_fd_success() { + let bin_file = File::open("/bin/true").unwrap(); + // On Chrome OS targets /bin/true is actually a script, so drop CLOEXEC to prevent ENOENT. + clear_cloexec(&bin_file).unwrap(); + + let j = Minijail::new().unwrap(); + j.run_fd(&bin_file, &[1, 2], &EMPTY_STRING_SLICE).unwrap(); + expect_result!(j.wait(), Ok(())); } #[test] fn kill_success() { let j = Minijail::new().unwrap(); j.run( - Path::new("usr/bin/sleep"), + Path::new("/usr/bin/sleep"), &[1, 2], &["/usr/bin/sleep", "5"], ) @@ -985,9 +1215,7 @@ mod tests { fn chroot() { let mut j = Minijail::new().unwrap(); j.enter_chroot(".").unwrap(); - if unsafe { j.fork(None).unwrap() } == 0 { - exit(0); - } + j.run("/bin/true", &[], &EMPTY_STRING_SLICE).unwrap(); } #[test] @@ -995,9 +1223,7 @@ mod tests { fn namespace_vfs() { let mut j = Minijail::new().unwrap(); j.namespace_vfs(); - if unsafe { j.fork(None).unwrap() } == 0 { - exit(0); - } + j.run("/bin/true", &[], &EMPTY_STRING_SLICE).unwrap(); } #[test] |