Skip to content

Commit 5280730

Browse files
ls: Add SMACK support (#9868)
* Add SMACK support for ls utility * More idiomatic match statement Co-authored-by: Sylvestre Ledru <[email protected]> * More idiomatic match statement for getting label Co-authored-by: Sylvestre Ledru <[email protected]> * ls: fix smack formatting and cache is_smack_enabled result * ls: use translate!() macro for SELinux warning messages * Move SMACK locale strings to ls-specific locale file to avoid performance regression --------- Co-authored-by: Sylvestre Ledru <[email protected]>
1 parent 5b70ed4 commit 5280730

File tree

9 files changed

+150
-32
lines changed

9 files changed

+150
-32
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ feat_selinux = [
6565
"selinux",
6666
"stat/selinux",
6767
]
68+
# "feat_smack" == enable support for SMACK Security Context (by using `--features feat_smack`)
69+
# NOTE:
70+
# * Running a uutils compiled with `feat_smack` requires a SMACK enabled Kernel at run time.
71+
feat_smack = ["ls/smack"]
6872
##
6973
## feature sets
7074
## (common/core and Tier1) feature sets

src/uu/ls/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,4 @@ harness = false
6060

6161
[features]
6262
feat_selinux = ["selinux", "uucore/selinux"]
63+
smack = ["uucore/smack"]

src/uu/ls/locales/en-US.ftl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,13 @@ ls-invalid-columns-width = ignoring invalid width in environment variable COLUMN
124124
ls-invalid-ignore-pattern = Invalid pattern for ignore: {$pattern}
125125
ls-invalid-hide-pattern = Invalid pattern for hide: {$pattern}
126126
ls-total = total {$size}
127+
128+
# Security context warnings
129+
ls-warning-failed-to-get-security-context = failed to get security context of: {$path}
130+
ls-warning-getting-security-context = getting security context of: {$path}: {$error}
131+
132+
# SMACK error messages (used by uucore::smack when called from ls)
133+
smack-error-not-enabled = SMACK is not enabled on this system
134+
smack-error-label-retrieval-failure = failed to get SMACK label: { $error }
135+
smack-error-label-set-failure = failed to set SMACK label to '{ $context }': { $error }
136+
smack-error-no-label-set = no SMACK label set

src/uu/ls/src/ls.rs

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,10 @@ pub struct Config {
365365
time_format_recent: String, // Time format for recent dates
366366
time_format_older: Option<String>, // Time format for older dates (optional, if not present, time_format_recent is used)
367367
context: bool,
368+
#[cfg(all(feature = "selinux", target_os = "linux"))]
368369
selinux_supported: bool,
370+
#[cfg(all(feature = "smack", target_os = "linux"))]
371+
smack_supported: bool,
369372
group_directories_first: bool,
370373
line_ending: LineEnding,
371374
dired: bool,
@@ -1157,16 +1160,10 @@ impl Config {
11571160
time_format_recent,
11581161
time_format_older,
11591162
context,
1160-
selinux_supported: {
1161-
#[cfg(all(feature = "selinux", target_os = "linux"))]
1162-
{
1163-
uucore::selinux::is_selinux_enabled()
1164-
}
1165-
#[cfg(not(all(feature = "selinux", target_os = "linux")))]
1166-
{
1167-
false
1168-
}
1169-
},
1163+
#[cfg(all(feature = "selinux", target_os = "linux"))]
1164+
selinux_supported: uucore::selinux::is_selinux_enabled(),
1165+
#[cfg(all(feature = "smack", target_os = "linux"))]
1166+
smack_supported: uucore::smack::is_smack_enabled(),
11701167
group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST),
11711168
line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)),
11721169
dired,
@@ -3387,37 +3384,59 @@ fn get_security_context<'a>(
33873384
}
33883385
}
33893386

