add landlock

This commit is contained in:
valoq 2025-06-28 20:04:30 +02:00
parent 945ffde551
commit 61599dfa25
No known key found for this signature in database
GPG Key ID: 19F09A0FB865CBD8
8 changed files with 116 additions and 0 deletions

View File

@ -20,6 +20,8 @@ Categories Used:
## [Unreleased](https://github.com/ouch-org/ouch/compare/0.6.1...HEAD) ## [Unreleased](https://github.com/ouch-org/ouch/compare/0.6.1...HEAD)
- Add landlock support for linux filesystem isolation [\#723](https://github.com/ouch-org/ouch/pull/723) ([valoq](https://github.com/valoq))
### New Features ### New Features
- Merge folders in decompression [\#798](https://github.com/ouch-org/ouch/pull/798) ([tommady](https://github.com/tommady)) - Merge folders in decompression [\#798](https://github.com/ouch-org/ouch/pull/798) ([tommady](https://github.com/tommady))

View File

@ -28,6 +28,7 @@ gzp = { version = "0.11.3", default-features = false, features = [
"snappy_default", "snappy_default",
] } ] }
ignore = "0.4.23" ignore = "0.4.23"
landlock = "0.4.2"
libc = "0.2.155" libc = "0.2.155"
linked-hash-map = "0.5.6" linked-hash-map = "0.5.6"
lz4_flex = "0.11.3" lz4_flex = "0.11.3"
@ -39,6 +40,7 @@ sevenz-rust2 = { version = "0.13.1", features = ["compress", "aes256"] }
snap = "1.1.1" snap = "1.1.1"
tar = "0.4.42" tar = "0.4.42"
tempfile = "3.10.1" tempfile = "3.10.1"
thiserror = "2.0.12"
time = { version = "0.3.36", default-features = false } time = { version = "0.3.36", default-features = false }
unrar = { version = "0.5.7", optional = true } unrar = { version = "0.5.7", optional = true }
xz2 = "0.1.7" xz2 = "0.1.7"

View File

@ -49,6 +49,8 @@ pub struct CliArgs {
#[arg(short = 'c', long, global = true)] #[arg(short = 'c', long, global = true)]
pub threads: Option<usize>, pub threads: Option<usize>,
pub output_dir: Option<PathBuf>,
// Ouch and claps subcommands // Ouch and claps subcommands
#[command(subcommand)] #[command(subcommand)]
pub cmd: Subcommand, pub cmd: Subcommand,
@ -155,6 +157,7 @@ mod tests {
// This is usually replaced in assertion tests // This is usually replaced in assertion tests
password: None, password: None,
threads: None, threads: None,
output_dir: None,
cmd: Subcommand::Decompress { cmd: Subcommand::Decompress {
// Put a crazy value here so no test can assert it unintentionally // Put a crazy value here so no test can assert it unintentionally
files: vec!["\x00\x11\x22".into()], files: vec!["\x00\x11\x22".into()],

View File

@ -7,8 +7,10 @@ pub mod error;
pub mod extension; pub mod extension;
pub mod list; pub mod list;
pub mod utils; pub mod utils;
pub mod sandbox;
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
use std::path::Path;
use cli::CliArgs; use cli::CliArgs;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -43,5 +45,40 @@ fn main() {
fn run() -> Result<()> { fn run() -> Result<()> {
let (args, skip_questions_positively, file_visibility_policy) = CliArgs::parse_and_validate_args()?; let (args, skip_questions_positively, file_visibility_policy) = CliArgs::parse_and_validate_args()?;
// Get the output dir if specified, else use current dir
let working_dir = args.output_dir
.clone()
.unwrap_or_else(|| env::current_dir().unwrap_or_default());
// restrict filesystem access to working_dir;
init_sandbox(&working_dir);
commands::run(args, skip_questions_positively, file_visibility_policy) commands::run(args, skip_questions_positively, file_visibility_policy)
} }
fn init_sandbox(allowed_dir: &Path) {
if std::env::var("CI").is_ok() {
return;
}
if utils::landlock_support::is_landlock_supported() {
let path_str = allowed_dir.to_str().expect("Cannot convert path");
match sandbox::restrict_paths(&[path_str]) {
Ok(status) => {
//check
}
Err(e) => {
//log warning
std::process::exit(EXIT_FAILURE);
}
}
} else {
// warn!("Landlock is NOT supported on this platform or kernel (<5.19).");
}
}

39
src/sandbox.rs Normal file
View File

@ -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<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

@ -0,0 +1,24 @@
//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,6 +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 use self::{ pub use self::{
file_visibility::FileVisibilityPolicy, file_visibility::FileVisibilityPolicy,

8
tests/landlock.rs Normal file
View File

@ -0,0 +1,8 @@
#[test]
fn test_landlock_restriction() {
if !cfg!(target_os = "linux") {
eprintln!("Skipping Landlock test: not running on Linux.");
return;
}
// TODO: Add test
}