mirror of
https://github.com/ouch-org/ouch.git
synced 2025-07-19 16:10:53 +00:00
add landlock
This commit is contained in:
parent
945ffde551
commit
61599dfa25
@ -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))
|
||||||
|
@ -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"
|
||||||
|
@ -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()],
|
||||||
|
37
src/main.rs
37
src/main.rs
@ -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
39
src/sandbox.rs
Normal 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()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
24
src/utils/landlock_support.rs
Normal file
24
src/utils/landlock_support.rs
Normal 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
|
||||||
|
}
|
@ -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
8
tests/landlock.rs
Normal 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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user