mirror of
https://github.com/ouch-org/ouch.git
synced 2025-07-19 16:10:53 +00:00
Merge f85080dc70a4a247eb3af72f54b061424af3ba31 into bbce74666682aa26f62fe0cc980b196257f846fb
This commit is contained in:
commit
d42d7ca5f8
@ -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))
|
||||||
|
59
Cargo.lock
generated
59
Cargo.lock
generated
@ -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"
|
||||||
@ -1123,6 +1154,7 @@ dependencies = [
|
|||||||
"insta",
|
"insta",
|
||||||
"is_executable",
|
"is_executable",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
"landlock",
|
||||||
"libc",
|
"libc",
|
||||||
"liblzma",
|
"liblzma",
|
||||||
"linked-hash-map",
|
"linked-hash-map",
|
||||||
@ -1142,6 +1174,7 @@ dependencies = [
|
|||||||
"tar",
|
"tar",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"test-strategy",
|
"test-strategy",
|
||||||
|
"thiserror 2.0.12",
|
||||||
"time",
|
"time",
|
||||||
"unrar",
|
"unrar",
|
||||||
"zip",
|
"zip",
|
||||||
@ -1725,7 +1758,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]]
|
||||||
@ -1739,6 +1781,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"
|
||||||
|
@ -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 }
|
||||||
liblzma = "0.4"
|
liblzma = "0.4"
|
||||||
|
@ -49,6 +49,10 @@ pub struct CliArgs {
|
|||||||
#[arg(short = 'c', long, global = true)]
|
#[arg(short = 'c', long, global = true)]
|
||||||
pub threads: Option<usize>,
|
pub threads: Option<usize>,
|
||||||
|
|
||||||
|
/// Disable the sandbox feature
|
||||||
|
#[arg(long, global = true)]
|
||||||
|
pub disable_sandbox: bool,
|
||||||
|
|
||||||
// Ouch and claps subcommands
|
// Ouch and claps subcommands
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub cmd: Subcommand,
|
pub cmd: Subcommand,
|
||||||
@ -85,6 +89,10 @@ pub enum Subcommand {
|
|||||||
/// Archive target files instead of storing symlinks (supported by `tar` and `zip`)
|
/// Archive target files instead of storing symlinks (supported by `tar` and `zip`)
|
||||||
#[arg(long, short = 'S')]
|
#[arg(long, short = 'S')]
|
||||||
follow_symlinks: bool,
|
follow_symlinks: bool,
|
||||||
|
|
||||||
|
/// Mark sandbox as disabled
|
||||||
|
#[arg(long, global = true)]
|
||||||
|
disable_sandbox: bool,
|
||||||
},
|
},
|
||||||
/// Decompresses one or more files, optionally into another folder
|
/// Decompresses one or more files, optionally into another folder
|
||||||
#[command(visible_alias = "d")]
|
#[command(visible_alias = "d")]
|
||||||
@ -104,6 +112,10 @@ pub enum Subcommand {
|
|||||||
/// Disable Smart Unpack
|
/// Disable Smart Unpack
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
no_smart_unpack: bool,
|
no_smart_unpack: bool,
|
||||||
|
|
||||||
|
/// Mark sandbox as disabled
|
||||||
|
#[arg(long, global = true)]
|
||||||
|
disable_sandbox: bool,
|
||||||
},
|
},
|
||||||
/// List contents of an archive
|
/// List contents of an archive
|
||||||
#[command(visible_aliases = ["l", "ls"])]
|
#[command(visible_aliases = ["l", "ls"])]
|
||||||
@ -115,6 +127,10 @@ pub enum Subcommand {
|
|||||||
/// Show archive contents as a tree
|
/// Show archive contents as a tree
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
tree: bool,
|
tree: bool,
|
||||||
|
|
||||||
|
/// Mark sandbox as disabled
|
||||||
|
#[arg(long, global = true)]
|
||||||
|
disable_sandbox: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,12 +171,14 @@ mod tests {
|
|||||||
// This is usually replaced in assertion tests
|
// This is usually replaced in assertion tests
|
||||||
password: None,
|
password: None,
|
||||||
threads: None,
|
threads: None,
|
||||||
|
disable_sandbox: false,
|
||||||
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()],
|
||||||
output_dir: None,
|
output_dir: None,
|
||||||
remove: false,
|
remove: false,
|
||||||
no_smart_unpack: false,
|
no_smart_unpack: false,
|
||||||
|
disable_sandbox: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,6 +193,7 @@ mod tests {
|
|||||||
output_dir: None,
|
output_dir: None,
|
||||||
remove: false,
|
remove: false,
|
||||||
no_smart_unpack: false,
|
no_smart_unpack: false,
|
||||||
|
disable_sandbox: false,
|
||||||
},
|
},
|
||||||
..mock_cli_args()
|
..mock_cli_args()
|
||||||
}
|
}
|
||||||
@ -187,6 +206,7 @@ mod tests {
|
|||||||
output_dir: None,
|
output_dir: None,
|
||||||
remove: false,
|
remove: false,
|
||||||
no_smart_unpack: false,
|
no_smart_unpack: false,
|
||||||
|
disable_sandbox: false,
|
||||||
},
|
},
|
||||||
..mock_cli_args()
|
..mock_cli_args()
|
||||||
}
|
}
|
||||||
@ -199,6 +219,7 @@ mod tests {
|
|||||||
output_dir: None,
|
output_dir: None,
|
||||||
remove: false,
|
remove: false,
|
||||||
no_smart_unpack: false,
|
no_smart_unpack: false,
|
||||||
|
disable_sandbox: false,
|
||||||
},
|
},
|
||||||
..mock_cli_args()
|
..mock_cli_args()
|
||||||
}
|
}
|
||||||
@ -214,6 +235,7 @@ mod tests {
|
|||||||
fast: false,
|
fast: false,
|
||||||
slow: false,
|
slow: false,
|
||||||
follow_symlinks: false,
|
follow_symlinks: false,
|
||||||
|
disable_sandbox: false,
|
||||||
},
|
},
|
||||||
..mock_cli_args()
|
..mock_cli_args()
|
||||||
}
|
}
|
||||||
@ -228,6 +250,7 @@ mod tests {
|
|||||||
fast: false,
|
fast: false,
|
||||||
slow: false,
|
slow: false,
|
||||||
follow_symlinks: false,
|
follow_symlinks: false,
|
||||||
|
disable_sandbox: false,
|
||||||
},
|
},
|
||||||
..mock_cli_args()
|
..mock_cli_args()
|
||||||
}
|
}
|
||||||
@ -242,6 +265,7 @@ mod tests {
|
|||||||
fast: false,
|
fast: false,
|
||||||
slow: false,
|
slow: false,
|
||||||
follow_symlinks: false,
|
follow_symlinks: false,
|
||||||
|
disable_sandbox: false,
|
||||||
},
|
},
|
||||||
..mock_cli_args()
|
..mock_cli_args()
|
||||||
}
|
}
|
||||||
@ -267,6 +291,7 @@ mod tests {
|
|||||||
fast: false,
|
fast: false,
|
||||||
slow: false,
|
slow: false,
|
||||||
follow_symlinks: false,
|
follow_symlinks: false,
|
||||||
|
disable_sandbox: false,
|
||||||
},
|
},
|
||||||
format: Some("tar.gz".into()),
|
format: Some("tar.gz".into()),
|
||||||
..mock_cli_args()
|
..mock_cli_args()
|
||||||
|
@ -35,6 +35,7 @@ pub fn compress_files(
|
|||||||
question_policy: QuestionPolicy,
|
question_policy: QuestionPolicy,
|
||||||
file_visibility_policy: FileVisibilityPolicy,
|
file_visibility_policy: FileVisibilityPolicy,
|
||||||
level: Option<i16>,
|
level: Option<i16>,
|
||||||
|
disable_sandbox: bool,
|
||||||
) -> crate::Result<bool> {
|
) -> crate::Result<bool> {
|
||||||
// If the input files contain a directory, then the total size will be underestimated
|
// If the input files contain a directory, then the total size will be underestimated
|
||||||
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
|
let file_writer = BufWriter::with_capacity(BUFFER_CAPACITY, output_file);
|
||||||
|
@ -5,6 +5,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
|
//use crate::utils::landlock;
|
||||||
|
|
||||||
#[cfg(not(feature = "bzip3"))]
|
#[cfg(not(feature = "bzip3"))]
|
||||||
use crate::archive;
|
use crate::archive;
|
||||||
@ -18,7 +19,7 @@ use crate::{
|
|||||||
utils::{
|
utils::{
|
||||||
self,
|
self,
|
||||||
io::lock_and_flush_output_stdio,
|
io::lock_and_flush_output_stdio,
|
||||||
is_path_stdin,
|
is_path_stdin, landlock,
|
||||||
logger::{info, info_accessible},
|
logger::{info, info_accessible},
|
||||||
nice_directory_display, user_wants_to_continue,
|
nice_directory_display, user_wants_to_continue,
|
||||||
},
|
},
|
||||||
@ -39,6 +40,7 @@ pub struct DecompressOptions<'a> {
|
|||||||
pub quiet: bool,
|
pub quiet: bool,
|
||||||
pub password: Option<&'a [u8]>,
|
pub password: Option<&'a [u8]>,
|
||||||
pub remove: bool,
|
pub remove: bool,
|
||||||
|
pub disable_sandbox: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decompress a file
|
/// Decompress a file
|
||||||
@ -79,6 +81,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
options.question_policy,
|
options.question_policy,
|
||||||
options.is_output_dir_provided,
|
options.is_output_dir_provided,
|
||||||
options.is_smart_unpack,
|
options.is_smart_unpack,
|
||||||
|
options.disable_sandbox,
|
||||||
)? {
|
)? {
|
||||||
files
|
files
|
||||||
} else {
|
} else {
|
||||||
@ -176,6 +179,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
options.question_policy,
|
options.question_policy,
|
||||||
options.is_output_dir_provided,
|
options.is_output_dir_provided,
|
||||||
options.is_smart_unpack,
|
options.is_smart_unpack,
|
||||||
|
options.disable_sandbox,
|
||||||
)? {
|
)? {
|
||||||
files
|
files
|
||||||
} else {
|
} else {
|
||||||
@ -211,6 +215,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
options.question_policy,
|
options.question_policy,
|
||||||
options.is_output_dir_provided,
|
options.is_output_dir_provided,
|
||||||
options.is_smart_unpack,
|
options.is_smart_unpack,
|
||||||
|
options.disable_sandbox,
|
||||||
)? {
|
)? {
|
||||||
files
|
files
|
||||||
} else {
|
} else {
|
||||||
@ -244,6 +249,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
options.question_policy,
|
options.question_policy,
|
||||||
options.is_output_dir_provided,
|
options.is_output_dir_provided,
|
||||||
options.is_smart_unpack,
|
options.is_smart_unpack,
|
||||||
|
options.disable_sandbox,
|
||||||
)? {
|
)? {
|
||||||
files
|
files
|
||||||
} else {
|
} else {
|
||||||
@ -287,6 +293,7 @@ pub fn decompress_file(options: DecompressOptions) -> crate::Result<()> {
|
|||||||
options.question_policy,
|
options.question_policy,
|
||||||
options.is_output_dir_provided,
|
options.is_output_dir_provided,
|
||||||
options.is_smart_unpack,
|
options.is_smart_unpack,
|
||||||
|
options.disable_sandbox,
|
||||||
)? {
|
)? {
|
||||||
files
|
files
|
||||||
} else {
|
} else {
|
||||||
@ -323,7 +330,20 @@ fn execute_decompression(
|
|||||||
question_policy: QuestionPolicy,
|
question_policy: QuestionPolicy,
|
||||||
is_output_dir_provided: bool,
|
is_output_dir_provided: bool,
|
||||||
is_smart_unpack: bool,
|
is_smart_unpack: bool,
|
||||||
|
disable_sandbox: bool,
|
||||||
) -> crate::Result<ControlFlow<(), usize>> {
|
) -> crate::Result<ControlFlow<(), usize>> {
|
||||||
|
// init landlock sandbox to restrict file system write access to output_dir
|
||||||
|
// The output directory iseither specified with the -d option or the current working directory is used
|
||||||
|
// TODO: restrict acess to the current working directory to allow only creating new files
|
||||||
|
// TODO: move to unpack and smart_unpack to cover the differetn dirctories used for
|
||||||
|
// decompression
|
||||||
|
//if !input_is_stdin && options.remove {
|
||||||
|
//permit write access to input_file_path
|
||||||
|
//} else {
|
||||||
|
//}
|
||||||
|
|
||||||
|
landlock::init_sandbox(&[output_dir], disable_sandbox);
|
||||||
|
|
||||||
if is_smart_unpack {
|
if is_smart_unpack {
|
||||||
return smart_unpack(unpack_fn, output_dir, output_file_path, question_policy);
|
return smart_unpack(unpack_fn, output_dir, output_file_path, question_policy);
|
||||||
}
|
}
|
||||||
@ -387,6 +407,9 @@ fn smart_unpack(
|
|||||||
nice_directory_display(temp_dir_path)
|
nice_directory_display(temp_dir_path)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
//first attempt to restict to the tmp file and allow only to rename it in the parent
|
||||||
|
//landlock::init_sandbox(Some(temp_dir_path));
|
||||||
|
|
||||||
let files = unpack_fn(temp_dir_path)?;
|
let files = unpack_fn(temp_dir_path)?;
|
||||||
|
|
||||||
let root_contains_only_one_element = fs::read_dir(temp_dir_path)?.take(2).count() == 1;
|
let root_contains_only_one_element = fs::read_dir(temp_dir_path)?.take(2).count() == 1;
|
||||||
|
@ -4,13 +4,14 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
|
//use crate::utils::landlock;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
archive,
|
archive,
|
||||||
commands::warn_user_about_loading_zip_in_memory,
|
commands::warn_user_about_loading_zip_in_memory,
|
||||||
extension::CompressionFormat::{self, *},
|
extension::CompressionFormat::{self, *},
|
||||||
list::{self, FileInArchive, ListOptions},
|
list::{self, FileInArchive, ListOptions},
|
||||||
utils::{io::lock_and_flush_output_stdio, user_wants_to_continue},
|
utils::{io::lock_and_flush_output_stdio, landlock, user_wants_to_continue},
|
||||||
QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
|
QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,7 +23,14 @@ pub fn list_archive_contents(
|
|||||||
list_options: ListOptions,
|
list_options: ListOptions,
|
||||||
question_policy: QuestionPolicy,
|
question_policy: QuestionPolicy,
|
||||||
password: Option<&[u8]>,
|
password: Option<&[u8]>,
|
||||||
|
disable_sandbox: bool,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
|
//rar uses a temporary file which needs to be defined early to be permitted in landlock
|
||||||
|
let mut temp_file = tempfile::NamedTempFile::new()?;
|
||||||
|
|
||||||
|
// Initialize landlock sandbox with write access restricted to /tmp/<tmp_file> as required by some formats
|
||||||
|
landlock::init_sandbox(&[temp_file.path()], disable_sandbox);
|
||||||
|
|
||||||
let reader = fs::File::open(archive_path)?;
|
let reader = fs::File::open(archive_path)?;
|
||||||
|
|
||||||
// Zip archives are special, because they require io::Seek, so it requires it's logic separated
|
// Zip archives are special, because they require io::Seek, so it requires it's logic separated
|
||||||
@ -107,7 +115,6 @@ pub fn list_archive_contents(
|
|||||||
#[cfg(feature = "unrar")]
|
#[cfg(feature = "unrar")]
|
||||||
Rar => {
|
Rar => {
|
||||||
if formats.len() > 1 {
|
if formats.len() > 1 {
|
||||||
let mut temp_file = tempfile::NamedTempFile::new()?;
|
|
||||||
io::copy(&mut reader, &mut temp_file)?;
|
io::copy(&mut reader, &mut temp_file)?;
|
||||||
Box::new(crate::archive::rar::list_archive(temp_file.path(), password)?)
|
Box::new(crate::archive::rar::list_archive(temp_file.path(), password)?)
|
||||||
} else {
|
} else {
|
||||||
|
@ -69,6 +69,7 @@ pub fn run(
|
|||||||
fast,
|
fast,
|
||||||
slow,
|
slow,
|
||||||
follow_symlinks,
|
follow_symlinks,
|
||||||
|
disable_sandbox,
|
||||||
} => {
|
} => {
|
||||||
// After cleaning, if there are no input files left, exit
|
// After cleaning, if there are no input files left, exit
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
@ -116,6 +117,7 @@ pub fn run(
|
|||||||
question_policy,
|
question_policy,
|
||||||
file_visibility_policy,
|
file_visibility_policy,
|
||||||
level,
|
level,
|
||||||
|
args.disable_sandbox,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Ok(true) = compress_result {
|
if let Ok(true) = compress_result {
|
||||||
@ -151,6 +153,7 @@ pub fn run(
|
|||||||
output_dir,
|
output_dir,
|
||||||
remove,
|
remove,
|
||||||
no_smart_unpack,
|
no_smart_unpack,
|
||||||
|
disable_sandbox,
|
||||||
} => {
|
} => {
|
||||||
let mut output_paths = vec![];
|
let mut output_paths = vec![];
|
||||||
let mut formats = vec![];
|
let mut formats = vec![];
|
||||||
@ -216,10 +219,12 @@ pub fn run(
|
|||||||
<[u8] as ByteSlice>::from_os_str(str).expect("convert password to bytes failed")
|
<[u8] as ByteSlice>::from_os_str(str).expect("convert password to bytes failed")
|
||||||
}),
|
}),
|
||||||
remove,
|
remove,
|
||||||
|
disable_sandbox: args.disable_sandbox,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Subcommand::List { archives: files, tree } => {
|
// check again if we need to provide disable_sandbox as argument here
|
||||||
|
Subcommand::List { archives: files, tree, disable_sandbox} => {
|
||||||
let mut formats = vec![];
|
let mut formats = vec![];
|
||||||
|
|
||||||
if let Some(format) = args.format {
|
if let Some(format) = args.format {
|
||||||
@ -257,9 +262,9 @@ pub fn run(
|
|||||||
args.password
|
args.password
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|str| <[u8] as ByteSlice>::from_os_str(str).expect("convert password to bytes failed")),
|
.map(|str| <[u8] as ByteSlice>::from_os_str(str).expect("convert password to bytes failed")),
|
||||||
|
args.disable_sandbox,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
src/main.rs
20
src/main.rs
@ -43,5 +43,25 @@ 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;
|
||||||
|
// 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)
|
||||||
|
//utils::landlock::init_sandbox(&working_dir);
|
||||||
|
|
||||||
commands::run(args, skip_questions_positively, file_visibility_policy)
|
commands::run(args, skip_questions_positively, file_visibility_policy)
|
||||||
}
|
}
|
||||||
|
114
src/utils/landlock.rs
Normal file
114
src/utils/landlock.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// Landlock support and generic Landlock sandbox implementation.
|
||||||
|
// https://landlock.io/rust-landlock/landlock/struct.Ruleset.html
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use landlock::{
|
||||||
|
Access, AccessFs, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
|
||||||
|
RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// The status code returned from `ouch` on error
|
||||||
|
pub const EXIT_FAILURE: i32 = libc::EXIT_FAILURE;
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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);
|
||||||
|
|
||||||
|
let mut ruleset = Ruleset::default()
|
||||||
|
.handle_access(access_all)?
|
||||||
|
.create()?
|
||||||
|
// Read-only access to / (entire filesystem).
|
||||||
|
.add_rules(landlock::path_beneath_rules(&["/"], access_read))?;
|
||||||
|
|
||||||
|
// Add write permissions to specified directory of provided
|
||||||
|
if !hierarchies.is_empty() {
|
||||||
|
ruleset = ruleset.add_rules(
|
||||||
|
hierarchies
|
||||||
|
.iter()
|
||||||
|
.map::<Result<_, MyRestrictError>, _>(|p| {
|
||||||
|
Ok(PathBeneath::new(PathFd::new(p)?, access_all))
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ruleset.restrict_self()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restricts the process to only access the given hierarchies using Landlock, if supported.
|
||||||
|
/// Accepts multiple allowed directories as &[&Path].
|
||||||
|
pub fn init_sandbox(allowed_dirs: &[&Path], disable_sandbox: bool) {
|
||||||
|
// if std::env::var("CI").is_ok() {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
if disable_sandbox {
|
||||||
|
println!("Sandbox feature disabled via --no-sandbox flag.");
|
||||||
|
// warn!("Security Process isolation disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_landlock_supported() {
|
||||||
|
let paths: Vec<&str> = allowed_dirs
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_str().expect("Cannot convert path"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let status = if !paths.is_empty() {
|
||||||
|
restrict_paths(&paths)
|
||||||
|
} else {
|
||||||
|
restrict_paths(&[])
|
||||||
|
};
|
||||||
|
|
||||||
|
match status {
|
||||||
|
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).");
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ mod file_visibility;
|
|||||||
mod formatting;
|
mod formatting;
|
||||||
mod fs;
|
mod fs;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
pub mod landlock;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
mod question;
|
mod question;
|
||||||
|
|
||||||
|
109
src/utils/src_utils_landlock.rs
Normal file
109
src/utils/src_utils_landlock.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// The status code returned from `ouch` on error
|
||||||
|
pub const EXIT_FAILURE: i32 = libc::EXIT_FAILURE;
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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);
|
||||||
|
|
||||||
|
let mut ruleset = Ruleset::default()
|
||||||
|
.handle_access(access_all)?
|
||||||
|
.create()?
|
||||||
|
// Read-only access to / (entire filesystem).
|
||||||
|
.add_rules(landlock::path_beneath_rules(&["/"], access_read))?;
|
||||||
|
|
||||||
|
// Add write permissions to specified directory of provided
|
||||||
|
if !hierarchies.is_empty() {
|
||||||
|
ruleset = ruleset.add_rules(
|
||||||
|
hierarchies
|
||||||
|
.iter()
|
||||||
|
.map::<Result<_, MyRestrictError>, _>(|p| {
|
||||||
|
Ok(PathBeneath::new(PathFd::new(p)?, access_all))
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ruleset.restrict_self()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restricts the process to only access the given hierarchies using Landlock, if supported.
|
||||||
|
/// Accepts multiple allowed directories as &[&Path].
|
||||||
|
pub fn init_sandbox(allowed_dirs: &[&Path]) {
|
||||||
|
// if std::env::var("CI").is_ok() {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if is_landlock_supported() {
|
||||||
|
let paths: Vec<&str> = allowed_dirs
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_str().expect("Cannot convert path"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let status = if !paths.is_empty() {
|
||||||
|
restrict_paths(&paths)
|
||||||
|
} else {
|
||||||
|
restrict_paths(&[])
|
||||||
|
};
|
||||||
|
|
||||||
|
match status {
|
||||||
|
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).");
|
||||||
|
}
|
||||||
|
}
|
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