Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mount namespaces to linux sandbox #45

Merged
merged 19 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 75 additions & 12 deletions src/linux/namespaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::fs::{self, File};
use std::io::Error as IoError;
use std::os::unix::ffi::OsStrExt;
use std::path::{Component, Path, PathBuf};
use std::{env, ptr};
use std::{env, io, ptr};

use bitflags::bitflags;

Expand Down Expand Up @@ -88,17 +88,13 @@ fn create_mount_namespace(bind_mounts: HashMap<PathBuf, MountFlags>) -> Result<(
});

// Bind mount all allowed directories.
let current_dir = env::current_dir().ok();
for (mut path, flags) in bind_mounts {
// Ensure all paths are absolute.
if path.is_relative() {
let current_dir = match &current_dir {
Some(current_dir) => current_dir,
// Ignore relative paths if we cannot access the working directory.
None => continue,
};
path = current_dir.join(path);
}
for (path, flags) in bind_mounts {
// Ensure all paths are absolute, without following symlinks.
let path = match absolute(&path) {
Ok(path) => normalize_path(&path),
// Ignore relative paths if we cannot access the working directory.
Err(_) => continue,
};

let src_c = CString::new(path.as_os_str().as_bytes()).unwrap();

Expand Down Expand Up @@ -374,3 +370,70 @@ bitflags! {
const SYSVSEM = libc::CLONE_SYSVSEM;
}
}

// Copied from Rust's STD:
// https://github.com/rust-lang/rust/blob/42faef503f3e765120ca0ef06991337668eafc32/library/std/src/sys/unix/path.rs#L23C1-L63C2
//
// Licensed under MIT:
// https://github.com/rust-lang/rust/blob/master/LICENSE-MIT
//
/// Make a POSIX path absolute without changing its semantics.
fn absolute(path: &Path) -> io::Result<PathBuf> {
// This is mostly a wrapper around collecting `Path::components`, with
// exceptions made where this conflicts with the POSIX specification.
// See 4.13 Pathname Resolution, IEEE Std 1003.1-2017
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13

// Get the components, skipping the redundant leading "." component if it
// exists.
let mut components = path.strip_prefix(".").unwrap_or(path).components();
let path_os = path.as_os_str().as_encoded_bytes();
kylewillmon marked this conversation as resolved.
Show resolved Hide resolved

let mut normalized = if path.is_absolute() {
// "If a pathname begins with two successive <slash> characters, the
// first component following the leading <slash> characters may be
// interpreted in an implementation-defined manner, although more than
// two leading <slash> characters shall be treated as a single <slash>
// character."
if path_os.starts_with(b"//") && !path_os.starts_with(b"///") {
components.next();
PathBuf::from("//")
} else {
PathBuf::new()
}
} else {
env::current_dir()?
};
normalized.extend(components);

// "Interfaces using pathname resolution may specify additional constraints
// when a pathname that does not name an existing directory contains at
// least one non- <slash> character and contains one or more trailing
// <slash> characters".
// A trailing <slash> is also meaningful if "a symbolic link is
// encountered during pathname resolution".
if path_os.ends_with(b"/") {
normalized.push("");
}

Ok(normalized)
}

/// Normalize path components, stripping out `.` and `..`.
fn normalize_path(path: &Path) -> PathBuf {
let mut normalized = PathBuf::new();

for component in path.components() {
match component {
Component::Prefix(_) => unreachable!("impl does not consider windows"),
Component::RootDir => normalized.push("/"),
Component::CurDir => continue,
Component::ParentDir => {
normalized.pop();
},
Component::Normal(segment) => normalized.push(segment),
}
}

normalized
}
15 changes: 7 additions & 8 deletions tests/net_without_namespaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,14 @@ fn main() {
let birdcage = Birdcage::new().unwrap();
let result = birdcage.lock();

match result {
// Seccomp is supported, so networking should still be blocked.
Ok(_) => {
let result = TcpStream::connect("8.8.8.8:443");
assert!(result.is_err());
},
// Seccomp isn't supported, so failure is desired.
Err(_) => (),
// Seccomp isn't supported, so failure is desired.
if result.is_err() {
return;
}

// Seccomp is supported, so networking should still be blocked.
let result = TcpStream::connect("8.8.8.8:443");
assert!(result.is_err());
}

#[cfg(not(target_os = "linux"))]
Expand Down