blob: 631c4361a552b49f96a2476595afe8f1f7a2ca1b [file] [log] [blame]
// 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::path::Path;
use std::str;
use libc::{self, c_ulong, gid_t, uid_t};
use anyhow::{bail, Context, Result};
use base::*;
use minijail::{self, Minijail};
use crate::crosvm::config::JailConfig;
#[allow(dead_code)]
pub(super) struct SandboxConfig<'a> {
pub(super) limit_caps: bool,
pub(super) log_failures: bool,
pub(super) seccomp_policy_path: Option<&'a Path>,
pub(super) seccomp_policy_name: &'a str,
pub(super) uid_map: Option<&'a str>,
pub(super) gid_map: Option<&'a str>,
pub(super) remount_mode: Option<c_ulong>,
}
pub(crate) struct ScopedMinijail(pub Minijail);
impl Drop for ScopedMinijail {
fn drop(&mut self) {
let _ = self.0.kill();
}
}
pub(super) fn create_base_minijail(
root: &Path,
r_limit: Option<u64>,
config: Option<&SandboxConfig>,
) -> Result<Minijail> {
// All child jails run in a new user namespace without any users mapped,
// they run as nobody unless otherwise configured.
let mut j = Minijail::new().context("failed to jail device")?;
if let Some(config) = config {
j.namespace_pids();
j.namespace_user();
j.namespace_user_disable_setgroups();
if config.limit_caps {
// Don't need any capabilities.
j.use_caps(0);
}
if let Some(uid_map) = config.uid_map {
j.uidmap(uid_map).context("error setting UID map")?;
}
if let Some(gid_map) = config.gid_map {
j.gidmap(gid_map).context("error setting GID map")?;
}
// Run in a new mount namespace.
j.namespace_vfs();
// Run in an empty network namespace.
j.namespace_net();
// Don't allow the device to gain new privileges.
j.no_new_privs();
// By default we'll prioritize using the pre-compiled .bpf over the .policy
// file (the .bpf is expected to be compiled using "trap" as the failure
// behavior instead of the default "kill" behavior).
// Refer to the code comment for the "seccomp-log-failures"
// command-line parameter for an explanation about why the |log_failures|
// flag forces the use of .policy files (and the build-time alternative to
// this run-time flag).
let bpf_policy_file = config.seccomp_policy_path.unwrap().with_extension("bpf");
if bpf_policy_file.exists() && !config.log_failures {
j.parse_seccomp_program(&bpf_policy_file)
.context("failed to parse precompiled seccomp policy")?;
} else {
// Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP,
// which will correctly kill the entire device process if a worker
// thread commits a seccomp violation.
j.set_seccomp_filter_tsync();
if config.log_failures {
j.log_seccomp_filter_failures();
}
j.parse_seccomp_filters(&config.seccomp_policy_path.unwrap().with_extension("policy"))
.context("failed to parse seccomp policy")?;
}
j.use_seccomp_filter();
// Don't do init setup.
j.run_as_init();
// Set up requested remount mode instead of default MS_PRIVATE.
if let Some(mode) = config.remount_mode {
j.set_remount_mode(mode);
}
}
// Only pivot_root if we are not re-using the current root directory.
if root != Path::new("/") {
// It's safe to call `namespace_vfs` multiple times.
j.namespace_vfs();
j.enter_pivot_root(root)
.context("failed to pivot root device")?;
}
// Most devices don't need to open many fds.
let limit = if let Some(r) = r_limit { r } else { 1024u64 };
j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit)
.context("error setting max open files")?;
Ok(j)
}
pub(super) fn simple_jail(
jail_config: &Option<JailConfig>,
policy: &str,
) -> Result<Option<Minijail>> {
if let Some(jail_config) = jail_config {
// A directory for a jailed device's pivot root.
if !jail_config.pivot_root.exists() {
bail!(
"{:?} doesn't exist, can't jail devices",
jail_config.pivot_root
);
}
let policy_path = jail_config
.seccomp_policy_dir
.as_ref()
.map(|dir| dir.join(policy));
let config = SandboxConfig {
limit_caps: true,
log_failures: jail_config.seccomp_log_failures,
seccomp_policy_path: policy_path.as_deref(),
seccomp_policy_name: policy,
uid_map: None,
gid_map: None,
remount_mode: None,
};
Ok(Some(create_base_minijail(
&jail_config.pivot_root,
None,
Some(&config),
)?))
} else {
Ok(None)
}
}
/// Mirror-mount all the directories in `dirs` into `jail` on a best-effort basis.
///
/// This function will not return an error if any of the directories in `dirs` is missing.
#[cfg(any(feature = "gpu", feature = "video-decoder", feature = "video-encoder"))]
pub(super) fn jail_mount_bind_if_exists<P: AsRef<std::ffi::OsStr>>(
jail: &mut Minijail,
dirs: &[P],
) -> Result<()> {
for dir in dirs {
let dir_path = Path::new(dir);
if dir_path.exists() {
jail.mount_bind(dir_path, dir_path, false)?;
}
}
Ok(())
}
#[derive(Copy, Clone)]
#[cfg_attr(not(feature = "tpm"), allow(dead_code))]
pub(super) struct Ids {
pub(super) uid: uid_t,
pub(super) gid: gid_t,
}
#[cfg(feature = "gpu")]
pub(super) fn add_current_user_as_root_to_jail(jail: &mut Minijail) -> Result<Ids> {
let crosvm_uid = geteuid();
let crosvm_gid = getegid();
jail.uidmap(&format!("0 {0} 1", crosvm_uid))
.context("error setting UID map")?;
jail.gidmap(&format!("0 {0} 1", crosvm_gid))
.context("error setting GID map")?;
Ok(Ids {
uid: crosvm_uid,
gid: crosvm_gid,
})
}
/// Set the uid/gid for the jailed process and give a basic id map. This is
/// required for bind mounts to work.
pub(super) fn add_current_user_to_jail(jail: &mut Minijail) -> Result<Ids> {
let crosvm_uid = geteuid();
let crosvm_gid = getegid();
jail.uidmap(&format!("{0} {0} 1", crosvm_uid))
.context("error setting UID map")?;
jail.gidmap(&format!("{0} {0} 1", crosvm_gid))
.context("error setting GID map")?;
if crosvm_uid != 0 {
jail.change_uid(crosvm_uid);
}
if crosvm_gid != 0 {
jail.change_gid(crosvm_gid);
}
Ok(Ids {
uid: crosvm_uid,
gid: crosvm_gid,
})
}