summary refs log tree commit diff
path: root/io_jail
diff options
context:
space:
mode:
authorDylan Reid <dgreid@chromium.org>2017-05-09 16:16:24 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-05-25 12:25:27 -0700
commitd6c579fcef98a4de7221a814dfece1c4f7430c71 (patch)
tree5955e3134f8c69a6cd7e6a123d41345c8bb1a5e2 /io_jail
parent9195ec9b239f4fd70c64531e6c4fa00774c49c5f (diff)
downloadcrosvm-d6c579fcef98a4de7221a814dfece1c4f7430c71.tar
crosvm-d6c579fcef98a4de7221a814dfece1c4f7430c71.tar.gz
crosvm-d6c579fcef98a4de7221a814dfece1c4f7430c71.tar.bz2
crosvm-d6c579fcef98a4de7221a814dfece1c4f7430c71.tar.lz
crosvm-d6c579fcef98a4de7221a814dfece1c4f7430c71.tar.xz
crosvm-d6c579fcef98a4de7221a814dfece1c4f7430c71.tar.zst
crosvm-d6c579fcef98a4de7221a814dfece1c4f7430c71.zip
io_jail: Add a wrapper around minijail
The io_jail is used to jail io processes run from crosvm. Under the hood
it mostly configures a minijail jail to run in.  The minijail
restrictions are applied when iojail::enter is called.  This closes
extra FDs and calls minijail_enter.

The minijail_run* functions are left out as we don't have a need to exec
foreign programs.  libminijail will be used to jail separate processes
spawned from the main crosvm process.

The ability to close all open file descriptors is added.  Minijail only
closes FDs after forking and before exec.

