Skip to content

Commit

Permalink
fs,lib: Add AccessFs::from_file() and test permanent incompatibility
Browse files Browse the repository at this point in the history
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 <mic@digikod.net>
  • Loading branch information
l0kod committed Aug 30, 2023
1 parent 8e47940 commit 0b3238c
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 14 deletions.
10 changes: 8 additions & 2 deletions src/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ABI>) -> bool {
mock < partial_support
|| mock <= *TEST_ABI
|| if let Some(full) = full_support {
full <= *TEST_ABI
} else {
partial_support <= *TEST_ABI
}
}

#[cfg(test)]
Expand Down
7 changes: 7 additions & 0 deletions src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Self::from_all(abi) & ACCESS_FILE
}
}

impl PrivateAccess for AccessFs {
fn ruleset_handle_access(
ruleset: &mut Ruleset,
Expand Down
36 changes: 25 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,16 @@ mod tests {
use crate::*;

// Emulate old kernel supports.
fn check_ruleset_support<F>(partial: ABI, full: ABI, check: F, error_if_abi_lt_partial: bool)
where
fn check_ruleset_support<F>(
partial: ABI,
full: Option<ABI>,
check: F,
error_if_abi_lt_partial: bool,
) where
F: Fn(Ruleset) -> Result<RestrictionStatus, TestRulesetError>,
{
// 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));

Expand All @@ -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 {
Expand All @@ -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(
Expand All @@ -166,7 +177,7 @@ mod tests {

check_ruleset_support(
abi,
abi,
Some(abi),
|ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::from_all(abi))?
Expand All @@ -184,20 +195,23 @@ mod tests {

check_ruleset_support(
abi,
abi,
Some(abi),
|ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::from_all(abi))?
.create()?
// 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]
Expand All @@ -206,7 +220,7 @@ mod tests {

check_ruleset_support(
abi,
abi,
Some(abi),
|ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::from_all(ABI::V1))?
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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)?
Expand All @@ -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)?
Expand Down
2 changes: 1 addition & 1 deletion src/ruleset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}))
Expand Down

0 comments on commit 0b3238c

Please sign in to comment.