#[cfg(test)]
#[macro_use]
extern crate lazy_static;
pub use access::{Access, HandledAccess};
pub use compat::{CompatLevel, Compatible, LandlockStatus, ABI};
pub use enumflags2::{make_bitflags, BitFlags};
pub use errors::{
AccessError, AddRuleError, AddRulesError, CompatError, CreateRulesetError, Errno,
HandleAccessError, HandleAccessesError, PathBeneathError, PathFdError, RestrictSelfError,
RulesetError, ScopeError,
};
pub use fs::{path_beneath_rules, AccessFs, PathBeneath, PathFd};
pub use net::{AccessNet, NetPort};
pub use ruleset::{
RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr,
RulesetStatus,
};
pub use scope::Scope;
use access::PrivateHandledAccess;
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 net;
mod ruleset;
mod scope;
mod uapi;
mod private {
pub trait Sealed {}
impl Sealed for crate::AccessFs {}
impl Sealed for crate::AccessNet {}
impl Sealed for crate::Scope {}
}
#[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
};
let landlock_status = abi.into();
println!("Expecting ruleset status {ruleset_status:?}");
println!("Expecting Landlock status {landlock_status:?}");
assert!(matches!(
ret,
Ok(RestrictionStatus {
ruleset,
landlock,
no_new_privs: true,
}) if ruleset == ruleset_status && landlock == landlock_status
))
}
} else {
let errno = get_errno_from_landlock_status();
println!("Expecting error {errno:?}");
match ret {
Err(
ref error @ TestRulesetError::Ruleset(RulesetError::CreateRuleset(
CreateRulesetError::CreateRulesetCall { ref source },
)),
) => {
assert_eq!(source.raw_os_error(), Some(*Errno::from(error)));
match (source.raw_os_error(), errno) {
(Some(e1), Some(e2)) => assert_eq!(e1, e2),
(Some(e1), None) => assert!(matches!(e1, libc::EINVAL | libc::E2BIG)),
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
}
}
#[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()?
.add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::Refer))?
.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,
);
}
#[test]
fn abi_v4_tcp() {
check_ruleset_support(
ABI::V3,
Some(ABI::V4),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::Truncate)?
.handle_access(AccessNet::BindTcp | AccessNet::ConnectTcp)?
.create()?
.add_rule(NetPort::new(1, AccessNet::ConnectTcp))?
.restrict_self()?)
},
false,
);
}
#[test]
fn abi_v5_ioctl_dev() {
check_ruleset_support(
ABI::V4,
Some(ABI::V5),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessNet::BindTcp)?
.handle_access(AccessFs::IoctlDev)?
.create()?
.add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::IoctlDev))?
.restrict_self()?)
},
false,
);
}
#[test]
fn abi_v6_scope_mix() {
check_ruleset_support(
ABI::V5,
Some(ABI::V6),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.handle_access(AccessFs::IoctlDev)?
.scope(Scope::AbstractUnixSocket | Scope::Signal)?
.create()?
.restrict_self()?)
},
false,
);
}
#[test]
fn abi_v6_scope_only() {
check_ruleset_support(
ABI::V6,
Some(ABI::V6),
move |ruleset: Ruleset| -> _ {
Ok(ruleset
.scope(Scope::AbstractUnixSocket | Scope::Signal)?
.create()?
.restrict_self()?)
},
false,
);
}
#[test]
fn ruleset_created_try_clone_ownedfd() {
use std::os::unix::io::{AsRawFd, OwnedFd};
let abi = ABI::V1;
check_ruleset_support(
abi,
Some(abi),
move |ruleset: Ruleset| -> _ {
let ruleset1 = ruleset.handle_access(AccessFs::from_all(abi))?.create()?;
let ruleset2 = ruleset1.try_clone().unwrap();
let ruleset3 = ruleset2.try_clone().unwrap();
let some1: Option<OwnedFd> = ruleset1.into();
if let Some(fd1) = some1 {
assert!(fd1.as_raw_fd() >= 0);
let some2: Option<OwnedFd> = ruleset2.into();
let fd2 = some2.unwrap();
assert!(fd2.as_raw_fd() >= 0);
assert_ne!(fd1.as_raw_fd(), fd2.as_raw_fd());
}
Ok(ruleset3.restrict_self()?)
},
false,
);
}
}