Skip to content

Commit 61599df

Browse files
committed
add landlock
1 parent 945ffde commit 61599df

File tree

8 files changed

+116
-0
lines changed

8 files changed

+116
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Categories Used:
2020

2121
## [Unreleased](https://github.com/ouch-org/ouch/compare/0.6.1...HEAD)
2222

23+
- Add landlock support for linux filesystem isolation [\#723](https://github.com/ouch-org/ouch/pull/723) ([valoq](https://github.com/valoq))
24+
2325
### New Features
2426

2527
- Merge folders in decompression [\#798](https://github.com/ouch-org/ouch/pull/798) ([tommady](https://github.com/tommady))

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ gzp = { version = "0.11.3", default-features = false, features = [
2828
"snappy_default",
2929
] }
3030
ignore = "0.4.23"
31+
landlock = "0.4.2"
3132
libc = "0.2.155"
3233
linked-hash-map = "0.5.6"
3334
lz4_flex = "0.11.3"
@@ -39,6 +40,7 @@ sevenz-rust2 = { version = "0.13.1", features = ["compress", "aes256"] }
3940
snap = "1.1.1"
4041
tar = "0.4.42"
4142
tempfile = "3.10.1"
43+
thiserror = "2.0.12"
4244
time = { version = "0.3.36", default-features = false }
4345
unrar = { version = "0.5.7", optional = true }
4446
xz2 = "0.1.7"

src/cli/args.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub struct CliArgs {
4949
#[arg(short = 'c', long, global = true)]
5050
pub threads: Option<usize>,
5151

52+
pub output_dir: Option<PathBuf>,
53+
5254
// Ouch and claps subcommands
5355
#[command(subcommand)]
5456
pub cmd: Subcommand,
@@ -155,6 +157,7 @@ mod tests {
155157
// This is usually replaced in assertion tests
156158
password: None,
157159
threads: None,
160+
output_dir: None,
158161
cmd: Subcommand::Decompress {
159162
// Put a crazy value here so no test can assert it unintentionally
160163
files: vec!["\x00\x11\x22".into()],

src/main.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ pub mod error;
77
pub mod extension;
88
pub mod list;
99
pub mod utils;
10+
pub mod sandbox;
1011

1112
use std::{env, path::PathBuf};
13+
use std::path::Path;
1214

1315
use cli::CliArgs;
1416
use once_cell::sync::Lazy;
@@ -43,5 +45,40 @@ fn main() {
4345

4446
fn run() -> Result<()> {
4547
let (args, skip_questions_positively, file_visibility_policy) = CliArgs::parse_and_validate_args()?;
48+
49+
// Get the output dir if specified, else use current dir
50+
let working_dir = args.output_dir
51+
.clone()
52+
.unwrap_or_else(|| env::current_dir().unwrap_or_default());
53+
54+
// restrict filesystem access to working_dir;
55+
init_sandbox(&working_dir);
56+
4657
commands::run(args, skip_questions_positively, file_visibility_policy)
4758
}
59+
60+
fn init_sandbox(allowed_dir: &Path) {
61+
62+
if std::env::var("CI").is_ok() {
63+
return;
64+
}
65+
66+
67+
if utils::landlock_support::is_landlock_supported() {
68+
69+
let path_str = allowed_dir.to_str().expect("Cannot convert path");
70+
match sandbox::restrict_paths(&[path_str]) {
71+
Ok(status) => {
72+
//check
73+
}
74+
Err(e) => {
75+
//log warning
76+
std::process::exit(EXIT_FAILURE);
77+
}
78+
}
79+
} else {
80+
// warn!("Landlock is NOT supported on this platform or kernel (<5.19).");
81+
}
82+
83+
}
84+

src/sandbox.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// generic landlock implementation https://landlock.io/rust-landlock/landlock/struct.Ruleset.html
2+
3+
use landlock::{
4+
Access, AccessFs, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
5+
RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
6+
};
7+
use thiserror::Error;
8+
9+
#[derive(Debug, Error)]
10+
pub enum MyRestrictError {
11+
#[error(transparent)]
12+
Ruleset(#[from] RulesetError),
13+
#[error(transparent)]
14+
AddRule(#[from] PathFdError),
15+
}
16+
17+
pub fn restrict_paths(hierarchies: &[&str]) -> Result<RestrictionStatus, MyRestrictError> {
18+
// The Landlock ABI should be incremented (and tested) regularly.
19+
// ABI set to 2 in compatibility with linux 5.19 and higher
20+
let abi = ABI::V2;
21+
let access_all = AccessFs::from_all(abi);
22+
let access_read = AccessFs::from_read(abi);
23+
24+
Ok(Ruleset::default()
25+
.handle_access(access_all)?
26+
.create()?
27+
// Read-only access to / (entire filesystem).
28+
.add_rules(landlock::path_beneath_rules(&["/"], access_read))?
29+
.add_rules(
30+
hierarchies
31+
.iter()
32+
.map::<Result<_, MyRestrictError>, _>(|p| {
33+
Ok(PathBeneath::new(PathFd::new(p)?, access_all))
34+
}),
35+
)?
36+
.restrict_self()?)
37+
}
38+
39+

src/utils/landlock_support.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//Check Landlock kernel support (Linux kernel >= 5.19)
2+
3+
#[cfg(target_os = "linux")]
4+
pub fn is_landlock_supported() -> bool {
5+
use std::process::Command;
6+
7+
if let Ok(output) = Command::new("uname").arg("-r").output() {
8+
if let Ok(version_str) = String::from_utf8(output.stdout) {
9+
// Version string is expected to be in "5.19.0-foo" or similar
10+
let mut parts = version_str.trim().split('.');
11+
if let (Some(major), Some(minor)) = (parts.next(), parts.next()) {
12+
if let (Ok(major), Ok(minor)) = (major.parse::<u32>(), minor.parse::<u32>()) {
13+
return (major > 5) || (major == 5 && minor >= 19);
14+
}
15+
}
16+
}
17+
}
18+
false
19+
}
20+
21+
#[cfg(not(target_os = "linux"))]
22+
pub fn is_landlock_supported() -> bool {
23+
false
24+
}

src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod fs;
1010
pub mod io;
1111
pub mod logger;
1212
mod question;
13+
pub mod landlock_support;
1314

1415
pub use self::{
1516
file_visibility::FileVisibilityPolicy,

tests/landlock.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#[test]
2+
fn test_landlock_restriction() {
3+
if !cfg!(target_os = "linux") {
4+
eprintln!("Skipping Landlock test: not running on Linux.");
5+
return;
6+
}
7+
// TODO: Add test
8+
}

0 commit comments

Comments
 (0)