3387+
#[cfg(all(feature = "selinux", target_os = "linux"))]
33903388
if config.selinux_supported {
3391-
#[cfg(all(feature = "selinux", target_os = "linux"))]
3392-
{
3393-
match selinux::SecurityContext::of_path(path, must_dereference, false) {
3394-
Err(_r) => {
3395-
// TODO: show the actual reason why it failed
3396-
show_warning!("failed to get security context of: {}", path.quote());
3397-
return Cow::Borrowed(SUBSTITUTE_STRING);
3398-
}
3399-
Ok(None) => return Cow::Borrowed(SUBSTITUTE_STRING),
3400-
Ok(Some(context)) => {
3401-
let context = context.as_bytes();
3389+
match selinux::SecurityContext::of_path(path, must_dereference, false) {
3390+
Err(_r) => {
3391+
// TODO: show the actual reason why it failed
3392+
show_warning!(
3393+
"{}",
3394+
translate!(
3395+
"ls-warning-failed-to-get-security-context",
3396+
"path" => path.quote().to_string()
3397+
)
3398+
);
3399+
return Cow::Borrowed(SUBSTITUTE_STRING);
3400+
}
3401+
Ok(None) => return Cow::Borrowed(SUBSTITUTE_STRING),
3402+
Ok(Some(context)) => {
3403+
let context = context.as_bytes();
34023404

3403-
let context = context.strip_suffix(&[0]).unwrap_or(context);
3405+
let context = context.strip_suffix(&[0]).unwrap_or(context);
34043406

3405-
let res: String = String::from_utf8(context.to_vec()).unwrap_or_else(|e| {
3406-
show_warning!(
3407-
"getting security context of: {}: {}",
3408-
path.quote(),
3409-
e.to_string()
3410-
);
3407+
let res: String = String::from_utf8(context.to_vec()).unwrap_or_else(|e| {
3408+
show_warning!(
3409+
"{}",
3410+
translate!(
3411+
"ls-warning-getting-security-context",
3412+
"path" => path.quote().to_string(),
3413+
"error" => e.to_string()
3414+
)
3415+
);
34113416

3412-
String::from_utf8_lossy(context).to_string()
3413-
});
3417+
String::from_utf8_lossy(context).to_string()
3418+
});
34143419

3415-
return Cow::Owned(res);
3416-
}
3420+
return Cow::Owned(res);
34173421
}
34183422
}
34193423
}
34203424

3425+
#[cfg(all(feature = "smack", target_os = "linux"))]
3426+
if config.smack_supported {
3427+
// For SMACK, use the path to get the label
3428+
// If must_dereference is true, we follow the symlink
3429+
let target_path = if must_dereference {
3430+
std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
3431+
} else {
3432+
path.to_path_buf()
3433+
};
3434+
3435+
return uucore::smack::get_smack_label_for_path(&target_path)
3436+
.map(Cow::Owned)
3437+
.unwrap_or(Cow::Borrowed(SUBSTITUTE_STRING));
3438+
}
3439+
34213440
Cow::Borrowed(SUBSTITUTE_STRING)
34223441
}
34233442

src/uucore/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ ranges = []
162162
ringbuffer = []
163163
safe-traversal = ["libc"]
164164
selinux = ["dep:selinux"]
165+
smack = ["xattr"]
165166
signals = []
166167
sum = [
167168
"digest",

src/uucore/locales/en-US.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ selinux-error-context-retrieval-failure = failed to retrieve the security contex
4646
selinux-error-context-set-failure = failed to set default file creation context to '{ $context }': { $error }
4747
selinux-error-context-conversion-failure = failed to set default file creation context to '{ $context }': { $error }
4848
49+
4950
# Safe traversal error messages
5051
safe-traversal-error-path-contains-null = path contains null byte
5152
safe-traversal-error-open-failed = failed to open { $path }: { $source }

src/uucore/src/lib/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ pub mod hardware;
8585
pub mod selinux;
8686
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
8787
pub mod signals;
88+
#[cfg(all(target_os = "linux", feature = "smack"))]
89+
pub mod smack;
8890
#[cfg(feature = "feat_systemd_logind")]
8991
pub mod systemd_logind;
9092
#[cfg(all(
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// This file is part of the uutils uucore package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
// spell-checker:ignore smackfs
7+
//! SMACK (Simplified Mandatory Access Control Kernel) support
8+
9+
use std::io;
10+
use std::path::Path;
11+
use std::sync::OnceLock;
12+
13+
use thiserror::Error;
14+
15+
use crate::error::{UError, strip_errno};
16+
use crate::translate;
17+
18+
#[derive(Debug, Error)]
19+
pub enum SmackError {
20+
#[error("{}", translate!("smack-error-not-enabled"))]
21+
SmackNotEnabled,
22+
23+
#[error("{}", translate!("smack-error-label-retrieval-failure", "error" => strip_errno(.0)))]
24+
LabelRetrievalFailure(io::Error),
25+
26+
#[error("{}", translate!("smack-error-label-set-failure", "context" => .0.clone(), "error" => strip_errno(.1)))]
27+
LabelSetFailure(String, io::Error),
28+
}
29+
30+
impl UError for SmackError {
31+
fn code(&self) -> i32 {
32+
match self {
33+
Self::SmackNotEnabled => 1,
34+
Self::LabelRetrievalFailure(_) => 2,
35+
Self::LabelSetFailure(_, _) => 3,
36+
}
37+
}
38+
}
39+
40+
impl From<SmackError> for i32 {
41+
fn from(error: SmackError) -> Self {
42+
error.code()
43+
}
44+
}
45+
46+
/// Checks if SMACK is enabled by verifying smackfs is mounted.
47+
/// The result is cached after the first call.
48+
pub fn is_smack_enabled() -> bool {
49+
static SMACK_ENABLED: OnceLock<bool> = OnceLock::new();
50+
*SMACK_ENABLED.get_or_init(|| Path::new("/sys/fs/smackfs").exists())
51+
}
52+
53+
/// Gets the SMACK label for a filesystem path via xattr.
54+
pub fn get_smack_label_for_path(path: &Path) -> Result<String, SmackError> {
55+
if !is_smack_enabled() {
56+
return Err(SmackError::SmackNotEnabled);
57+
}
58+
59+
match xattr::get(path, "security.SMACK64") {
60+
Ok(Some(value)) => Ok(String::from_utf8_lossy(&value).trim().to_string()),
61+
Ok(None) => Err(SmackError::LabelRetrievalFailure(io::Error::new(
62+
io::ErrorKind::NotFound,
63+
translate!("smack-error-no-label-set"),
64+
))),
65+
Err(e) => Err(SmackError::LabelRetrievalFailure(e)),
66+
}
67+
}
68+
69+
/// Sets the SMACK label for a filesystem path via xattr.
70+
pub fn set_smack_label_for_path(path: &Path, label: &str) -> Result<(), SmackError> {
71+
if !is_smack_enabled() {
72+
return Err(SmackError::SmackNotEnabled);
73+
}
74+
75+
xattr::set(path, "security.SMACK64", label.as_bytes())
76+
.map_err(|e| SmackError::LabelSetFailure(label.to_string(), e))
77+
}

src/uucore/src/lib/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ pub use crate::features::fsxattr;
125125
#[cfg(all(target_os = "linux", feature = "selinux"))]
126126
pub use crate::features::selinux;
127127

128+
#[cfg(all(target_os = "linux", feature = "smack"))]
129+
pub use crate::features::smack;
130+
128131
//## core functions
129132

130133
#[cfg(unix)]

0 commit comments

Comments
 (0)