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

Allow controlling Landlock ABI versions #41

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ name = "fs"
path = "tests/fs.rs"
harness = false

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

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

[target.'cfg(target_os = "linux")'.dependencies]
seccompiler = "0.2.0"
landlock = "0.2.0"
landlock = { git = "https://github.com/landlock-lsm/rust-landlock" }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not merge until a new landlock version is released.

libc = "0.2.132"

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::macos::MacSandbox;

pub mod error;
#[cfg(target_os = "linux")]
mod linux;
pub mod linux;
#[cfg(target_os = "macos")]
mod macos;

Expand Down
68 changes: 50 additions & 18 deletions src/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ use std::fs;
use std::io::Error as IoError;

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

use crate::error::{Error, Result};
Expand All @@ -18,8 +19,14 @@ use crate::{Exception, Sandbox};

mod seccomp;

/// Minimum landlock ABI version.
const ABI: LANDLOCK_ABI = LANDLOCK_ABI::V1;
/// Default minimum landlock ABI version.
///
/// Calling `LinuxSandbox::new` on a system where this is not supported will
/// cause it to fail.
const MIN_ABI: LANDLOCK_ABI = LANDLOCK_ABI::V1;

/// Latest supported landlock ABI version.
const MAX_ABI: LANDLOCK_ABI = LANDLOCK_ABI::V3;

/// Linux sandboxing.
pub struct LinuxSandbox {
Expand All @@ -29,23 +36,48 @@ pub struct LinuxSandbox {
full_env: bool,
}

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);
impl LinuxSandbox {
/// Create a customized Linux sandbox.
///
/// The [`min_landlock_abi`] argument defines the minimum Landlock Kernel
/// ABI version which must be supported. Sandboxing will fail on systems
/// which do not support this.
///
/// All landlock ABI versions after [`min_landlock_abi`] versions are used
/// on systems that support them, but are ignored otherwise. This means
/// that the sandbox will be created without any error even if these are
/// not supported.
pub fn new_with_version(min_landlock_abi: LANDLOCK_ABI) -> Result<Self> {
let mut ruleset = Ruleset::default();

// Require at least `min_landlock_abi`.
(&mut ruleset).set_compatibility(CompatLevel::HardRequirement);
cd-work marked this conversation as resolved.
Show resolved Hide resolved
(&mut ruleset).handle_access(AccessFs::from_all(min_landlock_abi))?;

// Add optional checks for everything after `min_landlock_abi`.
//
// NOTE: This will require these access permissions on systems that support
// checking for them, while ignoring them on all other systems.
(&mut ruleset).set_compatibility(CompatLevel::BestEffort);
(&mut ruleset).handle_access(AccessFs::from_all(MAX_ABI))?;

let mut landlock = ruleset.create()?;
(&mut landlock).set_no_new_privs(true);
cd-work marked this conversation as resolved.
Show resolved Hide resolved

Ok(Self { landlock, env_exceptions: Vec::new(), allow_networking: false, full_env: false })
}
}

impl Sandbox for LinuxSandbox {
fn new() -> Result<Self> {
Self::new_with_version(MIN_ABI)
}

fn add_exception(&mut self, exception: Exception) -> Result<&mut Self> {
let (path, access) = match exception {
Exception::Read(path) => (path, make_bitflags!(AccessFs::{ ReadFile | ReadDir })),
Exception::Write(path) => (path, AccessFs::from_write(ABI)),
Exception::ExecuteAndRead(path) => (path, AccessFs::from_read(ABI)),
Exception::Write(path) => (path, AccessFs::from_write(MAX_ABI)),
Exception::ExecuteAndRead(path) => (path, AccessFs::from_read(MAX_ABI)),
Exception::Environment(key) => {
self.env_exceptions.push(key);
return Ok(self);
Expand All @@ -62,7 +94,7 @@ impl Sandbox for LinuxSandbox {

let rule = PathBeneath::new(PathFd::new(path)?, access);

self.landlock.as_mut().add_rule(rule)?;
(&mut self.landlock).add_rule(rule)?;

Ok(self)
}
Expand All @@ -85,10 +117,10 @@ impl Sandbox for LinuxSandbox {
let status = self.landlock.restrict_self()?;

// Ensure all restrictions were properly applied.
if status.no_new_privs && status.ruleset == RulesetStatus::FullyEnforced {
Ok(())
} else {
if status.ruleset == RulesetStatus::NotEnforced || !status.no_new_privs {
Err(Error::ActivationFailed("sandbox could not be fully enforced".into()))
} else {
Ok(())
}
}
}
Expand Down
56 changes: 56 additions & 0 deletions tests/fs_truncate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#[rustfmt::skip]
#[cfg(target_os = "linux")]
use {
std::ffi::CString,
std::fs,

birdcage::linux::LANDLOCK_ABI,
birdcage::{Birdcage, Exception, Sandbox},
tempfile::NamedTempFile,
};

#[cfg(target_os = "linux")]
fn main() {
// Create files with non-zero length.
let input = "truncate this";
let public_file = NamedTempFile::new().unwrap();
let public_path = public_file.path();
fs::write(public_path, input).unwrap();
let private_file = NamedTempFile::new().unwrap();
let private_path = private_file.path();
fs::write(private_path, input).unwrap();

// Enable our sandbox.
let mut birdcage = match Birdcage::new_with_version(LANDLOCK_ABI::V3) {
Ok(birdcage) => birdcage,
// Skip this test if LANDLOCK_ABI::V3 is not supported.
Err(_) => return,
};
birdcage.add_exception(Exception::Write(public_path.into())).unwrap();
birdcage.add_exception(Exception::Read(public_path.into())).unwrap();
birdcage.add_exception(Exception::Read(private_path.into())).unwrap();
birdcage.lock().unwrap();

// Allow truncating public file.
let path_str = public_path.to_string_lossy().to_string();
let c_path = CString::new(path_str).unwrap();
let result = unsafe { libc::truncate(c_path.as_ptr(), 0) };
assert_eq!(result, 0);

// Ensure the file is empty.
let content = fs::read_to_string(public_path).unwrap();
assert_eq!(content, String::new());

// Prevent truncating private file.
let path_str = private_path.to_string_lossy().to_string();
let c_path = CString::new(path_str).unwrap();
let result = unsafe { libc::truncate(c_path.as_ptr(), 0) };
assert_eq!(result, -1);

// Ensure the file is NOT empty.
let content = fs::read_to_string(private_path).unwrap();
assert_eq!(content, String::from(input));
}

#[cfg(not(target_os = "linux"))]
fn main() {}
Loading