#[cfg(test)]
#[macro_use]
extern crate lazy_static;
pub use access::Access;
pub use compat::{CompatLevel, Compatible, ABI};
pub use enumflags2::{make_bitflags, BitFlags};
pub use errors::{
AccessError, AddRuleError, AddRulesError, CompatError, CreateRulesetError, HandleAccessError,
HandleAccessesError, PathBeneathError, PathFdError, RestrictSelfError, RulesetError,
};
pub use fs::{path_beneath_rules, AccessFs, PathBeneath, PathFd};
pub use ruleset::{
RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr,
RulesetStatus,
};
use access::PrivateAccess;
use compat::{CompatResult, CompatState, Compatibility, TailoredCompatLevel, TryCompat};
use ruleset::PrivateRule;
#[cfg(test)]
use compat::{can_emulate, get_errno_from_landlock_status};
#[cfg(test)]
use errors::TestRulesetError;
#[cfg(test)]
use strum::IntoEnumIterator;
mod access;
mod compat;
mod errors;
mod fs;
mod ruleset;
mod uapi;
#[cfg(test)]
mod tests {
use crate::*;
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> + Send + Copy + 'static,
{
assert!(partial <= full.unwrap_or(partial));
for abi in ABI::iter() {
let ret = std::thread::spawn(move || check(Ruleset::from(abi)))
.join()
.unwrap();
println!("Checking ABI {abi:?}: received {ret:#?}");
if can_emulate(abi, partial, full) {
if abi < partial && error_if_abi_lt_partial {
assert!(matches!(ret, Err(TestRulesetError::Ruleset(_))));
} else {
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 {
ruleset,
no_new_privs: true,
}) if ruleset == ruleset_status
))
}
} else {
let errno = get_errno_from_landlock_status().unwrap_or(libc::EINVAL);
println!("Expecting error {errno:?}");
assert!(matches!(
ret,
Err(TestRulesetError::Ruleset(RulesetError::CreateRuleset(
CreateRulesetError::CreateRulesetCall { source }
))) if source.raw_os_error() == Some(errno)
))
}
}
}
#[test]
fn allow_root_compat() {
let abi = ABI::V1;
check_ruleset_support(
abi,
Some(abi),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::from_all(abi))?
.create()?
.add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::from_all(abi)))?
.restrict_self()?)
},
false,
);
}
#[test]
fn too_much_access_rights_for_a_file() {
let abi = ABI::V1;
check_ruleset_support(
abi,
Some(abi),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::from_all(abi))?
.create()?
.add_rule(PathBeneath::new(
PathFd::new("/etc/passwd")?,
AccessFs::from_file(abi),
))?
.restrict_self()?)
},
false,
);
check_ruleset_support(
abi,
None,
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::from_all(abi))?
.create()?
.add_rule(PathBeneath::new(
PathFd::new("/etc/passwd")?,
AccessFs::from_all(abi),
))?
.restrict_self()?)
},
false,
);
}
#[test]
fn path_beneath_rules_with_too_much_access_rights_for_a_file() {
let abi = ABI::V1;
check_ruleset_support(
abi,
Some(abi),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::from_all(ABI::V1))?
.create()?
.add_rules(path_beneath_rules(["/etc/passwd"], AccessFs::from_all(abi)))?
.restrict_self()?)
},
false,
);
}
#[test]
fn allow_root_fragile() {
let abi = ABI::V1;
check_ruleset_support(
abi,
Some(abi),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.set_compatibility(CompatLevel::HardRequirement)
.handle_access(AccessFs::Execute)?
.set_compatibility(CompatLevel::BestEffort)
.handle_access(AccessFs::from_all(abi))?
.create()?
.set_no_new_privs(true)
.add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::from_all(abi)))?
.restrict_self()?)
},
true,
);
}
#[test]
fn ruleset_enforced() {
let abi = ABI::V1;
check_ruleset_support(
abi,
Some(abi),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::Execute)?
.create()?
.restrict_self()?)
},
false,
);
}
#[test]
fn abi_v2_exec_refer() {
check_ruleset_support(
ABI::V1,
Some(ABI::V2),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::Execute)?
.handle_access(AccessFs::Refer)?
.create()?
.restrict_self()?)
},
false,
);
}
#[test]
fn abi_v2_refer_only() {
check_ruleset_support(
ABI::V2,
Some(ABI::V2),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::Refer)?
.create()?
.restrict_self()?)
},
false,
);
}
#[test]
fn abi_v3_truncate() {
check_ruleset_support(
ABI::V2,
Some(ABI::V3),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::Refer)?
.handle_access(AccessFs::Truncate)?
.create()?
.restrict_self()?)
},
false,
);
}
#[test]
fn ruleset_created_try_clone() {
check_ruleset_support(
ABI::V1,
Some(ABI::V1),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::Execute)?
.create()?
.add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::Execute))?
.try_clone()?
.restrict_self()?)
},
false,
);
}
}