use platform::linux::seccomp;
use platform::unix::process::Process;
use platform::unix;
use profile::{Operation, PathPattern, Profile};
use sandbox::Command;
use libc::{self, c_char, c_int, c_ulong, c_void, gid_t, pid_t, size_t, ssize_t, uid_t};
use std::env;
use std::ffi::{CString, OsStr, OsString};
use std::fs::{self, File};
use std::io::{self, Write};
use std::iter;
use std::mem;
use std::os::unix::prelude::OsStrExt;
use std::path::{Path, PathBuf};
use std::ptr;
pub fn activate(profile: &Profile) -> Result<(),c_int> {
let jail = try!(ChrootJail::new(profile));
try!(jail.enter());
drop_capabilities()
}
struct ChrootJail {
directory: PathBuf,
}
impl ChrootJail {
fn new(profile: &Profile) -> Result<ChrootJail,c_int> {
let prefix = CString::new("/tmp/gaol.XXXXXX").unwrap();
let mut prefix: Vec<u8> = prefix.as_bytes_with_nul().iter().map(|x| *x).collect();
unsafe {
if libc::mkdtemp(prefix.as_mut_ptr() as *mut c_char).is_null() {
return Err(-1)
}
}
let jail_dir = PathBuf::from(OsStr::from_bytes(&prefix[..prefix.len() - 1]));
let jail = ChrootJail {
directory: jail_dir,
};
let dest = CString::new(jail.directory
.as_os_str()
.to_str()
.unwrap()
.as_bytes()).unwrap();
let tmpfs = CString::new("tmpfs").unwrap();
let result = unsafe {
libc::mount(tmpfs.as_ptr(),
dest.as_ptr(),
tmpfs.as_ptr(),
libc::MS_NOATIME | libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_NOSUID,
ptr::null())
};
if result != 0 {
return Err(result)
}
for operation in profile.allowed_operations().iter() {
match *operation {
Operation::FileReadAll(PathPattern::Literal(ref path)) |
Operation::FileReadAll(PathPattern::Subpath(ref path)) => {
try!(jail.bind_mount(path));
}
_ => {}
}
}
Ok(jail)
}
fn enter(&self) -> Result<(),c_int> {
let directory = CString::new(self.directory
.as_os_str()
.to_str()
.unwrap()
.as_bytes()).unwrap();
let result = unsafe {
libc::chroot(directory.as_ptr())
};
if result != 0 {
return Err(result)
}
match env::set_current_dir(&Path::new(".")) {
Ok(_) => Ok(()),
Err(_) => Err(-1),
}
}
fn bind_mount(&self, source_path: &Path) -> Result<(),c_int> {
let mut destination_path = self.directory.clone();
let mut components: Vec<OsString> =
source_path.components().skip(1)
.map(|component| component.as_os_str().to_os_string())
.collect();
let last_component = components.pop();
for component in components.into_iter() {
destination_path.push(component);
if fs::create_dir(&destination_path).is_err() {
return Err(-1)
}
}
if let Some(last_component) = last_component {
destination_path.push(last_component);
match fs::metadata(source_path) {
Ok(ref metadata) if metadata.is_dir() => {
if fs::create_dir(&destination_path).is_err() {
return Err(-1)
}
}
Ok(_) => {
if File::create(&destination_path).is_err() {
return Err(-1)
}
}
Err(_) => {
return Ok(())
}
}
}
let source_path = CString::new(source_path.as_os_str()
.to_str()
.unwrap()
.as_bytes()).unwrap();
let destination_path = CString::new(destination_path.as_os_str()
.to_str()
.unwrap()
.as_bytes()).unwrap();
let bind = CString::new("bind").unwrap();
let result = unsafe {
libc::mount(source_path.as_ptr(),
destination_path.as_ptr(),
bind.as_ptr(),
libc::MS_MGC_VAL | libc::MS_BIND | libc::MS_REC,
ptr::null_mut())
};
if result == 0 {
Ok(())
} else {
Err(result)
}
}
}
fn drop_capabilities() -> Result<(),c_int> {
let capability_data: Vec<_> = iter::repeat(__user_cap_data_struct {
effective: 0,
permitted: 0,
inheritable: 0,
}).take(_LINUX_CAPABILITY_U32S_3 as usize).collect();
let result = unsafe {
capset(&__user_cap_header_struct {
version: _LINUX_CAPABILITY_VERSION_3,
pid: 0,
}, capability_data.as_ptr())
};
if result == 0 {
Ok(())
} else {
Err(result)
}
}
unsafe fn prepare_user_and_pid_namespaces(parent_uid: uid_t, parent_gid: gid_t) -> io::Result<()> {
assert!(libc::unshare(libc::CLONE_NEWUSER | libc::CLONE_NEWPID) == 0);
try!(try!(File::create(&Path::new("/proc/self/setgroups"))).write_all(b"deny"));
let gid_contents = format!("0 {} 1", parent_gid);
try!(try!(File::create(&Path::new("/proc/self/gid_map"))).write_all(gid_contents.as_bytes()));
let uid_contents = format!("0 {} 1", parent_uid);
try!(try!(File::create(&Path::new("/proc/self/uid_map"))).write_all(uid_contents.as_bytes()));
Ok(())
}
pub fn start(profile: &Profile, command: &mut Command) -> io::Result<Process> {
let (parent_uid, parent_gid) = unsafe {
(libc::getuid(), libc::getgid())
};
let mut unshare_flags = libc::CLONE_NEWIPC | libc::CLONE_NEWNS | libc::CLONE_NEWUTS;
if !profile.allowed_operations().iter().any(|operation| {
match *operation {
Operation::NetworkOutbound(_) => true,
_ => false,
}
}) {
unshare_flags |= libc::CLONE_NEWNET
}
unsafe {
let mut pipe_fds = [0, 0];
assert!(libc::pipe(&mut pipe_fds[0]) == 0);
assert!(libc::prctl(libc::PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) == 0);
if libc::fork() == 0 {
libc::close(pipe_fds[0]);
prepare_user_and_pid_namespaces(parent_uid, parent_gid).unwrap();
match libc::fork() {
0 => {
assert!(libc::unshare(unshare_flags) == 0);
drop(unix::process::exec(command));
libc::abort()
}
grandchild_pid => {
assert!(libc::write(pipe_fds[1],
&grandchild_pid as *const pid_t as *const c_void,
mem::size_of::<pid_t>() as size_t) ==
mem::size_of::<pid_t>() as ssize_t);
libc::exit(0);
}
}
}
libc::close(pipe_fds[1]);
let mut grandchild_pid: pid_t = 0;
assert!(libc::read(pipe_fds[0],
&mut grandchild_pid as *mut i32 as *mut c_void,
mem::size_of::<pid_t>() as size_t) ==
mem::size_of::<pid_t>() as ssize_t);
Ok(Process {
pid: grandchild_pid,
})
}
}
#[repr(C)]
#[allow(non_camel_case_types)]
struct __user_cap_header_struct {
version: u32,
pid: c_int,
}
#[repr(C)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
struct __user_cap_data_struct {
effective: u32,
permitted: u32,
inheritable: u32,
}
#[allow(non_camel_case_types)]
type cap_user_header_t = *const __user_cap_header_struct;
#[allow(non_camel_case_types)]
type const_cap_user_data_t = *const __user_cap_data_struct;
const _LINUX_CAPABILITY_VERSION_3: u32 = 0x20080522;
const _LINUX_CAPABILITY_U32S_3: u32 = 2;
extern {
fn capset(hdrp: cap_user_header_t, datap: const_cap_user_data_t) -> c_int;
}