Skip to content

Commit

Permalink
Remove landlock
Browse files Browse the repository at this point in the history
This patch completely removes landlock from the Linux sandbox.

While landlock provides solid filesystem isolation even without
requiring any intricate Linux sandboxing knowledge, it is currently
still too limited to build a "bulletproof" filesystem sandbox.

As such, it is better suited for best-effort isolation of "assumed safe"
applications, rather than sandboxing of "potentially hazardous"
software.

For Birdcage, these are the biggest limitations:
 - Locking down sockets/pipes
 - High kernel requirement for all basic features (FS_TRUNCATE is 6.2)
  • Loading branch information
cd-work committed Oct 5, 2023
1 parent f6c3be6 commit 18112cb
Show file tree
Hide file tree
Showing 19 changed files with 53 additions and 202 deletions.
6 changes: 0 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ name = "fs"
path = "tests/fs.rs"
harness = false

[[test]]
name = "fs_without_landlock"
path = "tests/fs_without_landlock.rs"
harness = false

[[test]]
name = "full_env"
path = "tests/full_env.rs"
Expand Down Expand Up @@ -66,7 +61,6 @@ harness = false

[target.'cfg(target_os = "linux")'.dependencies]
seccompiler = "0.2.0"
landlock = "0.2.0"
libc = "0.2.132"

[dev-dependencies]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ use the example.
## Supported Platforms
- Linux (5.13+) via [Landlock] and [seccomp]
- Linux via [namespaces] and [seccomp]
- macOS via `sandbox_init()` (aka Seatbelt)
[landlock]: https://www.kernel.org/doc/html/latest/userspace-api/landlock.html
[namespaces]: https://man7.org/linux/man-pages/man7/namespaces.7.html
[seccomp]: https://man7.org/linux/man-pages/man2/seccomp.2.html
2 changes: 1 addition & 1 deletion examples/sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();

// Setup sandbox and its exceptions.
let mut birdcage = Birdcage::new()?;
let mut birdcage = Birdcage::new();