Change-Id: Ida7f52022c934e9e6edeb7b604cd6e6399860cb9
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/505100
Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'io_jail')
-rw-r--r--io_jail/Cargo.toml7
-rw-r--r--io_jail/src/lib.rs324
-rw-r--r--io_jail/src/libminijail.rs138
-rw-r--r--io_jail/src/test_filter.policy6
4 files changed, 475 insertions, 0 deletions
diff --git a/io_jail/Cargo.toml b/io_jail/Cargo.toml
new file mode 100644
index 0000000..b09b0cf
--- /dev/null
+++ b/io_jail/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "io_jail"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+
+[dependencies]
+libc = "*"
diff --git a/io_jail/src/lib.rs b/io_jail/src/lib.rs
new file mode 100644
index 0000000..0c9af8b
--- /dev/null
+++ b/io_jail/src/lib.rs
@@ -0,0 +1,324 @@
+// 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.
+
+extern crate libc;
+
+#[allow(dead_code)]
+#[allow(non_camel_case_types)]
+#[allow(non_snake_case)]
+#[allow(non_upper_case_globals)]
+mod libminijail;
+
+use std::ffi::CString;
+use std::fs;
+use std::os::unix::io::RawFd;
+use std::path::Path;
+use std::str::FromStr;
+
+#[derive(Clone, Copy, Debug)]
+pub enum Error {
+    /// minjail_new failed, this is an allocation failure.
+    CreatingMinijail,
+    /// The path or name string passed in didn't parse to a valid CString.
+    InvalidCString,
+    /// Setting the specified alt-syscall table failed with errno. Is the table in the kernel?
+    SetAltSyscallTable(i32),
+    /// chroot failed with the provided errno.
+    SettingChrootDirectory(i32),
+    /// pivot_root failed with the provided errno.
+    SettingPivotRootDirectory(i32),
+    /// There is an entry in /proc/self/fd that isn't a valid PID.
+    ReadFdDirEntry,
+    /// /proc/self/fd failed to open.
+    ReadFdDir,
+}
+pub type Result<T> = std::result::Result<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 std::path::Path;
+/// # use io_jail::Minijail;
+/// # fn seccomp_filter_test() -> Result<(), ()> {
+///       let mut j = Minijail::new().map_err(|_| ())?;
+///       j.no_new_privs();
+///       j.parse_seccomp_filters(Path::new("my_filter.policy")).map_err(|_| ())?;
+///       j.use_seccomp_filter();
+///       unsafe { // Enter will close all the programs FDs.
+///           j.enter(None).map_err(|_| ())?;
+///       }
+/// #     Ok(())
+/// # }
+/// ```
+///
+/// * Keep stdin, stdout, and stderr open after jailing.
+///
+/// ```
+/// # use io_jail::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 { // Enter will close all the programs FDs.
+///           j.enter(Some(&preserve_fds)).map_err(|_| ())?;
+///       }
+/// #     Ok(())
+/// # }
+/// ```
+/// # Errors
+/// The `enter` function doesn't return an error. Instead, It kills the current
+/// process on error.
+pub struct Minijail {
+    jail: *mut libminijail::minijail,
+}
+
+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.
+            libminijail::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 { libminijail::minijail_change_uid(self.jail, uid); }
+    }
+    pub fn change_gid(&mut self, gid: libc::gid_t) {
+        unsafe { libminijail::minijail_change_gid(self.jail, gid); }
+    }
+    pub fn set_supplementary_gids(&mut self, ids: &[libc::gid_t]) {
+        unsafe { libminijail::minijail_set_supplementary_gids(self.jail, ids.len(), ids.as_ptr()); }
+    }
+    pub fn keep_supplementary_gids(&mut self) {
+        unsafe { libminijail::minijail_keep_supplementary_gids(self.jail); }
+    }
+    pub fn use_seccomp(&mut self) {
+        unsafe { libminijail::minijail_use_seccomp(self.jail); }
+    }
+    pub fn no_new_privs(&mut self) {
+        unsafe { libminijail::minijail_no_new_privs(self.jail); }
+    }
+    pub fn use_seccomp_filter(&mut self) {
+        unsafe { libminijail::minijail_use_seccomp_filter(self.jail); }
+    }
+    pub fn set_seccomp_filter_tsync(&mut self) {
+        unsafe { libminijail::minijail_set_seccomp_filter_tsync(self.jail); }
+    }
+    pub fn parse_seccomp_filters(&mut self, path: &Path) -> Result<()> {
+        let pathstring = path.as_os_str().to_str().ok_or(Error::InvalidCString)?;
+        let filename = CString::new(pathstring).map_err(|_| Error::InvalidCString)?;
+        unsafe {
+            libminijail::minijail_parse_seccomp_filters(self.jail, filename.as_ptr());
+        }
+        Ok(())
+    }
+    pub fn log_seccomp_filter_failures(&mut self) {
+        unsafe { libminijail::minijail_log_seccomp_filter_failures(self.jail); }
+    }
+    pub fn use_caps(&mut self, capmask: u64) {
+        unsafe { libminijail::minijail_use_caps(self.jail, capmask); }
+    }
+    pub fn capbset_drop(&mut self, capmask: u64) {
+        unsafe { libminijail::minijail_capbset_drop(self.jail, capmask); }
+    }
+    pub fn set_ambient_caps(&mut self) {
+        unsafe { libminijail::minijail_set_ambient_caps(self.jail); }
+    }
+    pub fn reset_signal_mask(&mut self) {
+        unsafe { libminijail::minijail_reset_signal_mask(self.jail); }
+    }
+    pub fn namespace_vfs(&mut self) {
+        unsafe { libminijail::minijail_namespace_vfs(self.jail); }
+    }
+    pub fn new_session_keyring(&mut self) {
+        unsafe { libminijail::minijail_new_session_keyring(self.jail); }
+    }
+    pub fn skip_remount_private(&mut self) {
+        unsafe { libminijail::minijail_skip_remount_private(self.jail); }
+    }
+    pub fn namespace_ipc(&mut self) {
+        unsafe { libminijail::minijail_namespace_ipc(self.jail); }
+    }
+    pub fn namespace_net(&mut self) {
+        unsafe { libminijail::minijail_namespace_net(self.jail); }
+    }
+    pub fn namespace_cgroups(&mut self) {
+        unsafe { libminijail::minijail_namespace_cgroups(self.jail); }
+    }
+    pub fn remount_proc_readonly(&mut self) {
+        unsafe { libminijail::minijail_remount_proc_readonly(self.jail); }
+    }
+    pub fn inherit_usergroups(&mut self) {
+        unsafe { libminijail::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::InvalidCString)?;
+        let ret = unsafe {
+            libminijail::minijail_use_alt_syscall(self.jail, table_name_string.as_ptr())
+        };
+        if ret < 0 {
+            return Err(Error::SetAltSyscallTable(ret));
+        }
+        Ok(())
+    }
+    pub fn enter_chroot(&mut self, dir: &Path) -> Result<()> {
+        let pathstring = dir.as_os_str().to_str().ok_or(Error::InvalidCString)?;
+        let dirname = CString::new(pathstring).map_err(|_| Error::InvalidCString)?;
+        let ret = unsafe { libminijail::minijail_enter_chroot(self.jail, dirname.as_ptr()) };
+        if ret < 0 {
+            return Err(Error::SettingChrootDirectory(ret));
+        }
+        Ok(())
+    }
+    pub fn enter_pivot_root(&mut self, dir: &Path) -> Result<()> {
+        let pathstring = dir.as_os_str().to_str().ok_or(Error::InvalidCString)?;
+        let dirname = CString::new(pathstring).map_err(|_| Error::InvalidCString)?;
+        let ret = unsafe { libminijail::minijail_enter_pivot_root(self.jail, dirname.as_ptr()) };
+        if ret < 0 {
+            return Err(Error::SettingPivotRootDirectory(ret));
+        }
+        Ok(())
+    }
+    pub fn mount_tmp(&mut self) {
+        unsafe { libminijail::minijail_mount_tmp(self.jail); }
+    }
+    pub fn mount_tmp_size(&mut self, size: usize) {
+        unsafe { libminijail::minijail_mount_tmp_size(self.jail, size); }
+    }
+
+    /// Enters the previously configured minijail.
+    /// `enter` is unsafe because it closes all open FD for this process.  That
+    /// could cause a lot of trouble if not handled carefully.
+    /// This Function aborts on error because a partially entered jail isn't
+    /// recoverable.
+    pub unsafe fn enter(&self, inheritable_fds: Option<&[RawFd]>) -> Result<()> {
+        if let Some(keep_fds) = inheritable_fds {
+            self.close_open_fds(keep_fds)?;
+        }
+        libminijail::minijail_enter(self.jail);
+        Ok(())
+    }
+
+    // Closing all open FDs could be unsafe if something is relying on an FD
+    // that is closed unexpectedly.  It is safe as long as it is called
+    // immediately after forking and all needed FDs are in `inheritable_fds`.
+    unsafe fn close_open_fds(&self, inheritable_fds: &[RawFd]) -> Result<()> {
+        const FD_PATH: &'static str = "/proc/self/fd";
+        let mut fds_to_close: Vec<RawFd> = Vec::new();
+        for entry in fs::read_dir(FD_PATH).map_err(|_| Error::ReadFdDir)? {
+            let dir_entry = entry.map_err(|_| Error::ReadFdDirEntry)?;
+            let name_path = dir_entry.path();
+            let name = name_path.strip_prefix(FD_PATH)
+                    .map_err(|_| Error::InvalidCString)?;
+            let name_str = name.to_str().ok_or(Error::InvalidCString)?;
+            let fd = <i32>::from_str(name_str).map_err(|_| Error::InvalidCString)?;
+            if !inheritable_fds.contains(&fd) {
+                fds_to_close.push(fd);
+            }
+        }
+        for fd in fds_to_close {
+            // Note that this will also close the DIR fd used to read the
+            // directory, that FD was already closed.  Closing it again will
+            // return an error but won't break anything.
+            libc::close(fd);
+        }
+        Ok(())
+    }
+}
+
+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.
+            libminijail::minijail_destroy(self.jail);
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn create_and_free() {
+        unsafe {
+            let j = libminijail::minijail_new();
+            assert_ne!(std::ptr::null_mut(), j);
+            libminijail::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(Path::new("src/test_filter.policy")).unwrap();
+        j.use_seccomp_filter();
+        unsafe {
+            j.enter(None).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: &'static str = "/dev/null";
+            let j = Minijail::new().unwrap();
+            let first = libc::open(FILE_PATH.as_ptr() as *const i8, libc::O_RDONLY);
+            assert!(first >= 0);
+            let second = libc::open(FILE_PATH.as_ptr() as *const i8, libc::O_RDONLY);
+            assert!(second >= 0);
+            let fds: Vec<RawFd> = vec![0, 1, 2, first];
+            j.enter(Some(&fds)).unwrap();
+            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.
+        }
+    }
+
+    #[test]
+    #[ignore] // privileged operation.
+    fn chroot() {
+        let mut j = Minijail::new().unwrap();
+        j.enter_chroot(Path::new(".")).unwrap();
+        unsafe {
+            j.enter(None).unwrap();
+        }
+    }
+
+    #[test]
+    #[ignore] // privileged operation.
+    fn namespace_vfs() {
+        let mut j = Minijail::new().unwrap();
+        j.namespace_vfs();
+        unsafe {
+            j.enter(None).unwrap();
+        }
+    }
+}
diff --git a/io_jail/src/libminijail.rs b/io_jail/src/libminijail.rs
new file mode 100644
index 0000000..803a4df
--- /dev/null
+++ b/io_jail/src/libminijail.rs
@@ -0,0 +1,138 @@
+// 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.
+
+extern crate libc;
+
+use libc::{gid_t, pid_t, uid_t};
+use std::os::raw::{c_char, c_int, c_ulong};
+
+/// Struct minijail is an opaque type inside libminijail.
+/// See the minijail man page for a description of funcitons.
+#[derive(Debug, Copy, Clone)]
+pub enum minijail {}
+
+#[link(name = "minijail")]
+extern "C" {
+    pub fn minijail_new() -> *mut minijail;
+    pub fn minijail_change_uid(j: *mut minijail, uid: uid_t);
+    pub fn minijail_change_gid(j: *mut minijail, gid: gid_t);
+    pub fn minijail_set_supplementary_gids(j: *mut minijail,
+                                           size: usize,
+                                           list: *const gid_t);
+    pub fn minijail_keep_supplementary_gids(j: *mut minijail);
+    pub fn minijail_change_user(j: *mut minijail,
+                                user: *const c_char)
+                                -> c_int;
+    pub fn minijail_change_group(j: *mut minijail,
+                                 group: *const c_char)
+                                 -> c_int;
+    pub fn minijail_use_seccomp(j: *mut minijail);
+    pub fn minijail_no_new_privs(j: *mut minijail);
+    pub fn minijail_use_seccomp_filter(j: *mut minijail);
+    pub fn minijail_set_seccomp_filter_tsync(j: *mut minijail);
+    pub fn minijail_parse_seccomp_filters(j: *mut minijail, path: *const c_char);
+    pub fn minijail_parse_seccomp_filters_from_fd(j: *mut minijail, fd: c_int);
+    pub fn minijail_log_seccomp_filter_failures(j: *mut minijail);
+    pub fn minijail_use_caps(j: *mut minijail, capmask: u64);
+    pub fn minijail_capbset_drop(j: *mut minijail, capmask: u64);
+    pub fn minijail_set_ambient_caps(j: *mut minijail);
+    pub fn minijail_reset_signal_mask(j: *mut minijail);
+    pub fn minijail_namespace_vfs(j: *mut minijail);
+    pub fn minijail_namespace_enter_vfs(j: *mut minijail, ns_path: *const c_char);
+    pub fn minijail_new_session_keyring(j: *mut minijail);
+    pub fn minijail_skip_remount_private(j: *mut minijail);
+    pub fn minijail_namespace_ipc(j: *mut minijail);
+    pub fn minijail_namespace_net(j: *mut minijail);
+    pub fn minijail_namespace_enter_net(j: *mut minijail, ns_path: *const c_char);
+    pub fn minijail_namespace_cgroups(j: *mut minijail);
+    pub fn minijail_close_open_fds(j: *mut minijail);
+    pub fn minijail_namespace_pids(j: *mut minijail);
+    pub fn minijail_namespace_user(j: *mut minijail);
+    pub fn minijail_namespace_user_disable_setgroups(j: *mut minijail);
+    pub fn minijail_uidmap(j: *mut minijail,
+                           uidmap: *const c_char)
+                           -> c_int;
+    pub fn minijail_gidmap(j: *mut minijail,
+                           gidmap: *const c_char)
+                           -> c_int;
+    pub fn minijail_remount_proc_readonly(j: *mut minijail);
+    pub fn minijail_run_as_init(j: *mut minijail);
+    pub fn minijail_write_pid_file(j: *mut minijail,
+                                   path: *const c_char)
+                                   -> c_int;
+    pub fn minijail_inherit_usergroups(j: *mut minijail);
+    pub fn minijail_use_alt_syscall(j: *mut minijail,
+                                    table: *const c_char)
+                                    -> c_int;
+    pub fn minijail_add_to_cgroup(j: *mut minijail,
+                                  path: *const c_char)
+                                  -> c_int;
+    pub fn minijail_enter_chroot(j: *mut minijail,
+                                 dir: *const c_char)
+                                 -> c_int;
+    pub fn minijail_enter_pivot_root(j: *mut minijail,
+                                     dir: *const c_char)
+                                     -> c_int;
+    pub fn minijail_get_original_path(j: *mut minijail,
+                                      chroot_path: *const c_char)
+                                      -> *mut c_char;
+    pub fn minijail_mount_tmp(j: *mut minijail);
+    pub fn minijail_mount_tmp_size(j: *mut minijail, size: usize);
+    pub fn minijail_mount_with_data(j: *mut minijail,
+                                    src: *const c_char,
+                                    dest: *const c_char,
+                                    type_: *const c_char,
+                                    flags: c_ulong,
+                                    data: *const c_char)
+                                    -> c_int;
+    pub fn minijail_mount(j: *mut minijail,
+                          src: *const c_char,
+                          dest: *const c_char,
+                          type_: *const c_char,
+                          flags: c_ulong)
+                          -> c_int;
+    pub fn minijail_bind(j: *mut minijail,
+                         src: *const c_char,
+                         dest: *const c_char,
+                         writeable: c_int)
+                         -> c_int;
+    pub fn minijail_enter(j: *const minijail);
+    pub fn minijail_run(j: *mut minijail,
+                        filename: *const c_char,
+                        argv: *const *const c_char)
+                        -> c_int;
+    pub fn minijail_run_no_preload(j: *mut minijail,
+                                   filename: *const c_char,
+                                   argv: *const *const c_char)
+                                   -> c_int;
+    pub fn minijail_run_pid(j: *mut minijail,
+                            filename: *const c_char,
+                            argv: *const *const c_char,
+                            pchild_pid: *mut pid_t)
+                            -> c_int;
+    pub fn minijail_run_pipe(j: *mut minijail,
+                             filename: *const c_char,
+                             argv: *const *const c_char,
+                             pstdin_fd: *mut c_int)
+                             -> c_int;
+    pub fn minijail_run_pid_pipes(j: *mut minijail,
+                                  filename: *const c_char,
+                                  argv: *const *const c_char,
+                                  pchild_pid: *mut pid_t,
+                                  pstdin_fd: *mut c_int,
+                                  pstdout_fd: *mut c_int,
+                                  pstderr_fd: *mut c_int)
+                                  -> c_int;
+    pub fn minijail_run_pid_pipes_no_preload(j: *mut minijail,
+                                             filename: *const c_char,
+                                             argv: *const *const c_char,
+                                             pchild_pid: *mut pid_t,
+                                             pstdin_fd: *mut c_int,
+                                             pstdout_fd: *mut c_int,
+                                             pstderr_fd: *mut c_int)
+                                             -> c_int;
+    pub fn minijail_kill(j: *mut minijail) -> c_int;
+    pub fn minijail_wait(j: *mut minijail) -> c_int;
+    pub fn minijail_destroy(j: *mut minijail);
+} // extern "C"
diff --git a/io_jail/src/test_filter.policy b/io_jail/src/test_filter.policy
new file mode 100644
index 0000000..479e7e5
--- /dev/null
+++ b/io_jail/src/test_filter.policy
@@ -0,0 +1,6 @@
+close: 1
+exit: 1
+futex: 1
+lseek: 1
+read: 1
+write: 1