use crate::compat::private::OptionCompatLevelMut;
use crate::{
uapi, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel, CompatState,
Compatibility, Compatible, CreateRulesetError, HandledAccess, LandlockStatus,
PrivateHandledAccess, RestrictSelfError, RulesetError, Scope, ScopeError, TryCompat,
};
use std::io::Error;
use std::mem::size_of_val;
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
#[cfg(test)]
use crate::*;
pub trait Rule<T>: PrivateRule<T>
where
T: HandledAccess,
{
}
pub trait PrivateRule<T>
where
Self: TryCompat<T> + Compatible,
T: HandledAccess,
{
const TYPE_ID: uapi::landlock_rule_type;
fn as_ptr(&mut self) -> *const libc::c_void;
fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError>;
}
#[derive(Debug, PartialEq, Eq)]
pub enum RulesetStatus {
FullyEnforced,
PartiallyEnforced,
NotEnforced,
}
impl From<CompatState> for RulesetStatus {
fn from(state: CompatState) -> Self {
match state {
CompatState::Init | CompatState::No | CompatState::Dummy => RulesetStatus::NotEnforced,
CompatState::Full => RulesetStatus::FullyEnforced,
CompatState::Partial => RulesetStatus::PartiallyEnforced,
}
}
}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct RestrictionStatus {
pub ruleset: RulesetStatus,
pub no_new_privs: bool,
pub landlock: LandlockStatus,
}
fn prctl_set_no_new_privs() -> Result<(), Error> {
match unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } {
0 => Ok(()),
_ => Err(Error::last_os_error()),
}
}
fn support_no_new_privs() -> bool {
matches!(
unsafe { libc::prctl(libc::PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) },
0 | 1
)
}
#[derive(Debug)]
pub struct Ruleset {
pub(crate) requested_handled_fs: BitFlags<AccessFs>,
pub(crate) requested_handled_net: BitFlags<AccessNet>,
pub(crate) requested_scoped: BitFlags<Scope>,
pub(crate) actual_handled_fs: BitFlags<AccessFs>,
pub(crate) actual_handled_net: BitFlags<AccessNet>,
pub(crate) actual_scoped: BitFlags<Scope>,
pub(crate) compat: Compatibility,
}
impl From<Compatibility> for Ruleset {
fn from(compat: Compatibility) -> Self {
Ruleset {
requested_handled_fs: Default::default(),
requested_handled_net: Default::default(),
requested_scoped: Default::default(),
actual_handled_fs: Default::default(),
actual_handled_net: Default::default(),
actual_scoped: Default::default(),
compat,
}
}
}
#[cfg(test)]
impl From<ABI> for Ruleset {
fn from(abi: ABI) -> Self {
Ruleset::from(Compatibility::from(abi))
}
}
#[test]
fn ruleset_add_rule_iter() {
assert!(matches!(
Ruleset::from(ABI::Unsupported)
.handle_access(AccessFs::Execute)
.unwrap()
.create()
.unwrap()
.add_rule(PathBeneath::new(
PathFd::new("/").unwrap(),
AccessFs::ReadFile
))
.unwrap_err(),
RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
));
}
impl Default for Ruleset {
fn default() -> Self {
Compatibility::new().into()
}
}
impl Ruleset {
#[allow(clippy::new_without_default)]
#[deprecated(note = "Use Ruleset::default() instead")]
pub fn new() -> Self {
Ruleset::default()
}
pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
let body = || -> Result<RulesetCreated, CreateRulesetError> {
match self.compat.state {
CompatState::Init => {
Err(CreateRulesetError::MissingHandledAccess)
}
CompatState::No | CompatState::Dummy => {
#[cfg(test)]
assert!(
!self.requested_handled_fs.is_empty()
|| !self.requested_handled_net.is_empty()
|| !self.requested_scoped.is_empty()
);
self.compat.update(CompatState::Dummy);
match self.compat.level.into() {
CompatLevel::HardRequirement => {
Err(CreateRulesetError::MissingHandledAccess)
}
_ => Ok(RulesetCreated::new(self, None)),
}
}
CompatState::Full | CompatState::Partial => {
#[cfg(test)]
assert!(
!self.actual_handled_fs.is_empty()
|| !self.actual_handled_net.is_empty()
|| !self.actual_scoped.is_empty()
);
let attr = uapi::landlock_ruleset_attr {
handled_access_fs: self.actual_handled_fs.bits(),
handled_access_net: self.actual_handled_net.bits(),
scoped: self.actual_scoped.bits(),
};
match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {
fd if fd >= 0 => Ok(RulesetCreated::new(
self,
Some(unsafe { OwnedFd::from_raw_fd(fd) }),
)),
_ => Err(CreateRulesetError::CreateRulesetCall {
source: Error::last_os_error(),
}),
}
}
}
};
Ok(body()?)
}
}
impl OptionCompatLevelMut for Ruleset {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat.level
}
}
impl OptionCompatLevelMut for &mut Ruleset {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat.level
}
}
impl Compatible for Ruleset {}
impl Compatible for &mut Ruleset {}
impl AsMut<Ruleset> for Ruleset {
fn as_mut(&mut self) -> &mut Ruleset {
self
}
}
#[test]
fn ruleset_as_mut() {
let mut ruleset = Ruleset::from(ABI::Unsupported);
let _ = ruleset.as_mut();
let mut ruleset_created = Ruleset::from(ABI::Unsupported)
.handle_access(AccessFs::Execute)
.unwrap()
.create()
.unwrap();
let _ = ruleset_created.as_mut();
}
pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
where
T: Into<BitFlags<U>>,
U: HandledAccess + PrivateHandledAccess,
{
U::ruleset_handle_access(self.as_mut(), access.into())?;
Ok(self)
}
fn scope<T>(mut self, scope: T) -> Result<Self, RulesetError>
where
T: Into<BitFlags<Scope>>,
{
let scope = scope.into();
let ruleset = self.as_mut();
ruleset.requested_scoped |= scope;
if let Some(a) = scope
.try_compat(
ruleset.compat.abi(),
ruleset.compat.level,
&mut ruleset.compat.state,
)
.map_err(ScopeError::Compat)?
{
ruleset.actual_scoped |= a;
}
Ok(self)
}
}
impl RulesetAttr for Ruleset {}
impl RulesetAttr for &mut Ruleset {}
#[test]
fn ruleset_attr() {
let mut ruleset = Ruleset::from(ABI::Unsupported);
let ruleset_ref = &mut ruleset;
ruleset_ref
.set_compatibility(CompatLevel::BestEffort)
.handle_access(AccessFs::Execute)
.unwrap()
.handle_access(AccessFs::ReadFile)
.unwrap();
ruleset
.set_compatibility(CompatLevel::BestEffort)
.handle_access(AccessFs::Execute)
.unwrap()
.handle_access(AccessFs::WriteFile)
.unwrap()
.create()
.unwrap();
}
#[test]
fn ruleset_created_handle_access_fs() {
let access = make_bitflags!(AccessFs::{Execute | ReadDir});
let ruleset = Ruleset::from(ABI::V1).handle_access(access).unwrap();
assert_eq!(ruleset.requested_handled_fs, access);
assert_eq!(ruleset.actual_handled_fs, access);
let ruleset = Ruleset::from(ABI::V1)
.handle_access(AccessFs::Execute)
.unwrap()
.handle_access(AccessFs::ReadDir)
.unwrap()
.handle_access(AccessFs::Execute)
.unwrap();
assert_eq!(ruleset.requested_handled_fs, access);
assert_eq!(ruleset.actual_handled_fs, access);
assert!(matches!(Ruleset::from(ABI::Unsupported)
.handle_access(AccessFs::Execute)
.unwrap()
.set_compatibility(CompatLevel::HardRequirement)
.handle_access(AccessFs::ReadDir)
.unwrap_err(),
RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
CompatError::Access(AccessError::Incompatible { access })
))) if access == AccessFs::ReadDir
));
}
#[test]
fn ruleset_created_handle_access_net_tcp() {
let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();
assert_eq!(ruleset.requested_handled_net, access);
assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);
let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
assert_eq!(ruleset.requested_handled_net, access);
assert_eq!(ruleset.actual_handled_net, access);
let ruleset = Ruleset::from(ABI::V4)
.handle_access(AccessNet::BindTcp)
.unwrap()
.handle_access(AccessNet::ConnectTcp)
.unwrap()
.handle_access(AccessNet::BindTcp)
.unwrap();
assert_eq!(ruleset.requested_handled_net, access);
assert_eq!(ruleset.actual_handled_net, access);
assert!(matches!(Ruleset::from(ABI::Unsupported)
.handle_access(AccessNet::BindTcp)
.unwrap()
.set_compatibility(CompatLevel::HardRequirement)
.handle_access(AccessNet::ConnectTcp)
.unwrap_err(),
RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
CompatError::Access(AccessError::Incompatible { access })
))) if access == AccessNet::ConnectTcp
));
}
#[test]
fn ruleset_created_scope() {
let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
let ruleset = Ruleset::from(ABI::V5).scope(scopes).unwrap();
assert_eq!(ruleset.requested_scoped, scopes);
assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
let ruleset = Ruleset::from(ABI::V6).scope(scopes).unwrap();
assert_eq!(ruleset.requested_scoped, scopes);
assert_eq!(ruleset.actual_scoped, scopes);
let ruleset = Ruleset::from(ABI::V6)
.scope(Scope::AbstractUnixSocket)
.unwrap()
.scope(Scope::Signal)
.unwrap()
.scope(Scope::AbstractUnixSocket)
.unwrap();
assert_eq!(ruleset.requested_scoped, scopes);
assert_eq!(ruleset.actual_scoped, scopes);
assert!(matches!(Ruleset::from(ABI::Unsupported)
.scope(Scope::AbstractUnixSocket)
.unwrap()
.set_compatibility(CompatLevel::HardRequirement)
.scope(Scope::Signal)
.unwrap_err(),
RulesetError::Scope(ScopeError::Compat(
CompatError::Access(AccessError::Incompatible { access })
)) if access == Scope::Signal
));
}
#[test]
fn ruleset_created_fs_net_scope() {
let access_fs = make_bitflags!(AccessFs::{Execute | ReadDir});
let access_net = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
let ruleset = Ruleset::from(ABI::V5)
.handle_access(access_fs)
.unwrap()
.scope(scopes)
.unwrap()
.handle_access(access_net)
.unwrap();
assert_eq!(ruleset.requested_handled_fs, access_fs);
assert_eq!(ruleset.actual_handled_fs, access_fs);
assert_eq!(ruleset.requested_handled_net, access_net);
assert_eq!(ruleset.actual_handled_net, access_net);
assert_eq!(ruleset.requested_scoped, scopes);
assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
let ruleset = Ruleset::from(ABI::V6)
.handle_access(access_fs)
.unwrap()
.scope(scopes)
.unwrap()
.handle_access(access_net)
.unwrap();
assert_eq!(ruleset.requested_handled_fs, access_fs);
assert_eq!(ruleset.actual_handled_fs, access_fs);
assert_eq!(ruleset.requested_handled_net, access_net);
assert_eq!(ruleset.actual_handled_net, access_net);
assert_eq!(ruleset.requested_scoped, scopes);
assert_eq!(ruleset.actual_scoped, scopes);
}
impl OptionCompatLevelMut for RulesetCreated {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat.level
}
}
impl OptionCompatLevelMut for &mut RulesetCreated {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat.level
}
}
impl Compatible for RulesetCreated {}
impl Compatible for &mut RulesetCreated {}
pub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {
fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
where
T: Rule<U>,
U: HandledAccess + PrivateHandledAccess,
{
let body = || -> Result<Self, AddRulesError> {
let self_ref = self.as_mut();
rule.check_consistency(self_ref)?;
let mut compat_rule = match rule
.try_compat(
self_ref.compat.abi(),
self_ref.compat.level,
&mut self_ref.compat.state,
)
.map_err(AddRuleError::Compat)?
{
Some(r) => r,
None => return Ok(self),
};
match self_ref.compat.state {
CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
CompatState::Full | CompatState::Partial => {
#[cfg(test)]
assert!(self_ref.fd.is_some());
let fd = self_ref.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
match unsafe {
uapi::landlock_add_rule(fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
} {
0 => Ok(self),
_ => Err(AddRuleError::<U>::AddRuleCall {
source: Error::last_os_error(),
}
.into()),
}
}
}
};
Ok(body()?)
}
fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
where
I: IntoIterator<Item = Result<T, E>>,
T: Rule<U>,
U: HandledAccess + PrivateHandledAccess,
E: From<RulesetError>,
{
for rule in rules {
self = self.add_rule(rule?)?;
}
Ok(self)
}
fn set_no_new_privs(mut self, no_new_privs: bool) -> Self {
<Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = no_new_privs;
self
}
}
#[derive(Debug)]
pub struct RulesetCreated {
fd: Option<OwnedFd>,
no_new_privs: bool,
pub(crate) requested_handled_fs: BitFlags<AccessFs>,
pub(crate) requested_handled_net: BitFlags<AccessNet>,
compat: Compatibility,
}
impl RulesetCreated {
pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
#[cfg(test)]
assert!(!matches!(ruleset.compat.state, CompatState::Init));
RulesetCreated {
fd,
no_new_privs: true,
requested_handled_fs: ruleset.requested_handled_fs,
requested_handled_net: ruleset.requested_handled_net,
compat: ruleset.compat,
}
}
pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
let enforced_nnp = if self.no_new_privs {
if let Err(e) = prctl_set_no_new_privs() {
match self.compat.level.into() {
CompatLevel::BestEffort => {}
CompatLevel::SoftRequirement => {
self.compat.update(CompatState::Dummy);
}
CompatLevel::HardRequirement => {
return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
}
}
let support_nnp = support_no_new_privs();
match self.compat.state {
CompatState::Init | CompatState::No | CompatState::Dummy => {
if support_nnp {
return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
}
}
CompatState::Full | CompatState::Partial => {
return Err(RestrictSelfError::SetNoNewPrivsCall { source: e })
}
}
false
} else {
true
}
} else {
false
};
match self.compat.state {
CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
ruleset: self.compat.state.into(),
landlock: self.compat.status(),
no_new_privs: enforced_nnp,
}),
CompatState::Full | CompatState::Partial => {
#[cfg(test)]
assert!(self.fd.is_some());
let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
match unsafe { uapi::landlock_restrict_self(fd, 0) } {
0 => {
self.compat.update(CompatState::Full);
Ok(RestrictionStatus {
ruleset: self.compat.state.into(),
landlock: self.compat.status(),
no_new_privs: enforced_nnp,
})
}
_ => Err(RestrictSelfError::RestrictSelfCall {
source: Error::last_os_error(),
}),
}
}
}
};
Ok(body()?)
}
pub fn try_clone(&self) -> std::io::Result<Self> {
Ok(RulesetCreated {
fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
no_new_privs: self.no_new_privs,
requested_handled_fs: self.requested_handled_fs,
requested_handled_net: self.requested_handled_net,
compat: self.compat,
})
}
}
impl From<RulesetCreated> for Option<OwnedFd> {
fn from(ruleset: RulesetCreated) -> Self {
ruleset.fd
}
}
#[test]
fn ruleset_created_ownedfd_none() {
let ruleset = Ruleset::from(ABI::Unsupported)
.handle_access(AccessFs::Execute)
.unwrap()
.create()
.unwrap();
let fd: Option<OwnedFd> = ruleset.into();
assert!(fd.is_none());
}
impl AsMut<RulesetCreated> for RulesetCreated {
fn as_mut(&mut self) -> &mut RulesetCreated {
self
}
}
impl RulesetCreatedAttr for RulesetCreated {}
impl RulesetCreatedAttr for &mut RulesetCreated {}
#[test]
fn ruleset_created_attr() {
let mut ruleset_created = Ruleset::from(ABI::Unsupported)
.handle_access(AccessFs::Execute)
.unwrap()
.create()
.unwrap();
let ruleset_created_ref = &mut ruleset_created;
ruleset_created_ref
.set_compatibility(CompatLevel::BestEffort)
.add_rule(PathBeneath::new(
PathFd::new("/usr").unwrap(),
AccessFs::Execute,
))
.unwrap()
.add_rule(PathBeneath::new(
PathFd::new("/etc").unwrap(),
AccessFs::Execute,
))
.unwrap();
assert_eq!(
ruleset_created
.set_compatibility(CompatLevel::BestEffort)
.add_rule(PathBeneath::new(
PathFd::new("/tmp").unwrap(),
AccessFs::Execute,
))
.unwrap()
.add_rule(PathBeneath::new(
PathFd::new("/var").unwrap(),
AccessFs::Execute,
))
.unwrap()
.restrict_self()
.unwrap(),
RestrictionStatus {
ruleset: RulesetStatus::NotEnforced,
landlock: LandlockStatus::NotImplemented,
no_new_privs: true,
}
);
}
#[test]
fn ruleset_compat_dummy() {
for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
println!("level: {:?}", level);
let ruleset = Ruleset::from(ABI::Unsupported);
assert_eq!(ruleset.compat.state, CompatState::Init);
let ruleset = ruleset.set_compatibility(level);
assert_eq!(ruleset.compat.state, CompatState::Init);
let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
assert_eq!(
ruleset.compat.state,
match level {
CompatLevel::BestEffort => CompatState::No,
CompatLevel::SoftRequirement => CompatState::Dummy,
_ => unreachable!(),
}
);
let ruleset_created = ruleset.create().unwrap();
assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
let ruleset_created = ruleset_created
.add_rule(PathBeneath::new(
PathFd::new("/usr").unwrap(),
AccessFs::Execute,
))
.unwrap();
assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
}
}
#[test]
fn ruleset_compat_partial() {
let ruleset = Ruleset::from(ABI::V1);
assert_eq!(ruleset.compat.state, CompatState::Init);
let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
assert_eq!(ruleset.compat.state, CompatState::No);
let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
assert_eq!(ruleset.compat.state, CompatState::Partial);
let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
assert_eq!(ruleset.compat.state, CompatState::Partial);
}
#[test]
fn ruleset_unsupported() {
assert_eq!(
Ruleset::from(ABI::Unsupported)
.handle_access(AccessFs::Execute)
.unwrap()
.create()
.unwrap()
.restrict_self()
.unwrap(),
RestrictionStatus {
ruleset: RulesetStatus::NotEnforced,
landlock: LandlockStatus::NotImplemented,
no_new_privs: true,
}
);
assert_eq!(
Ruleset::from(ABI::Unsupported)
.set_compatibility(CompatLevel::SoftRequirement)
.handle_access(AccessFs::Execute)
.unwrap()
.create()
.unwrap()
.restrict_self()
.unwrap(),
RestrictionStatus {
ruleset: RulesetStatus::NotEnforced,
landlock: LandlockStatus::NotImplemented,
no_new_privs: true,
}
);
matches!(
Ruleset::from(ABI::Unsupported)
.set_compatibility(CompatLevel::HardRequirement)
.handle_access(AccessFs::Execute)
.unwrap_err(),
RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
);
matches!(
Ruleset::from(ABI::Unsupported)
.set_compatibility(CompatLevel::HardRequirement)
.scope(Scope::Signal)
.unwrap_err(),
RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
);
assert_eq!(
Ruleset::from(ABI::Unsupported)
.handle_access(AccessFs::Execute)
.unwrap()
.create()
.unwrap()
.set_compatibility(CompatLevel::SoftRequirement)
.restrict_self()
.unwrap(),
RestrictionStatus {
ruleset: RulesetStatus::NotEnforced,
landlock: LandlockStatus::NotImplemented,
no_new_privs: true,
}
);
if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
assert_eq!(
Ruleset::from(ABI::V1)
.handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
.unwrap()
.create()
.unwrap()
.set_compatibility(CompatLevel::SoftRequirement)
.add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
.unwrap()
.restrict_self()
.unwrap(),
RestrictionStatus {
ruleset: RulesetStatus::NotEnforced,
landlock: LandlockStatus::Available {
effective_abi: ABI::V1,
kernel_abi: None,
},
no_new_privs: true,
}
);
}
assert_eq!(
Ruleset::from(ABI::Unsupported)
.handle_access(AccessFs::Execute)
.unwrap()
.create()
.unwrap()
.set_no_new_privs(false)
.restrict_self()
.unwrap(),
RestrictionStatus {
ruleset: RulesetStatus::NotEnforced,
landlock: LandlockStatus::NotImplemented,
no_new_privs: false,
}
);
assert!(matches!(
Ruleset::from(ABI::Unsupported)
.handle_access(AccessFs::from_all(ABI::Unsupported))
.unwrap_err(),
RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
CompatError::Access(AccessError::Empty)
)))
));
assert!(matches!(
Ruleset::from(ABI::Unsupported)
.create()
.unwrap_err(),
RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
));
assert!(matches!(
Ruleset::from(ABI::V1)
.handle_access(AccessFs::from_all(ABI::Unsupported))
.unwrap_err(),
RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
CompatError::Access(AccessError::Empty)
)))
));
assert!(matches!(
Ruleset::from(ABI::Unsupported)
.scope(Scope::from_all(ABI::Unsupported))
.unwrap_err(),
RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
));
assert!(matches!(
Ruleset::from(ABI::V1)
.scope(Scope::from_all(ABI::Unsupported))
.unwrap_err(),
RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
));
for handled_access in &[
make_bitflags!(AccessFs::{Execute | WriteFile}),
AccessFs::Execute.into(),
] {
let ruleset = Ruleset::from(ABI::V1)
.handle_access(*handled_access)
.unwrap();
let ruleset_created = RulesetCreated::new(ruleset, None);
assert!(matches!(
ruleset_created
.add_rule(PathBeneath::new(
PathFd::new("/").unwrap(),
AccessFs::ReadFile
))
.unwrap_err(),
RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
));
}
}
#[test]
fn ignore_abi_v2_with_abi_v1() {
assert_eq!(
Ruleset::from(ABI::V1)
.set_compatibility(CompatLevel::HardRequirement)
.handle_access(AccessFs::from_all(ABI::V1))
.unwrap()
.set_compatibility(CompatLevel::SoftRequirement)
.handle_access(AccessFs::Refer)
.unwrap()
.create()
.unwrap()
.add_rule(PathBeneath::new(
PathFd::new("/tmp").unwrap(),
AccessFs::from_all(ABI::V2)
))
.unwrap()
.add_rule(PathBeneath::new(
PathFd::new("/usr").unwrap(),
make_bitflags!(AccessFs::{ReadFile | ReadDir})
))
.unwrap()
.restrict_self()
.unwrap(),
RestrictionStatus {
ruleset: RulesetStatus::NotEnforced,
landlock: LandlockStatus::Available {
effective_abi: ABI::V1,
kernel_abi: None,
},
no_new_privs: true,
}
);
}
#[test]
fn unsupported_handled_access() {
matches!(
Ruleset::from(ABI::V3)
.handle_access(AccessNet::from_all(ABI::V3))
.unwrap_err(),
RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
CompatError::Access(AccessError::Empty)
)))
);
}
#[test]
fn unsupported_handled_access_errno() {
assert_eq!(
Errno::from(
Ruleset::from(ABI::V3)
.handle_access(AccessNet::from_all(ABI::V3))
.unwrap_err()
),
Errno::new(libc::EINVAL)
);
}