for path in cli.allow_read {
birdcage.add_exception(Exception::Read(path))?;
Expand Down
26 changes: 0 additions & 26 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ use std::fmt::{self, Display, Formatter};
use std::io::Error as IoError;
use std::result::Result as StdResult;

#[cfg(target_os = "linux")]
use landlock::{PathFdError, RulesetError};
#[cfg(target_os = "linux")]
use seccompiler::{BackendError, Error as SeccompError};

Expand All @@ -18,17 +16,11 @@ pub type Result<T> = StdResult<T, Error>;
/// Sandboxing error.
#[derive(Debug)]
pub enum Error {
/// Landlock ruleset creation/modification error.
#[cfg(target_os = "linux")]
Ruleset(RulesetError),

/// Seccomp errors.
#[cfg(target_os = "linux")]
Seccomp(SeccompError),

/// Invalid sandbox exception path.
#[cfg(target_os = "linux")]
InvalidPath(PathFdError),
#[cfg(target_os = "macos")]
InvalidPath(InvalidPathError),

Expand All @@ -44,12 +36,8 @@ impl StdError for Error {}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
#[cfg(target_os = "linux")]
Self::Ruleset(error) => write!(f, "landlock ruleset error: {error}"),
#[cfg(target_os = "linux")]
Self::Seccomp(error) => write!(f, "seccomp error: {error}"),
#[cfg(target_os = "linux")]
Self::InvalidPath(error) => write!(f, "invalid path: {error}"),
#[cfg(target_os = "macos")]
Self::InvalidPath(error) => write!(f, "invalid path: {error:?}"),
Self::Io(error) => write!(f, "input/output error: {error}"),
Expand All @@ -60,13 +48,6 @@ impl Display for Error {
}
}

#[cfg(target_os = "linux")]
impl From<RulesetError> for Error {
fn from(error: RulesetError) -> Self {
Self::Ruleset(error)
}
}

#[cfg(target_os = "linux")]
impl From<SeccompError> for Error {
fn from(error: SeccompError) -> Self {
Expand All @@ -81,13 +62,6 @@ impl From<BackendError> for Error {
}
}

#[cfg(target_os = "linux")]
impl From<PathFdError> for Error {
fn from(error: PathFdError) -> Self {
Self::InvalidPath(error)
}
}

#[cfg(target_os = "macos")]
impl From<InvalidPathError> for Error {
fn from(error: InvalidPathError) -> Self {
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
//! fs::read_to_string(file.path()).unwrap();
//!
//! // Initialize the sandbox; by default everything is prohibited.
//! Birdcage::new().unwrap().lock().unwrap();
//! Birdcage::new().lock().unwrap();
//!
//! // Reads with sandbox should fail.
//! let result = fs::read_to_string(file.path());
Expand Down Expand Up @@ -56,7 +56,7 @@ pub type Birdcage = MacSandbox;

pub trait Sandbox: Sized {
/// Setup the sandboxing environment.
fn new() -> Result<Self>;
fn new() -> Self;

/// Add a new exception to the sandbox.
///
Expand Down
117 changes: 30 additions & 87 deletions src/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
//! Linux sandboxing.
//!
//! This module implements sandboxing on Linux based on the Landlock LSM,
//! namespaces, and seccomp.

use std::collections::HashMap;
use std::io::Error as IoError;
use std::path::PathBuf;

use landlock::{
make_bitflags, Access, AccessFs, Compatible, PathBeneath, PathFd, Ruleset, RulesetAttr,
RulesetCreated, RulesetCreatedAttr, RulesetStatus, ABI as LANDLOCK_ABI,
};

use crate::error::{Error, Result};
use crate::error::Result;
use crate::linux::namespaces::MountFlags;
use crate::linux::seccomp::NetworkFilter;
use crate::{Exception, Sandbox};

mod namespaces;
mod seccomp;

/// Minimum landlock ABI version.
const ABI: LANDLOCK_ABI = LANDLOCK_ABI::V1;

/// Linux sandboxing.
#[derive(Default)]
pub struct LinuxSandbox {
bind_mounts: HashMap<PathBuf, MountFlags>,
env_exceptions: Vec<String>,
landlock: RulesetCreated,
allow_networking: bool,
full_env: bool,
}
Expand Down Expand Up @@ -54,63 +44,19 @@ impl LinuxSandbox {
}

impl Sandbox for LinuxSandbox {
fn new() -> Result<Self> {
// Setup landlock filtering.
let mut landlock = Ruleset::new()
.set_best_effort(false)
.handle_access(AccessFs::from_all(ABI))?
.create()?;
landlock.as_mut().set_no_new_privs(true);

Ok(Self {
landlock,
allow_networking: false,
full_env: false,
env_exceptions: Default::default(),
bind_mounts: Default::default(),
})
fn new() -> Self {
Self::default()
}

fn add_exception(&mut self, exception: Exception) -> Result<&mut Self> {
let (path_fd, access) = match exception {
Exception::Read(path) => {
let path_fd = PathFd::new(&path)?;

self.update_bind_mount(path, false, false);

(path_fd, make_bitflags!(AccessFs::{ ReadFile | ReadDir }))
},
Exception::Write(path) => {
let path_fd = PathFd::new(&path)?;

self.update_bind_mount(path, true, false);

(path_fd, AccessFs::from_write(ABI))
},
Exception::ExecuteAndRead(path) => {
let path_fd = PathFd::new(&path)?;

self.update_bind_mount(path, false, true);

(path_fd, AccessFs::from_read(ABI))
},
Exception::Environment(key) => {
self.env_exceptions.push(key);
return Ok(self);
},
Exception::FullEnvironment => {
self.full_env = true;
return Ok(self);
},
Exception::Networking => {
self.allow_networking = true;
return Ok(self);
},
};

let rule = PathBeneath::new(path_fd, access);

self.landlock.as_mut().add_rule(rule)?;
match exception {
Exception::Read(path) => self.update_bind_mount(path, false, false),
Exception::Write(path) => self.update_bind_mount(path, true, false),
Exception::ExecuteAndRead(path) => self.update_bind_mount(path, false, true),
Exception::Environment(key) => self.env_exceptions.push(key),
Exception::FullEnvironment => self.full_env = true,
Exception::Networking => self.allow_networking = true,
}

Ok(self)
}
Expand All @@ -122,32 +68,29 @@ impl Sandbox for LinuxSandbox {
}

// Setup namespaces.
let namespace_result =
namespaces::create_namespaces(self.allow_networking, self.bind_mounts);
namespaces::create_namespaces(self.allow_networking, self.bind_mounts)?;

// Setup seccomp network filter.
if !self.allow_networking {
let seccomp_result = NetworkFilter::apply();

// Propagate failure if neither seccomp nor namespaces could isolate networking.
if let (Err(_), Err(err)) = (&namespace_result, seccomp_result) {
return Err(err);
}
let _ = NetworkFilter::apply();
}

// Use landlock only if namespaces failed.
if namespace_result.is_ok() {
return Ok(());
}
// Block suid/sgid.
//
// This is also blocked by our bind mount's MS_NOSUID flag, so we're just
// doubling-down here.
no_new_privs()?;

// Apply landlock rules.
let status = self.landlock.restrict_self()?;
Ok(())
}
}

// Ensure all restrictions were properly applied.
if status.no_new_privs && status.ruleset == RulesetStatus::FullyEnforced {
Ok(())
} else {
Err(Error::ActivationFailed("sandbox could not be fully enforced".into()))
}
/// Prevent suid/sgid.
fn no_new_privs() -> Result<()> {
let result = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) };

match result {
0 => Ok(()),
_ => Err(IoError::last_os_error().into()),
}
}
14 changes: 6 additions & 8 deletions src/linux/namespaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,9 @@ fn pivot_root(new_root: &CStr, put_old: &CStr) -> Result<()> {
fn umount(target: &CStr) -> Result<()> {
let result = unsafe { libc::umount2(target.as_ptr(), libc::MNT_DETACH) };

if result == 0 {
Ok(())
} else {
Err(IoError::last_os_error().into())
match result {
0 => Ok(()),
_ => Err(IoError::last_os_error().into()),
}
}

Expand Down Expand Up @@ -288,10 +287,9 @@ fn create_user_namespace(
/// Enter a namespace.
fn unshare(namespaces: Namespaces) -> Result<()> {
let result = unsafe { libc::unshare(namespaces.bits()) };
if result == 0 {
Ok(())
} else {
Err(IoError::last_os_error().into())
match result {
0 => Ok(()),
_ => Err(IoError::last_os_error().into()),
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ pub struct MacSandbox {
}

impl Sandbox for MacSandbox {
fn new() -> Result<Self> {
Ok(Self { profile: DEFAULT_RULE.to_vec(), env_exceptions: Vec::new(), full_env: false })
fn new() -> Self {
Self { profile: DEFAULT_RULE.to_vec(), env_exceptions: Vec::new(), full_env: false }
}

fn add_exception(&mut self, exception: Exception) -> Result<&mut Self> {
Expand Down
2 changes: 1 addition & 1 deletion tests/canonicalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fs;
use birdcage::{Birdcage, Exception, Sandbox};

fn main() {
let mut birdcage = Birdcage::new().unwrap();
let mut birdcage = Birdcage::new();
birdcage.add_exception(Exception::Read("./".into())).unwrap();
birdcage.lock().unwrap();

Expand Down
2 changes: 1 addition & 1 deletion tests/consistent_id_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn main() {
let euid = unsafe { libc::geteuid() };
let egid = unsafe { libc::getegid() };

let birdcage = Birdcage::new().unwrap();
let birdcage = Birdcage::new();
birdcage.lock().unwrap();

assert_eq!(uid, unsafe { libc::getuid() });
Expand Down
2 changes: 1 addition & 1 deletion tests/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn main() {
env::set_var("PRIVATE", "BAD");

// Activate our sandbox.
let mut birdcage = Birdcage::new().unwrap();
let mut birdcage = Birdcage::new();
birdcage.add_exception(Exception::Environment("PUBLIC".into())).unwrap();
birdcage.lock().unwrap();

Expand Down
2 changes: 1 addition & 1 deletion tests/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::process::Command;
use birdcage::{Birdcage, Exception, Sandbox};

fn main() {
let mut birdcage = Birdcage::new().unwrap();
let mut birdcage = Birdcage::new();
birdcage.add_exception(Exception::ExecuteAndRead("/usr/bin/true".into())).unwrap();
birdcage.add_exception(Exception::ExecuteAndRead("/usr/lib".into())).unwrap();
if PathBuf::from("/lib64").exists() {
Expand Down
2 changes: 1 addition & 1 deletion tests/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn main() {
fs::write(&public_path, FILE_CONTENT.as_bytes()).unwrap();

// Activate our sandbox.
let mut birdcage = Birdcage::new().unwrap();
let mut birdcage = Birdcage::new();
birdcage.add_exception(Exception::Read(public_path.path().into())).unwrap();
birdcage.lock().unwrap();

Expand Down
Loading

0 comments on commit 18112cb

Please sign in to comment.