From 1aa6bad460cf76324c9c8517e6c5f6d72f831d89 Mon Sep 17 00:00:00 2001 From: valoq Date: Sat, 28 Jun 2025 23:00:24 +0200 Subject: [PATCH] cleanup --- Cargo.lock | 59 +++++++++++++++++++++++++++++-- src/main.rs | 22 ++++++++++-- src/sandbox.rs | 39 --------------------- src/utils/landlock.rs | 66 +++++++++++++++++++++++++++++++++++ src/utils/landlock_support.rs | 24 ------------- src/utils/mod.rs | 2 +- 6 files changed, 142 insertions(+), 70 deletions(-) delete mode 100644 src/sandbox.rs create mode 100644 src/utils/landlock.rs delete mode 100644 src/utils/landlock_support.rs diff --git a/Cargo.lock b/Cargo.lock index 71b8cf9..e6a6d15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,7 +284,7 @@ dependencies = [ "byteorder", "bytesize", "libbzip3-sys", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -570,6 +570,26 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "errno" version = "0.3.10" @@ -728,7 +748,7 @@ dependencies = [ "libz-sys", "num_cpus", "snap", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -886,6 +906,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "landlock" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d2ef408b88e913bfc6594f5e693d57676f6463ded7d8bf994175364320c706" +dependencies = [ + "enumflags2", + "libc", + "thiserror 2.0.12", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1114,6 +1145,7 @@ dependencies = [ "insta", "is_executable", "itertools", + "landlock", "libc", "linked-hash-map", "lz4_flex", @@ -1132,6 +1164,7 @@ dependencies = [ "tar", "tempfile", "test-strategy", + "thiserror 2.0.12", "time", "unrar", "xz2", @@ -1716,7 +1749,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -1730,6 +1772,17 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "time" version = "0.3.41" diff --git a/src/main.rs b/src/main.rs index e74d4df..1e1466a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ pub mod error; pub mod extension; pub mod list; pub mod utils; -pub mod sandbox; +//pub mod sandbox; use std::{env, path::PathBuf}; use std::path::Path; @@ -23,6 +23,9 @@ use self::{ }, }; +//use utils::landlock::*; + + // Used in BufReader and BufWriter to perform less syscalls const BUFFER_CAPACITY: usize = 1024 * 32; @@ -52,6 +55,19 @@ fn run() -> Result<()> { .unwrap_or_else(|| env::current_dir().unwrap_or_default()); // restrict filesystem access to working_dir; + // 1. working_dir is either the output_dir specified by the -d option or + // 2. it is the temporary .tmp-ouch-XXXXXX directory that is renamed after decompression + // + //Case 1: Files are directly written to the output_directory, which may be created by ouch + // full landlock permissions granted inside the specified directory + //Case 2: Files are written to the .tmp-ouch directory, requiring make_dir permissions on the + // parent (cwd) for renaming and full permissions within the tmp-ouch directory itself + // + // Since either the specified output directory is created if it did not exist, or the .ouch-tmp + // directory is created in the current working directory, the parent directory of the target + // directory requires LANDLOCK_ACCESS_FS_MAKE_DIR + + // expects either the .tmp-ouch-XXXXXX path or the specified output directory (-d option) init_sandbox(&working_dir); commands::run(args, skip_questions_positively, file_visibility_policy) @@ -64,10 +80,10 @@ fn init_sandbox(allowed_dir: &Path) { } - if utils::landlock_support::is_landlock_supported() { + if utils::landlock::is_landlock_supported() { let path_str = allowed_dir.to_str().expect("Cannot convert path"); - match sandbox::restrict_paths(&[path_str]) { + match utils::landlock::restrict_paths(&[path_str]) { Ok(status) => { //check } diff --git a/src/sandbox.rs b/src/sandbox.rs deleted file mode 100644 index 9c555f7..0000000 --- a/src/sandbox.rs +++ /dev/null @@ -1,39 +0,0 @@ -// 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()?) -} - - diff --git a/src/utils/landlock.rs b/src/utils/landlock.rs new file mode 100644 index 0000000..8ab67a9 --- /dev/null +++ b/src/utils/landlock.rs @@ -0,0 +1,66 @@ +// Landlock support and generic Landlock sandbox 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; + +/// Returns true if Landlock is supported by the running kernel (Linux kernel >= 5.19). +#[cfg(target_os = "linux")] +pub fn is_landlock_supported() -> bool { + use std::process::Command; + + if let Ok(output) = Command::new("uname").arg("-r").output() { + if let Ok(version_str) = String::from_utf8(output.stdout) { + // Version string is expected to be in "5.19.0-foo" or similar + let mut parts = version_str.trim().split('.'); + if let (Some(major), Some(minor)) = (parts.next(), parts.next()) { + if let (Ok(major), Ok(minor)) = (major.parse::(), minor.parse::()) { + return (major > 5) || (major == 5 && minor >= 19); + } + } + } + } + false +} + +#[cfg(not(target_os = "linux"))] +pub fn is_landlock_supported() -> bool { + false +} + +#[derive(Debug, Error)] +pub enum MyRestrictError { + #[error(transparent)] + Ruleset(#[from] RulesetError), + #[error(transparent)] + AddRule(#[from] PathFdError), +} + +/// Restricts the process to only access the given hierarchies using Landlock, if supported. +/// +/// The Landlock ABI is set to v2 for compatibility with Linux 5.19+. +/// All hierarchies are given full access, but root ("/") is read-only. +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()?) +} \ No newline at end of file diff --git a/src/utils/landlock_support.rs b/src/utils/landlock_support.rs deleted file mode 100644 index e196284..0000000 --- a/src/utils/landlock_support.rs +++ /dev/null @@ -1,24 +0,0 @@ -//Check Landlock kernel support (Linux kernel >= 5.19) - -#[cfg(target_os = "linux")] -pub fn is_landlock_supported() -> bool { - use std::process::Command; - - if let Ok(output) = Command::new("uname").arg("-r").output() { - if let Ok(version_str) = String::from_utf8(output.stdout) { - // Version string is expected to be in "5.19.0-foo" or similar - let mut parts = version_str.trim().split('.'); - if let (Some(major), Some(minor)) = (parts.next(), parts.next()) { - if let (Ok(major), Ok(minor)) = (major.parse::(), minor.parse::()) { - return (major > 5) || (major == 5 && minor >= 19); - } - } - } - } - false -} - -#[cfg(not(target_os = "linux"))] -pub fn is_landlock_supported() -> bool { - false -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a5cc375..611ee89 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -10,7 +10,7 @@ mod fs; pub mod io; pub mod logger; mod question; -pub mod landlock_support; +pub mod landlock; pub use self::{ file_visibility::FileVisibilityPolicy,