Add option to filter by SELinux context

Filter files by their SELinux security context. The matching uses the
given context as a regular expressions for the pattern.

Examples:
  --context unlabeled_t
  --context ^user_u:object_r:*
This commit is contained in:
Christian Göttsche 2023-01-27 15:23:53 +01:00
parent 42244e5f32
commit 42269cd66e
10 changed files with 77 additions and 0 deletions

10
Cargo.lock generated
View File

@ -354,6 +354,7 @@ dependencies = [
"test-case",
"users",
"version_check",
"xattr",
]
[[package]]
@ -1219,3 +1220,12 @@ name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "xattr"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea263437ca03c1522846a4ddafbca2542d0ad5ed9b784909d4b27b76f62bc34a"
dependencies = [
"libc",
]

View File

@ -69,6 +69,7 @@ features = ["nu-ansi-term"]
[target.'cfg(unix)'.dependencies]
users = "0.11.0"
nix = { version = "0.26.2", default-features = false, features = ["signal"] }
xattr = "1.0.0"
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
libc = "0.2"

View File

@ -320,6 +320,7 @@ Options:
--changed-within <date|dur> Filter by file modification time (newer than)
--changed-before <date|dur> Filter by file modification time (older than)
-o, --owner <user:group> Filter by owning user and/or group
--context Filter by SELinux context
-x, --exec <cmd>... Execute a command for each search result
-X, --exec-batch <cmd>... Execute a command with all search results at once
-c, --color <when> When to use colors [default: auto] [possible values: auto,

8
doc/fd.1 vendored
View File

@ -334,6 +334,14 @@ Examples:
\-\-owner :students
\-\-owner "!john:students"
.TP
.BI "\-\-context " security_context
Filter files by their SELinux security context. The matching uses \fIsecurity_context\fR as
a regular expressions for the pattern.
Examples:
\-\-context unlabeled_t
\-\-context ^user_u:object_r:*
.TP
.BI "\-\-base\-directory " path
Change the current working directory of fd to the provided path. This means that search results will
be shown with respect to the given base path. Note that relative paths which are passed to fd via the

View File

@ -14,6 +14,8 @@ use crate::error::print_error;
use crate::exec::CommandSet;
use crate::filesystem;
#[cfg(unix)]
use crate::filter::ContextFilter;
#[cfg(unix)]
use crate::filter::OwnerFilter;
use crate::filter::SizeFilter;
@ -445,6 +447,19 @@ pub struct Opts {
)]
pub owner: Option<OwnerFilter>,
/// Filter files by their SELinux context. The matching uses 'security_context' as
/// a regular expressions for the pattern.
///
/// Examples:
/// {n} --context unlabeled_t
/// {n} --context ^user_u:object_r:*
#[cfg(unix)]
#[arg(long, value_parser = ContextFilter::from_string, value_name = "security_context",
help = "Filter by SELinux context",
long_help,
)]
pub context: Option<ContextFilter>,
#[command(flatten)]
pub exec: Exec,

View File

@ -6,6 +6,8 @@ use regex::bytes::RegexSet;
use crate::exec::CommandSet;
use crate::filetypes::FileTypes;
#[cfg(unix)]
use crate::filter::ContextFilter;
#[cfg(unix)]
use crate::filter::OwnerFilter;
use crate::filter::{SizeFilter, TimeFilter};
@ -108,6 +110,10 @@ pub struct Config {
/// User/group ownership constraint
pub owner_constraint: Option<OwnerFilter>,
#[cfg(unix)]
/// SELinux context constraint
pub context_constraint: Option<ContextFilter>,
/// Whether or not to display filesystem errors
pub show_filesystem_errors: bool,

24
src/filter/context.rs Normal file
View File

@ -0,0 +1,24 @@
use anyhow::Result;
use regex::{Regex, RegexBuilder};
use std::path::Path;
#[derive(Clone, Debug)]
pub struct ContextFilter {
regex: Regex,
}
impl ContextFilter {
pub fn from_string(input: &str) -> Result<Self> {
Ok(ContextFilter {
regex: RegexBuilder::new(input).build()?,
})
}
pub fn matches(&self, path: &Path) -> bool {
let Some(raw_context) = xattr::get(path, "security.selinux").unwrap_or(None) else { return false };
let context = String::from_utf8_lossy(&raw_context);
self.regex.is_match(&context)
}
}

View File

@ -1,11 +1,15 @@
pub use self::size::SizeFilter;
pub use self::time::TimeFilter;
#[cfg(unix)]
pub use self::context::ContextFilter;
#[cfg(unix)]
pub use self::owner::OwnerFilter;
mod size;
mod time;
#[cfg(unix)]
mod context;
#[cfg(unix)]
mod owner;

View File

@ -303,6 +303,8 @@ fn construct_config(mut opts: Opts, pattern_regexps: &[String]) -> Result<Config
time_constraints,
#[cfg(unix)]
owner_constraint,
#[cfg(unix)]
context_constraint: opts.context.take(),
show_filesystem_errors: opts.show_errors,
path_separator,
actual_path_separator,

View File

@ -491,6 +491,12 @@ fn spawn_senders(
return ignore::WalkState::Continue;
}
}
if let Some(context_constraint) = &config.context_constraint {
if !context_constraint.matches(entry_path) {
return ignore::WalkState::Continue;
}
}
}
// Filter out unwanted sizes if it is a file and we have been given size constraints.