blob: c6f37f97484e6c370882d164ce1388dca573b3da [file] [log] [blame]
// Copyright 2020 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.
//! Encapsulates the logic used to setup sandboxes for TEE applications.
use std::fmt::{self, Display};
use std::os::unix::io::RawFd;
use std::path::Path;
use libc::pid_t;
use minijail::{self, Minijail};
#[derive(Debug)]
pub enum Error {
/// Failed to initialize the jail.
Jail(minijail::Error),
/// An error occurred when spawning the child process.
ForkingJail(minijail::Error),
/// Failed to set the pivot root path.
PivotRoot(minijail::Error),
/// Failed to parse the seccomp policy.
SeccompPolicy(minijail::Error),
/// Failed to set the maximum number of open files.
SettingMaxOpenFiles(minijail::Error),
/// An error returned while waiting for a child process.
Wait(minijail::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match self {
Jail(e) => write!(f, "failed to setup jail: {}", e),
ForkingJail(e) => write!(f, "failed to fork jail process: {}", e),
PivotRoot(e) => write!(f, "failed to pivot root: {}", e),
SeccompPolicy(e) => write!(f, "failed to parse seccomp policy: {}", e),
SettingMaxOpenFiles(e) => write!(f, "error setting max open files: {}", e),
Wait(e) => write!(f, "failed waiting on jailed process to complete: {}", e),
}
}
}
/// The result of an operation in this crate.
pub type Result<T> = std::result::Result<T, Error>;
const PIVOT_ROOT: &str = "/mnt/empty";
/// An abstraction for the TEE application sandbox.
pub struct Sandbox(minijail::Minijail);
impl Sandbox {
/// Setup default sandbox / namespaces
pub fn new(seccomp_bpf_file: Option<&Path>) -> Result<Self> {
// 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().map_err(Error::Jail)?;
j.namespace_pids();
// TODO() determine why uid sandboxing doesn't work.
//j.namespace_user();
//j.namespace_user_disable_setgroups();
j.change_user("nobody").unwrap();
j.change_group("nobody").unwrap();
j.use_caps(0);
j.namespace_vfs();
j.namespace_net();
j.no_new_privs();
if let Some(path) = seccomp_bpf_file {
j.parse_seccomp_program(&path)
.map_err(Error::SeccompPolicy)?;
j.use_seccomp_filter();
}
j.enter_pivot_root(Path::new(PIVOT_ROOT))
.map_err(Error::PivotRoot)?;
let limit = 1024u64;
j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit)
.map_err(Error::SettingMaxOpenFiles)?;
Ok(Sandbox(j))
}
/// A version of the sandbox for use with tests because it doesn't require
/// elevated privilege.
pub fn new_test() -> Result<Self> {
let mut j = Minijail::new().map_err(Error::Jail)?;
j.namespace_user();
j.namespace_user_disable_setgroups();
j.no_new_privs();
Ok(Sandbox(j))
}
/// Execute `cmd` with the specified arguments `args`. The specified file
/// descriptors are connected to stdio for the child process.
pub fn run(
&mut self,
cmd: &Path,
args: &[&str],
stdin: RawFd,
stdout: RawFd,
stderr: RawFd,
) -> Result<pid_t> {
// Execute child process with stdin and stdout hooked up to communication and stderr to logging.
let keep_fds: [(RawFd, RawFd); 3] = [(stdin, 0), (stdout, 1), (stderr, 2)];
let pid = match self
.0
.run_remap(cmd, &keep_fds, args)
.map_err(Error::ForkingJail)?
{
0 => {
unsafe { libc::exit(0) };
}
p => p,
};
Ok(pid)
}
/// Wait until the child process completes. Non-zero return codes are
/// returned as an error.
pub fn wait_for_completion(&mut self) -> Result<()> {
self.0.wait().map_err(Error::Wait)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
use sys_util::pipe;
fn do_test(mut s: Sandbox) {
const STDOUT_TEST: &str = "stdout test";
const STDERR_TEST: &str = "stderr test";
let (r_stdin, mut w_stdin) = pipe(true).unwrap();
let (mut r_stdout, w_stdout) = pipe(true).unwrap();
let (mut r_stderr, w_stderr) = pipe(true).unwrap();
s.run(
Path::new("/bin/sh"),
&["/bin/sh"],
r_stdin.as_raw_fd(),
w_stdout.as_raw_fd(),
w_stderr.as_raw_fd(),
)
.unwrap();
std::mem::drop(r_stdin);
std::mem::drop(w_stdout);
std::mem::drop(w_stderr);
write!(
&mut w_stdin,
"echo -n '{}'; echo -n '{}' 1>&2; exit;",
STDOUT_TEST, STDERR_TEST
)
.unwrap();
w_stdin.flush().unwrap();
std::mem::drop(w_stdin);
let mut stdout_result = String::new();
r_stdout.read_to_string(&mut stdout_result).unwrap();
let mut stderr_result = String::new();
r_stderr.read_to_string(&mut stderr_result).unwrap();
let result = s.wait_for_completion();
if result.is_err() {
eprintln!("Got error code: {:?}", result)
}
assert_eq!(
(stdout_result, stderr_result),
(STDOUT_TEST.to_string(), STDERR_TEST.to_string())
);
result.unwrap();
}
#[test]
#[ignore] // privileged operation.
fn sandbox() {
let s = Sandbox::new(None).unwrap();
do_test(s);
}
#[test]
fn sandbox_unpriviledged() {
let s = Sandbox::new_test().unwrap();
do_test(s);
}
}