// Copyright 2017 The Chromium OS Authors. All rights reserved. // 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; use std::io; 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}; #[derive(Debug)] pub enum Error { // minijail failed to accept bind mount. BindMount { errno: i32, src: PathBuf, dst: PathBuf, }, // minijail failed to accept mount. Mount { errno: i32, src: PathBuf, dest: PathBuf, fstype: String, flags: usize, data: String, }, /// Failure to count the number of threads in /proc/self/tasks. CheckingMultiThreaded(io::Error), /// minjail_new failed, this is an allocation failure. CreatingMinijail, /// minijail_fork failed with the given error code. ForkingMinijail(i32), /// Attempt to `fork` while already multithreaded. ForkingWhileMultiThreaded, /// The seccomp policy path doesn't exist. SeccompPath(PathBuf), /// The string passed in didn't parse to a valid CString. StrToCString(String), /// The path passed in didn't parse to a valid CString. PathToCString(PathBuf), /// Failed to call dup2 to set stdin, stdout, or stderr to /dev/null. DupDevNull(i32), /// Failed to set up /dev/null for FDs 0, 1, or 2. OpenDevNull(io::Error), /// Failed to read policy bpf from file. ReadProgram(io::Error), /// Setting the specified alt-syscall table failed with errno. Is the table in the kernel? SetAltSyscallTable { errno: i32, name: String }, /// Setting the specified rlimit failed with errno. SetRlimit { errno: i32, kind: libc::c_int }, /// chroot failed with the provided errno. SettingChrootDirectory(i32, PathBuf), /// pivot_root failed with the provided errno. SettingPivotRootDirectory(i32, PathBuf), /// There is an entry in /proc/self/fd that isn't a valid PID. ReadFdDirEntry(io::Error), /// /proc/self/fd failed to open. ReadFdDir(io::Error), /// An entry in /proc/self/fd is not an integer ProcFd(String), /// Minijail refused to preserve an FD in the inherit list of `fork()`. PreservingFd(i32), /// Program size is too large ProgramTooLarge, /// Alignment of file should be divisible by the alignment of sock_filter. WrongProgramAlignment, /// File size should be non-zero and a multiple of sock_filter WrongProgramSize, /// The command was not found. NoCommand, /// The command could not be run. NoAccess, /// Process was killed by SIGSYS indicating a seccomp violation. SeccompViolation(i32), /// Process was killed by a signal other than SIGSYS. Killed(u8), /// Process finished returning a non-zero code. ReturnCode(u8), } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Error::*; match self { BindMount { src, dst, errno } => write!( f, "failed to accept bind mount {} -> {}: {}", src.display(), dst.display(), io::Error::from_raw_os_error(*errno), ), Mount { errno, src, dest, fstype, flags, data, } => write!( f, "failed to accept mount {} -> {} of type {:?} with flags 0x{:x} \ and data {:?}: {}", src.display(), dest.display(), fstype, flags, data, io::Error::from_raw_os_error(*errno), ), CheckingMultiThreaded(e) => write!( f, "Failed to count the number of threads from /proc/self/tasks {}", e ), CreatingMinijail => write!(f, "minjail_new failed due to an allocation failure"), ForkingMinijail(e) => write!(f, "minijail_fork failed with error {}", e), ForkingWhileMultiThreaded => write!(f, "Attempt to call fork() while multithreaded"), SeccompPath(p) => write!(f, "missing seccomp policy path: {}", p.display()), StrToCString(s) => write!(f, "failed to convert string into CString: {}", s), PathToCString(s) => write!(f, "failed to convert path into CString: {}", s.display()), DupDevNull(errno) => write!( f, "failed to call dup2 to set stdin, stdout, or stderr to /dev/null: {}", io::Error::from_raw_os_error(*errno), ), OpenDevNull(e) => write!( f, "fail to open /dev/null for setting FDs 0, 1, or 2: {}", e, ), ReadProgram(e) => write!(f, "failed to read from bpf file: {}", e), SetAltSyscallTable { name, errno } => write!( f, "failed to set alt-syscall table {}: {}", name, io::Error::from_raw_os_error(*errno), ), SetRlimit { errno, kind } => write!(f, "failed to set rlimit {}: {}", kind, errno), SettingChrootDirectory(errno, p) => write!( f, "failed to set chroot {}: {}", p.display(), io::Error::from_raw_os_error(*errno), ), SettingPivotRootDirectory(errno, p) => write!( f, "failed to set pivot root {}: {}", p.display(), io::Error::from_raw_os_error(*errno), ), ReadFdDirEntry(e) => write!(f, "failed to read an entry in /proc/self/fd: {}", e), ReadFdDir(e) => write!(f, "failed to open /proc/self/fd: {}", e), ProcFd(s) => write!(f, "an entry in /proc/self/fd is not an integer: {}", s), PreservingFd(e) => write!(f, "fork failed in minijail_preserve_fd with error {}", e), ProgramTooLarge => write!(f, "bpf program is too large (max 64K instructions)"), WrongProgramAlignment => write!( f, "the alignment of bpf file was not a multiple of that of sock_filter" ), WrongProgramSize => write!(f, "bpf file was empty or not a multiple of sock_filter"), NoCommand => write!(f, "command was not found"), NoAccess => write!(f, "unable to execute command"), 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), } } } impl std::error::Error for Error {} pub type Result = std::result::Result; /// Configuration to jail a process based on wrapping libminijail. /// /// Intentionally leave out everything related to `minijail_run`. Forking is /// hard to reason about w.r.t. memory and resource safety. It is better to avoid /// forking from rust code. Leave forking to the library user, who can make /// an informed decision about when to fork to minimize risk. /// # Examples /// * Load seccomp policy - like "minijail0 -n -S myfilter.policy" /// /// ``` /// # use minijail::Minijail; /// # fn seccomp_filter_test() -> Result<(), ()> { /// let mut j = Minijail::new().map_err(|_| ())?; /// j.no_new_privs(); /// j.parse_seccomp_filters("my_filter.policy").map_err(|_| ())?; /// j.use_seccomp_filter(); /// unsafe { // `fork` will close all the programs FDs. /// j.fork(None).map_err(|_| ())?; /// } /// # Ok(()) /// # } /// ``` /// /// * Keep stdin, stdout, and stderr open after jailing. /// /// ``` /// # use minijail::Minijail; /// # use std::os::unix::io::RawFd; /// # fn seccomp_filter_test() -> Result<(), ()> { /// let j = Minijail::new().map_err(|_| ())?; /// let preserve_fds: Vec = vec![0, 1, 2]; /// unsafe { // `fork` will close all the programs FDs. /// j.fork(Some(&preserve_fds)).map_err(|_| ())?; /// } /// # Ok(()) /// # } /// ``` /// # Errors /// The `fork` function might not return an error if it fails after forking. A /// partial jail is not recoverable and will instead result in killing the /// process. pub struct Minijail { jail: *mut minijail, } #[link(name = "c")] extern "C" { fn __libc_current_sigrtmax() -> libc::c_int; } fn translate_wait_error(ret: libc::c_int) -> Result<()> { if ret == 0 { return Ok(()); } if ret == MINIJAIL_ERR_NO_COMMAND as libc::c_int { return Err(Error::NoCommand); } if ret == MINIJAIL_ERR_NO_ACCESS as libc::c_int { return Err(Error::NoAccess); } let sig_base: libc::c_int = MINIJAIL_ERR_SIG_BASE as libc::c_int; let sig_max_code: libc::c_int = unsafe { __libc_current_sigrtmax() } + sig_base; if ret > sig_base && ret <= sig_max_code { return Err(Error::Killed( (ret - MINIJAIL_ERR_SIG_BASE as libc::c_int) as u8, )); } if ret > 0 && ret <= 0xff { return Err(Error::ReturnCode(ret as u8)); } unreachable!(); } impl Minijail { /// Creates a new jail configuration. pub fn new() -> Result { let j = unsafe { // libminijail actually owns the minijail structure. It will live until we call // minijail_destroy. minijail_new() }; if j.is_null() { return Err(Error::CreatingMinijail); } Ok(Minijail { jail: j }) } // The following functions are safe because they only set values in the // struct already owned by minijail. The struct's lifetime is tied to // `struct Minijail` so it is guaranteed to be valid pub fn change_uid(&mut self, uid: libc::uid_t) { unsafe { minijail_change_uid(self.jail, uid); } } pub fn change_gid(&mut self, gid: libc::gid_t) { unsafe { minijail_change_gid(self.jail, gid); } } pub fn change_user(&mut self, user: &str) -> Result<()> { let user_cstring = CString::new(user).map_err(|_| Error::StrToCString(user.to_owned()))?; unsafe { minijail_change_user(self.jail, user_cstring.as_ptr()); } Ok(()) } pub fn change_group(&mut self, group: &str) -> Result<()> { let group_cstring = CString::new(group).map_err(|_| Error::StrToCString(group.to_owned()))?; unsafe { minijail_change_group(self.jail, group_cstring.as_ptr()); } Ok(()) } pub fn set_supplementary_gids(&mut self, ids: &[libc::gid_t]) { unsafe { minijail_set_supplementary_gids(self.jail, ids.len(), ids.as_ptr()); } } pub fn keep_supplementary_gids(&mut self) { unsafe { minijail_keep_supplementary_gids(self.jail); } } // rlim_t is defined in minijail-sys to be u64 on all platforms, to avoid // issues on 32-bit platforms. It's also useful to us here to avoid // libc::rlim64_t, which is not defined at all on Android. pub fn set_rlimit(&mut self, kind: libc::c_int, cur: rlim_t, max: rlim_t) -> Result<()> { let errno = unsafe { minijail_rlimit(self.jail, kind, cur, max) }; if errno == 0 { Ok(()) } else { Err(Error::SetRlimit { errno, kind }) } } pub fn use_seccomp(&mut self) { unsafe { minijail_use_seccomp(self.jail); } } pub fn no_new_privs(&mut self) { unsafe { minijail_no_new_privs(self.jail); } } pub fn use_seccomp_filter(&mut self) { unsafe { minijail_use_seccomp_filter(self.jail); } } pub fn set_seccomp_filter_tsync(&mut self) { unsafe { minijail_set_seccomp_filter_tsync(self.jail); } } pub fn parse_seccomp_program>(&mut self, path: P) -> Result<()> { if !path.as_ref().is_file() { return Err(Error::SeccompPath(path.as_ref().to_owned())); } let buffer = fs::read(path).map_err(Error::ReadProgram)?; if buffer.len() % std::mem::size_of::() != 0 { return Err(Error::WrongProgramSize); } let count = buffer.len() / std::mem::size_of::(); if count > (!0 as u16) as usize { return Err(Error::ProgramTooLarge); } if buffer.as_ptr() as usize % std::mem::align_of::() != 0 { return Err(Error::WrongProgramAlignment); } // Safe cast because we checked that the buffer address is divisible by the alignment of // sock_filter. #[allow(clippy::cast_ptr_alignment)] let header = sock_fprog { len: count as c_ushort, filter: buffer.as_ptr() as *mut sock_filter, }; unsafe { minijail_set_seccomp_filters(self.jail, &header); } Ok(()) } pub fn parse_seccomp_filters>(&mut self, path: P) -> Result<()> { if !path.as_ref().is_file() { return Err(Error::SeccompPath(path.as_ref().to_owned())); } let pathstring = path .as_ref() .as_os_str() .to_str() .ok_or_else(|| Error::PathToCString(path.as_ref().to_owned()))?; let filename = CString::new(pathstring).map_err(|_| Error::PathToCString(path.as_ref().to_owned()))?; unsafe { minijail_parse_seccomp_filters(self.jail, filename.as_ptr()); } Ok(()) } pub fn log_seccomp_filter_failures(&mut self) { unsafe { minijail_log_seccomp_filter_failures(self.jail); } } pub fn use_caps(&mut self, capmask: u64) { unsafe { minijail_use_caps(self.jail, capmask); } } pub fn capbset_drop(&mut self, capmask: u64) { unsafe { minijail_capbset_drop(self.jail, capmask); } } pub fn set_ambient_caps(&mut self) { unsafe { minijail_set_ambient_caps(self.jail); } } pub fn reset_signal_mask(&mut self) { unsafe { minijail_reset_signal_mask(self.jail); } } pub fn run_as_init(&mut self) { unsafe { minijail_run_as_init(self.jail); } } pub fn namespace_pids(&mut self) { unsafe { minijail_namespace_pids(self.jail); } } pub fn namespace_user(&mut self) { unsafe { minijail_namespace_user(self.jail); } } pub fn namespace_user_disable_setgroups(&mut self) { unsafe { minijail_namespace_user_disable_setgroups(self.jail); } } pub fn namespace_vfs(&mut self) { unsafe { minijail_namespace_vfs(self.jail); } } pub fn new_session_keyring(&mut self) { unsafe { minijail_new_session_keyring(self.jail); } } pub fn skip_remount_private(&mut self) { unsafe { minijail_skip_remount_private(self.jail); } } pub fn namespace_ipc(&mut self) { unsafe { minijail_namespace_ipc(self.jail); } } pub fn namespace_net(&mut self) { unsafe { minijail_namespace_net(self.jail); } } pub fn namespace_cgroups(&mut self) { unsafe { minijail_namespace_cgroups(self.jail); } } pub fn remount_proc_readonly(&mut self) { unsafe { minijail_remount_proc_readonly(self.jail); } } pub fn set_remount_mode(&mut self, mode: c_ulong) { unsafe { minijail_remount_mode(self.jail, mode) } } pub fn uidmap(&mut self, uid_map: &str) -> Result<()> { let map_cstring = CString::new(uid_map).map_err(|_| Error::StrToCString(uid_map.to_owned()))?; unsafe { minijail_uidmap(self.jail, map_cstring.as_ptr()); } Ok(()) } pub fn gidmap(&mut self, gid_map: &str) -> Result<()> { let map_cstring = CString::new(gid_map).map_err(|_| Error::StrToCString(gid_map.to_owned()))?; unsafe { minijail_gidmap(self.jail, map_cstring.as_ptr()); } Ok(()) } pub fn inherit_usergroups(&mut self) { unsafe { minijail_inherit_usergroups(self.jail); } } pub fn use_alt_syscall(&mut self, table_name: &str) -> Result<()> { let table_name_string = CString::new(table_name).map_err(|_| Error::StrToCString(table_name.to_owned()))?; let ret = unsafe { minijail_use_alt_syscall(self.jail, table_name_string.as_ptr()) }; if ret < 0 { return Err(Error::SetAltSyscallTable { errno: ret, name: table_name.to_owned(), }); } Ok(()) } pub fn enter_chroot>(&mut self, dir: P) -> Result<()> { let pathstring = dir .as_ref() .as_os_str() .to_str() .ok_or_else(|| Error::PathToCString(dir.as_ref().to_owned()))?; let dirname = CString::new(pathstring).map_err(|_| Error::PathToCString(dir.as_ref().to_owned()))?; let ret = unsafe { minijail_enter_chroot(self.jail, dirname.as_ptr()) }; if ret < 0 { return Err(Error::SettingChrootDirectory(ret, dir.as_ref().to_owned())); } Ok(()) } pub fn enter_pivot_root>(&mut self, dir: P) -> Result<()> { let pathstring = dir .as_ref() .as_os_str() .to_str() .ok_or_else(|| Error::PathToCString(dir.as_ref().to_owned()))?; let dirname = CString::new(pathstring).map_err(|_| Error::PathToCString(dir.as_ref().to_owned()))?; let ret = unsafe { minijail_enter_pivot_root(self.jail, dirname.as_ptr()) }; if ret < 0 { return Err(Error::SettingPivotRootDirectory( ret, dir.as_ref().to_owned(), )); } Ok(()) } pub fn mount, P2: AsRef>( &mut self, src: P1, dest: P2, fstype: &str, flags: usize, ) -> Result<()> { self.mount_with_data(src, dest, fstype, flags, "") } pub fn mount_with_data, P2: AsRef>( &mut self, src: P1, dest: P2, fstype: &str, flags: usize, data: &str, ) -> Result<()> { let src_os = src .as_ref() .as_os_str() .to_str() .ok_or_else(|| Error::PathToCString(src.as_ref().to_owned()))?; let src_path = CString::new(src_os).map_err(|_| Error::StrToCString(src_os.to_owned()))?; let dest_os = dest .as_ref() .as_os_str() .to_str() .ok_or_else(|| Error::PathToCString(dest.as_ref().to_owned()))?; let dest_path = CString::new(dest_os).map_err(|_| Error::StrToCString(dest_os.to_owned()))?; let fstype_string = CString::new(fstype).map_err(|_| Error::StrToCString(fstype.to_owned()))?; let data_string = CString::new(data).map_err(|_| Error::StrToCString(data.to_owned()))?; let ret = unsafe { minijail_mount_with_data( self.jail, src_path.as_ptr(), dest_path.as_ptr(), fstype_string.as_ptr(), flags as _, data_string.as_ptr(), ) }; if ret < 0 { return Err(Error::Mount { errno: ret, src: src.as_ref().to_owned(), dest: dest.as_ref().to_owned(), fstype: fstype.to_owned(), flags, data: data.to_owned(), }); } Ok(()) } pub fn mount_dev(&mut self) { unsafe { minijail_mount_dev(self.jail); } } pub fn mount_tmp(&mut self) { unsafe { minijail_mount_tmp(self.jail); } } pub fn mount_tmp_size(&mut self, size: usize) { unsafe { minijail_mount_tmp_size(self.jail, size); } } pub fn mount_bind, P2: AsRef>( &mut self, src: P1, dest: P2, writable: bool, ) -> Result<()> { let src_os = src .as_ref() .as_os_str() .to_str() .ok_or_else(|| Error::PathToCString(src.as_ref().to_owned()))?; let src_path = CString::new(src_os).map_err(|_| Error::StrToCString(src_os.to_owned()))?; let dest_os = dest .as_ref() .as_os_str() .to_str() .ok_or_else(|| Error::PathToCString(dest.as_ref().to_owned()))?; let dest_path = CString::new(dest_os).map_err(|_| Error::StrToCString(dest_os.to_owned()))?; let ret = unsafe { minijail_bind( self.jail, src_path.as_ptr(), dest_path.as_ptr(), writable as _, ) }; if ret < 0 { return Err(Error::BindMount { errno: ret, src: src.as_ref().to_owned(), dst: dest.as_ref().to_owned(), }); } Ok(()) } /// Forks and execs a child and puts it in the previously configured minijail. /// FDs 0, 1, and 2 are overwritten with /dev/null FDs unless they are included in the /// inheritable_fds list. This function may abort in the child on error because a partially /// entered jail isn't recoverable. pub fn run, S: AsRef>( &self, cmd: P, inheritable_fds: &[RawFd], args: &[S], ) -> Result { self.run_remap( cmd, &inheritable_fds .iter() .map(|&a| (a, a)) .collect::>(), args, ) } /// Behaves the same as `run()` except `inheritable_fds` is a list of fd /// mappings rather than just a list of fds to preserve. pub fn run_remap, S: AsRef>( &self, cmd: P, inheritable_fds: &[(RawFd, RawFd)], args: &[S], ) -> Result { 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()))?; // 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()); for (src_fd, dst_fd) in inheritable_fds { let ret = unsafe { minijail_preserve_fd(self.jail, *src_fd, *dst_fd) }; if ret < 0 { return Err(Error::PreservingFd(ret)); } } let dev_null = fs::OpenOptions::new() .read(true) .write(true) .open("/dev/null") .map_err(Error::OpenDevNull)?; // 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) { let ret = unsafe { minijail_preserve_fd(self.jail, dev_null.as_raw_fd(), *io_fd) }; if ret < 0 { return Err(Error::PreservingFd(ret)); } } } unsafe { 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)); } Ok(pid) } /// Forks a child and puts it in the previously configured minijail. /// /// # Safety /// `fork` is unsafe because it closes all open FD for this process. That /// could cause a lot of trouble if not handled carefully. FDs 0, 1, and 2 /// are overwritten with /dev/null FDs unless they are included in the /// inheritable_fds list. /// /// Also, any Rust objects that own fds may try to close them after the fork. If they belong /// to a fd number that was mapped to, the mapped fd will be closed instead. /// /// This Function may abort in the child on error because a partially /// entered jail isn't recoverable. pub unsafe fn fork(&self, inheritable_fds: Option<&[RawFd]>) -> Result { let m: Vec<(RawFd, RawFd)> = inheritable_fds .unwrap_or(&[]) .iter() .map(|&a| (a, a)) .collect(); self.fork_remap(&m) } /// Behaves the same as `fork()` except `inheritable_fds` is a list of fd /// mappings rather than just a list of fds to preserve. /// /// # Safety /// See `fork`. pub unsafe fn fork_remap(&self, inheritable_fds: &[(RawFd, RawFd)]) -> Result { if !is_single_threaded().map_err(Error::CheckingMultiThreaded)? { // This test will fail during `cargo test` because the test harness always spawns a test // thread. We will make an exception for that case because the tests for this module // should always be run in a serial fashion using `--test-threads=1`. #[cfg(not(test))] return Err(Error::ForkingWhileMultiThreaded); } for (src_fd, dst_fd) in inheritable_fds { let ret = minijail_preserve_fd(self.jail, *src_fd, *dst_fd); if ret < 0 { return Err(Error::PreservingFd(ret)); } } let dev_null = fs::OpenOptions::new() .read(true) .write(true) .open("/dev/null") .map_err(Error::OpenDevNull)?; // 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) { let ret = minijail_preserve_fd(self.jail, dev_null.as_raw_fd(), *io_fd); if ret < 0 { return Err(Error::PreservingFd(ret)); } } } minijail_close_open_fds(self.jail); let ret = minijail_fork(self.jail); if ret < 0 { return Err(Error::ForkingMinijail(ret)); } if ret == 0 { // Safe because dev_null was remapped. dev_null.into_raw_fd(); } Ok(ret as pid_t) } pub fn wait(&self) -> Result<()> { let ret: libc::c_int; // This is safe because it does not modify the struct. unsafe { ret = minijail_wait(self.jail); } translate_wait_error(ret) } /// Send a SIGTERM to the child process and wait for its return code. pub fn kill(&self) -> Result<()> { let ret = unsafe { // The kill does not change any internal state. minijail_kill(self.jail) }; // minijail_kill waits for the process, so also translate the returned wait error. translate_wait_error(ret) } } impl Drop for Minijail { /// Frees the Minijail created in Minijail::new. fn drop(&mut self) { unsafe { // Destroys the minijail's memory. It is safe to do here because all references to // this object have been dropped. minijail_destroy(self.jail); } } } // Count the number of files in the directory specified by `path`. fn count_dir_entries>(path: P) -> io::Result { Ok(fs::read_dir(path)?.count()) } // Return true if the current thread is the only thread in the process. fn is_single_threaded() -> io::Result { match count_dir_entries("/proc/self/task") { Ok(1) => Ok(true), Ok(_) => Ok(false), Err(e) => Err(e), } } #[cfg(test)] mod tests { use std::process::exit; use super::*; const SHELL: &str = "/bin/sh"; const EMPTY_STRING_SLICE: &[&str] = &[]; #[test] fn create_and_free() { unsafe { let j = minijail_new(); assert_ne!(std::ptr::null_mut(), j); minijail_destroy(j); } let j = Minijail::new().unwrap(); drop(j); } #[test] // Test that setting a seccomp filter with no-new-privs works as non-root. // This is equivalent to minijail0 -n -S fn seccomp_no_new_privs() { let mut j = Minijail::new().unwrap(); 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); } } #[test] // Test that open FDs get closed and that FDs in the inherit list are left open. fn close_fds() { unsafe { // Using libc to open/close FDs for testing. const FILE_PATH: &[u8] = b"/dev/null\0"; let j = Minijail::new().unwrap(); let first = libc::open(FILE_PATH.as_ptr() as *const c_char, libc::O_RDONLY); assert!(first >= 0); let second = libc::open(FILE_PATH.as_ptr() as *const c_char, libc::O_RDONLY); assert!(second >= 0); let fds: Vec = 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); } } } macro_rules! expect_result { ($call:expr, $expected:pat) => { let got = $call; match got { $expected => {} _ => { panic!("got {:?} expected {:?}", got, stringify!($expected)); } } }; } #[test] fn wait_success() { let j = Minijail::new().unwrap(); j.run("/bin/true", &[1, 2], &EMPTY_STRING_SLICE).unwrap(); expect_result!(j.wait(), Ok(())); } #[test] fn wait_killed() { let j = Minijail::new().unwrap(); j.run( SHELL, &[1, 2], &[SHELL, "-c", "kill -9 $$ &\n/usr/bin/sleep 5"], ) .unwrap(); expect_result!(j.wait(), Err(Error::Killed(9))); } #[test] fn wait_returncode() { let j = Minijail::new().unwrap(); j.run("/bin/false", &[1, 2], &EMPTY_STRING_SLICE).unwrap(); expect_result!(j.wait(), Err(Error::ReturnCode(1))); } #[test] fn wait_noaccess() { let j = Minijail::new().unwrap(); j.run("/dev/null", &[1, 2], &EMPTY_STRING_SLICE).unwrap(); expect_result!(j.wait(), Err(Error::NoAccess)); } #[test] fn wait_nocommand() { let j = Minijail::new().unwrap(); j.run("/bin/does not exist", &[1, 2], &EMPTY_STRING_SLICE) .unwrap(); expect_result!(j.wait(), Err(Error::NoCommand)); } #[test] fn kill_success() { let j = Minijail::new().unwrap(); j.run( Path::new("usr/bin/sleep"), &[1, 2], &["/usr/bin/sleep", "5"], ) .unwrap(); const EXPECTED_SIGNAL: u8 = libc::SIGTERM as u8; expect_result!(j.kill(), Err(Error::Killed(EXPECTED_SIGNAL))); } #[test] #[ignore] // privileged operation. fn chroot() { let mut j = Minijail::new().unwrap(); j.enter_chroot(".").unwrap(); if unsafe { j.fork(None).unwrap() } == 0 { exit(0); } } #[test] #[ignore] // privileged operation. fn namespace_vfs() { let mut j = Minijail::new().unwrap(); j.namespace_vfs(); if unsafe { j.fork(None).unwrap() } == 0 { exit(0); } } #[test] fn run() { let j = Minijail::new().unwrap(); j.run("/bin/true", &[], &EMPTY_STRING_SLICE).unwrap(); } #[test] fn run_string_vec() { let j = Minijail::new().unwrap(); let args = vec!["ignored".to_string()]; j.run(Path::new("/bin/true"), &[], &args).unwrap(); } }