From 0b3238c6dd70bfee557693ba011b794e3c9329e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Tue, 8 Aug 2023 13:41:53 +0200 Subject: [PATCH] fs,lib: Add AccessFs::from_file() and test permanent incompatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend check_ruleset_support() to be able to handle never-fully-supported rulesets. This is useful to test permanent incompatibilities such as directory-only access rights requested on files, which will be fixed and tested with too_much_access_rights_for_a_file() in the next commit. Add AccessFs::From_file(), which indirectly exposes ACCESS_FILE according to an ABI. This is useful for this test and other similar code that don't rely on path_beneath_rules(). Signed-off-by: Mickaël Salaün --- src/compat.rs | 10 ++++++++-- src/fs.rs | 7 +++++++ src/lib.rs | 36 +++++++++++++++++++++++++----------- src/ruleset.rs | 2 +- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/compat.rs b/src/compat.rs index 60e0893..3ca2268 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -125,8 +125,14 @@ lazy_static! { } #[cfg(test)] -pub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support: ABI) -> bool { - mock < partial_support || mock <= *TEST_ABI || full_support <= *TEST_ABI +pub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support: Option) -> bool { + mock < partial_support + || mock <= *TEST_ABI + || if let Some(full) = full_support { + full <= *TEST_ABI + } else { + partial_support <= *TEST_ABI + } } #[cfg(test)] diff --git a/src/fs.rs b/src/fs.rs index 47e27b8..416945e 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -128,6 +128,13 @@ fn consistent_access_fs_rw() { } } +impl AccessFs { + /// Gets the access rights legitimate for non-directory files. + pub fn from_file(abi: ABI) -> BitFlags { + Self::from_all(abi) & ACCESS_FILE + } +} + impl PrivateAccess for AccessFs { fn ruleset_handle_access( ruleset: &mut Ruleset, diff --git a/src/lib.rs b/src/lib.rs index 253c2d1..120e0ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,12 +115,16 @@ mod tests { use crate::*; // Emulate old kernel supports. - fn check_ruleset_support(partial: ABI, full: ABI, check: F, error_if_abi_lt_partial: bool) - where + fn check_ruleset_support( + partial: ABI, + full: Option, + check: F, + error_if_abi_lt_partial: bool, + ) where F: Fn(Ruleset) -> Result, { // If there is no partial support, it means that `full == partial`. - assert!(partial <= full); + assert!(partial <= full.unwrap_or(partial)); for abi in ABI::iter() { let ret = check(Ruleset::from(abi)); @@ -131,13 +135,19 @@ mod tests { // TODO: Check exact error type; this may require better error types. assert!(matches!(ret, Err(TestRulesetError::Ruleset(_)))); } else { - let ruleset_status = if abi >= full { + let full_support = if let Some(full_inner) = full { + abi >= full_inner + } else { + false + }; + let ruleset_status = if full_support { RulesetStatus::FullyEnforced } else if abi >= partial { RulesetStatus::PartiallyEnforced } else { RulesetStatus::NotEnforced }; + println!("Expecting ruleset status {ruleset_status:?}"); assert!(matches!( ret, Ok(RestrictionStatus { @@ -150,6 +160,7 @@ mod tests { // The errno value should be ENOSYS, EOPNOTSUPP, or EINVAL (e.g. when an unknown // access right is provided). let errno = get_errno_from_landlock_status().unwrap_or(libc::EINVAL); + println!("Expecting error {errno:?}"); assert!(matches!( ret, Err(TestRulesetError::Ruleset(RulesetError::CreateRuleset( @@ -166,7 +177,7 @@ mod tests { check_ruleset_support( abi, - abi, + Some(abi), |ruleset: Ruleset| -> _ { Ok(ruleset .handle_access(AccessFs::from_all(abi))? @@ -184,7 +195,7 @@ mod tests { check_ruleset_support( abi, - abi, + Some(abi), |ruleset: Ruleset| -> _ { Ok(ruleset .handle_access(AccessFs::from_all(abi))? @@ -192,12 +203,15 @@ mod tests { // Same code as allow_root_compat() but with /etc/passwd instead of / .add_rule(PathBeneath::new( PathFd::new("/etc/passwd")?, + // TODO: Only allow legitimate access rights on a file. AccessFs::from_all(abi), ))? .restrict_self()?) }, false, ); + + // TODO: Fix and test partial compatibility with never-fully-supported ruleset. } #[test] @@ -206,7 +220,7 @@ mod tests { check_ruleset_support( abi, - abi, + Some(abi), |ruleset: Ruleset| -> _ { Ok(ruleset .handle_access(AccessFs::from_all(ABI::V1))? @@ -225,7 +239,7 @@ mod tests { check_ruleset_support( abi, - abi, + Some(abi), |ruleset: Ruleset| -> _ { // Sets default support requirement: abort the whole sandboxing for any Landlock error. Ok(ruleset @@ -250,7 +264,7 @@ mod tests { check_ruleset_support( abi, - abi, + Some(abi), |ruleset: Ruleset| -> _ { Ok(ruleset // Restricting without rule exceptions is legitimate to forbid a set of actions. @@ -266,7 +280,7 @@ mod tests { fn abi_v2_exec_refer() { check_ruleset_support( ABI::V1, - ABI::V2, + Some(ABI::V2), |ruleset: Ruleset| -> _ { Ok(ruleset .handle_access(AccessFs::Execute)? @@ -284,7 +298,7 @@ mod tests { // When no access is handled, do not try to create a ruleset without access. check_ruleset_support( ABI::V2, - ABI::V2, + Some(ABI::V2), |ruleset: Ruleset| -> _ { Ok(ruleset .handle_access(AccessFs::Refer)? diff --git a/src/ruleset.rs b/src/ruleset.rs index 81089cb..3faedbc 100644 --- a/src/ruleset.rs +++ b/src/ruleset.rs @@ -732,7 +732,7 @@ fn ruleset_unsupported() { ); // Don't explicitly call create() on a CI that doesn't support Landlock. - if compat::can_emulate(ABI::V1, ABI::V1, ABI::V2) { + if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) { assert_eq!( Ruleset::from(ABI::V1) .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))