| // 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 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}; |
| 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. |
| 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 { |
| 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 { |
| // 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), |
| /// Failed to wait the process. |
| Wait(i32), |
| } |
| |
| 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), |
| Wait(errno) => write!(f, "failed to wait: {}", io::Error::from_raw_os_error(*errno)), |
| } |
| } |
| } |
| |
| impl std::error::Error for Error {} |
| |
| pub type Result<T> = StdResult<T, Error>; |
| |
| /// 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<RawFd> = 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 < 0 { |
| return Err(Error::Wait(ret)); |
| } |
| 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!(format!("Unexpected returned value from wait: {}", ret)); |
| } |
| |
| impl Minijail { |
| /// Creates a new jail configuration. |
| pub fn new() -> Result<Minijail> { |
| 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 }) |
| } |
| |
| /// Clones self to a new `Minijail`. Useful because `fork` can only be called once on a |
| /// `Minijail`. |
| pub fn try_clone(&self) -> Result<Minijail> { |
| let jail_out = Minijail::new()?; |
| unsafe { |
| // Safe to clone one minijail to the other as minijail_clone doesn't modify the source |
| // jail(`self`) and leaves a valid minijail in the destination(`jail_out`). |
| let ret = minijail_copy_jail(self.jail, jail_out.jail); |
| if ret < 0 { |
| return Err(Error::ReturnCode(ret as u8)); |
| } |
| } |
| |
| Ok(jail_out) |
| } |
| |
| // 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() as size_t, 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<P: AsRef<Path>>(&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::<sock_filter>() != 0 { |
| return Err(Error::WrongProgramSize); |
| } |
| let count = buffer.len() / std::mem::size_of::<sock_filter>(); |
| if count > (!0 as u16) as usize { |
| return Err(Error::ProgramTooLarge); |
| } |
| if buffer.as_ptr() as usize % std::mem::align_of::<sock_filter>() != 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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P1: AsRef<Path>, P2: AsRef<Path>>( |
| &mut self, |
| src: P1, |
| dest: P2, |
| fstype: &str, |
| flags: usize, |
| ) -> Result<()> { |
| self.mount_with_data(src, dest, fstype, flags, "") |
| } |
| pub fn mount_with_data<P1: AsRef<Path>, P2: AsRef<Path>>( |
| &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 as size_t); |
| } |
| } |
| pub fn mount_bind<P1: AsRef<Path>, P2: AsRef<Path>>( |
| &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<P: AsRef<Path>, S: AsRef<str>>( |
| &self, |
| cmd: P, |
| inheritable_fds: &[RawFd], |
| args: &[S], |
| ) -> Result<pid_t> { |
| self.run_internal( |
| Command::new(Program::Filename(cmd.as_ref().to_path_buf())) |
| .keep_fds(inheritable_fds) |
| .args(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<P: AsRef<Path>, S: AsRef<str>>( |
| &self, |
| cmd: P, |
| inheritable_fds: &[(RawFd, RawFd)], |
| args: &[S], |
| ) -> Result<pid_t> { |
| self.run_internal( |
| Command::new(Program::Filename(cmd.as_ref().to_path_buf())) |
| .remap_fds(inheritable_fds) |
| .args(args)?, |
| ) |
| } |
| |
| /// 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)?, |
| ) |
| } |
| |
| /// 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)?, |
| ) |
| } |
| |
| 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)); |
| } |
| } |
| |
| 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 !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)); |
| } |
| } |
| } |
| |
| unsafe { |
| minijail_close_open_fds(self.jail); |
| } |
| |
| match cmd.program { |
| Program::Filename(ref path) => path.as_path().run_command(&self, &cmd), |
| Program::FileDescriptor(fd) => fd.run_command(&self, &cmd), |
| } |
| } |
| |
| /// 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<pid_t> { |
| 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<pid_t> { |
| 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<P: AsRef<Path>>(path: P) -> io::Result<usize> { |
| 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<bool> { |
| match count_dir_entries("/proc/self/task") { |
| Ok(1) => Ok(true), |
| Ok(_) => Ok(false), |
| Err(e) => Err(e), |
| } |
| } |
| |
| 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 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 { |
| 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 <seccomp_policy> |
| 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(); |
| j.run("/bin/true", &[], &EMPTY_STRING_SLICE).unwrap(); |
| } |
| |
| #[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); |
| // 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, 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(); |
| } |
| } |
| |
| 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(); |
| // 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"), |
| &[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(); |
| j.run("/bin/true", &[], &EMPTY_STRING_SLICE).unwrap(); |
| } |
| |
| #[test] |
| #[ignore] // privileged operation. |
| fn namespace_vfs() { |
| let mut j = Minijail::new().unwrap(); |
| j.namespace_vfs(); |
| j.run("/bin/true", &[], &EMPTY_STRING_SLICE).unwrap(); |
| } |
| |
| #[test] |
| fn run() { |
| let j = Minijail::new().unwrap(); |
| j.run("/bin/true", &[], &EMPTY_STRING_SLICE).unwrap(); |
| } |
| |
| #[test] |
| fn run_clone() { |
| let j = Minijail::new().unwrap(); |
| let b = j.try_clone().unwrap(); |
| // Pass the same FDs to both clones and make sure they don't conflict. |
| j.run("/bin/true", &[1, 2], &EMPTY_STRING_SLICE).unwrap(); |
| b.run("/bin/true", &[1, 2], &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(); |
| } |
| } |