diff --git a/CHANGELOG.md b/CHANGELOG.md index 52a3b6fb..23a48356 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Categories Used: ### New Features - Add multithreading support for `zstd` compression [\#689](https://github.com/ouch-org/ouch/pull/689) ([nalabrie](https://github.com/nalabrie)) +- Add landlock support for linux filesystem isolation [\#723](https://github.com/ouch-org/ouch/pull/723) ([valoq](https://github.com/valoq)) ### Bug Fixes diff --git a/Cargo.toml b/Cargo.toml index aaa1fba9..3dadd19c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ flate2 = { version = "1.0.30", default-features = false } fs-err = "2.11.0" gzp = { version = "0.11.3", default-features = false, features = ["snappy_default"] } ignore = "0.4.22" +landlock = "0.4.1" libc = "0.2.155" linked-hash-map = "0.5.6" lz4_flex = "0.11.3" @@ -31,6 +32,7 @@ sevenz-rust = { version = "0.6.1", features = ["compress", "aes256"] } snap = "1.1.1" tar = "0.4.41" tempfile = "3.10.1" +thiserror = "1.0.64" time = { version = "0.3.36", default-features = false } unrar = { version = "0.5.6", optional = true } xz2 = "0.1.7" diff --git a/src/main.rs b/src/main.rs index 10ead08c..a32ff984 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ pub mod error; pub mod extension; pub mod list; pub mod utils; +pub mod sandbox; use std::{env, path::PathBuf}; @@ -28,6 +29,14 @@ pub const EXIT_FAILURE: i32 = libc::EXIT_FAILURE; fn main() { let handler = spawn_logger_thread(); + + //restrict write permissions to the current workign directory + let working_dir = get_current_working_dir().expect("Cannot get current working dir"); + let path_str = working_dir.to_str().expect("Cannot convert path"); + let status = sandbox::restrict_paths(&[path_str]).expect("failed to build the ruleset"); + + //todo: check status and report error or warnign if landlock restriction failed + let result = run(); handler.shutdown_and_wait(); @@ -41,3 +50,7 @@ fn run() -> Result<()> { let (args, skip_questions_positively, file_visibility_policy) = CliArgs::parse_and_validate_args()?; commands::run(args, skip_questions_positively, file_visibility_policy) } + +fn get_current_working_dir() -> std::io::Result { + env::current_dir() +} diff --git a/src/sandbox.rs b/src/sandbox.rs new file mode 100644 index 00000000..9c555f75 --- /dev/null +++ b/src/sandbox.rs @@ -0,0 +1,39 @@ +// generic landlock implementation https://landlock.io/rust-landlock/landlock/struct.Ruleset.html + +use landlock::{ + Access, AccessFs, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset, + RulesetAttr, RulesetCreatedAttr, RulesetError, ABI, +}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum MyRestrictError { + #[error(transparent)] + Ruleset(#[from] RulesetError), + #[error(transparent)] + AddRule(#[from] PathFdError), +} + +pub fn restrict_paths(hierarchies: &[&str]) -> Result { + // The Landlock ABI should be incremented (and tested) regularly. + // ABI set to 2 in compatibility with linux 5.19 and higher + let abi = ABI::V2; + let access_all = AccessFs::from_all(abi); + let access_read = AccessFs::from_read(abi); + + Ok(Ruleset::default() + .handle_access(access_all)? + .create()? + // Read-only access to / (entire filesystem). + .add_rules(landlock::path_beneath_rules(&["/"], access_read))? + .add_rules( + hierarchies + .iter() + .map::, _>(|p| { + Ok(PathBeneath::new(PathFd::new(p)?, access_all)) + }), + )? + .restrict_self()?) +} + +