This commit is contained in:
valoq 2025-06-28 23:00:24 +02:00
parent 61599dfa25
commit 1aa6bad460
No known key found for this signature in database
GPG Key ID: 19F09A0FB865CBD8
6 changed files with 142 additions and 70 deletions

59
Cargo.lock generated
View File

@ -284,7 +284,7 @@ dependencies = [
"byteorder", "byteorder",
"bytesize", "bytesize",
"libbzip3-sys", "libbzip3-sys",
"thiserror", "thiserror 1.0.69",
] ]
[[package]] [[package]]
@ -570,6 +570,26 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 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]] [[package]]
name = "errno" name = "errno"
version = "0.3.10" version = "0.3.10"
@ -728,7 +748,7 @@ dependencies = [
"libz-sys", "libz-sys",
"num_cpus", "num_cpus",
"snap", "snap",
"thiserror", "thiserror 1.0.69",
] ]
[[package]] [[package]]
@ -886,6 +906,17 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@ -1114,6 +1145,7 @@ dependencies = [
"insta", "insta",
"is_executable", "is_executable",
"itertools", "itertools",
"landlock",
"libc", "libc",
"linked-hash-map", "linked-hash-map",
"lz4_flex", "lz4_flex",
@ -1132,6 +1164,7 @@ dependencies = [
"tar", "tar",
"tempfile", "tempfile",
"test-strategy", "test-strategy",
"thiserror 2.0.12",
"time", "time",
"unrar", "unrar",
"xz2", "xz2",
@ -1716,7 +1749,16 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [ 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]] [[package]]
@ -1730,6 +1772,17 @@ dependencies = [
"syn 2.0.98", "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]] [[package]]
name = "time" name = "time"
version = "0.3.41" version = "0.3.41"

View File

@ -7,7 +7,7 @@ pub mod error;
pub mod extension; pub mod extension;
pub mod list; pub mod list;
pub mod utils; pub mod utils;
pub mod sandbox; //pub mod sandbox;
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
use std::path::Path; use std::path::Path;
@ -23,6 +23,9 @@ use self::{
}, },
}; };
//use utils::landlock::*;
// Used in BufReader and BufWriter to perform less syscalls // Used in BufReader and BufWriter to perform less syscalls
const BUFFER_CAPACITY: usize = 1024 * 32; const BUFFER_CAPACITY: usize = 1024 * 32;
@ -52,6 +55,19 @@ fn run() -> Result<()> {
.unwrap_or_else(|| env::current_dir().unwrap_or_default()); .unwrap_or_else(|| env::current_dir().unwrap_or_default());
// restrict filesystem access to working_dir; // 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); init_sandbox(&working_dir);
commands::run(args, skip_questions_positively, file_visibility_policy) 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"); 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) => { Ok(status) => {
//check //check
} }

View File

@ -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<RestrictionStatus, MyRestrictError> {
// 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::<Result<_, MyRestrictError>, _>(|p| {
Ok(PathBeneath::new(PathFd::new(p)?, access_all))
}),
)?
.restrict_self()?)
}

66
src/utils/landlock.rs Normal file
View File

@ -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::<u32>(), minor.parse::<u32>()) {
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<RestrictionStatus, MyRestrictError> {
// 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::<Result<_, MyRestrictError>, _>(|p| {
Ok(PathBeneath::new(PathFd::new(p)?, access_all))
}),
)?
.restrict_self()?)
}

View File

@ -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::<u32>(), minor.parse::<u32>()) {
return (major > 5) || (major == 5 && minor >= 19);
}
}
}
}
false
}
#[cfg(not(target_os = "linux"))]
pub fn is_landlock_supported() -> bool {
false
}

View File

@ -10,7 +10,7 @@ mod fs;
pub mod io; pub mod io;
pub mod logger; pub mod logger;
mod question; mod question;
pub mod landlock_support; pub mod landlock;
pub use self::{ pub use self::{
file_visibility::FileVisibilityPolicy, file_visibility::FileVisibilityPolicy,