use crate::compat::private::OptionCompatLevelMut;
use crate::compat::Compatibility;
use crate::flags::{RestrictSelfFlag, SyscallFlagExt};
use crate::prctl::try_set_no_new_privs;
use crate::{
uapi, CompatLevel, CompatState, Compatible, LandlockStatus, RestrictSelfError, RulesetError,
};
use private::RestrictSelfFlagsState;
#[cfg(test)]
use crate::ABI;
pub(crate) mod private {
use crate::RulesetError;
pub trait RestrictSelfFlagsState {
fn try_set_flag(
&mut self,
flag: super::RestrictSelfFlag,
set: bool,
) -> Result<(), RulesetError>;
}
}
pub trait RestrictSelfAttr: Sized + private::RestrictSelfFlagsState {
fn log_subdomains(mut self, set: bool) -> Result<Self, RulesetError> {
self.try_set_flag(RestrictSelfFlag::LogSubdomains, set)?;
Ok(self)
}
}
#[derive(Debug)]
pub struct RestrictSelf {
requested_flags: u32,
actual_flags: u32,
no_new_privs: bool,
compat: Compatibility,
}
impl Default for RestrictSelf {
fn default() -> Self {
Self {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: Compatibility::new(),
}
}
}
#[cfg(test)]
impl From<ABI> for RestrictSelf {
fn from(abi: ABI) -> Self {
Self {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: Compatibility::from(abi),
}
}
}
impl RestrictSelfFlagsState for RestrictSelf {
fn try_set_flag(&mut self, flag: RestrictSelfFlag, set: bool) -> Result<(), RulesetError> {
let raw_bit = flag.raw_bit();
if set == flag.default_value() {
self.requested_flags &= !raw_bit;
} else {
self.requested_flags |= raw_bit;
}
if flag.try_compat(set, &mut self.compat)? {
self.actual_flags |= raw_bit;
} else {
self.actual_flags &= !raw_bit;
}
Ok(())
}
}
impl RestrictSelfAttr for RestrictSelf {}
impl OptionCompatLevelMut for RestrictSelf {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat.level
}
}
impl Compatible for RestrictSelf {}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct RestrictSelfStatus {
pub landlock: LandlockStatus,
pub no_new_privs: bool,
pub log_subdomains: bool,
}
impl RestrictSelf {
pub fn no_new_privs(mut self, yes: bool) -> Self {
self.no_new_privs = yes;
self
}
pub fn apply(mut self) -> Result<RestrictSelfStatus, RulesetError> {
let enforced_nnp = if self.no_new_privs {
try_set_no_new_privs(&mut self.compat)?
} else {
false
};
let log_subdomains = RestrictSelfFlag::LogSubdomains.is_set(self.actual_flags);
let status = RestrictSelfStatus {
landlock: self.compat.status(),
no_new_privs: enforced_nnp,
log_subdomains,
};
match self.compat.state {
CompatState::Init | CompatState::No | CompatState::Dummy => return Ok(status),
CompatState::Full | CompatState::Partial => {
if self.actual_flags == 0 {
return Ok(status);
}
}
}
match unsafe { uapi::landlock_restrict_self(-1, self.actual_flags) } {
0 => Ok(status),
_ => Err(RestrictSelfError::RestrictSelfCall {
source: std::io::Error::last_os_error(),
}
.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::uapi;
use crate::*;
#[test]
fn restrict_self_default() {
let rs = RestrictSelf::default();
assert_eq!(rs.requested_flags, 0);
assert_eq!(rs.actual_flags, 0);
let status = rs.apply().unwrap();
assert!(status.log_subdomains);
}
#[test]
fn restrict_self_log_subdomains() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::V7.into(),
};
let rs = rs.log_subdomains(false).unwrap();
assert_ne!(rs.requested_flags, 0);
assert_ne!(rs.actual_flags, 0);
assert_eq!(rs.requested_flags, rs.actual_flags);
}
#[test]
fn restrict_self_compatibility() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::Unsupported.into(),
};
assert!(matches!(
rs.set_compatibility(CompatLevel::HardRequirement)
.log_subdomains(false)
.unwrap_err(),
RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
flag: RestrictSelfFlag::LogSubdomains,
set: false,
})
));
}
#[test]
fn restrict_self_no_flags() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::V7.into(),
};
let status = rs.apply().unwrap();
assert!(matches!(status.landlock, LandlockStatus::Available { .. }));
assert!(status.log_subdomains); }
#[test]
fn restrict_self_partial_no_op() {
let mut compat: Compatibility = ABI::V7.into();
compat.update(CompatState::No);
assert_eq!(compat.state, CompatState::No);
let rs = RestrictSelf {
requested_flags: 0b01,
actual_flags: 0,
no_new_privs: true,
compat,
};
let status = rs.apply().unwrap();
assert!(matches!(status.landlock, LandlockStatus::Available { .. }));
assert!(status.log_subdomains); }
#[test]
fn restrict_self_best_effort_drops_unsupported() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::Unsupported.into(),
};
let rs = rs.log_subdomains(false).unwrap();
assert_ne!(rs.requested_flags, 0);
assert_eq!(rs.actual_flags, 0);
let status = rs.apply().unwrap();
assert_eq!(status.landlock, LandlockStatus::NotImplemented);
assert!(status.log_subdomains); }
#[test]
fn restrict_self_soft_requirement_drops_unsupported() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::Unsupported.into(),
};
let rs = rs
.set_compatibility(CompatLevel::SoftRequirement)
.log_subdomains(false)
.unwrap();
assert_ne!(rs.requested_flags, 0);
assert_eq!(rs.actual_flags, 0);
let status = rs.apply().unwrap();
assert_eq!(status.landlock, LandlockStatus::NotImplemented);
assert!(status.log_subdomains); }
#[test]
fn restrict_self_subdomains_applied() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::V7.into(),
};
let rs = rs.log_subdomains(false).unwrap();
assert_ne!(rs.actual_flags, 0);
assert_ne!(
rs.actual_flags & uapi::LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
0
);
}
#[test]
fn restrict_self_hard_requirement_supported() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::V7.into(),
};
let rs = rs
.set_compatibility(CompatLevel::HardRequirement)
.log_subdomains(false)
.unwrap();
assert_ne!(rs.requested_flags, 0);
assert_ne!(rs.actual_flags, 0);
}
#[test]
fn restrict_self_last_call_wins() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::V7.into(),
};
let rs = rs
.log_subdomains(false)
.unwrap()
.log_subdomains(true)
.unwrap();
assert_eq!(rs.requested_flags, 0);
assert_eq!(rs.actual_flags, 0);
}
#[test]
fn restrict_self_default_after_hard_requirement() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::Unsupported.into(),
};
let rs = rs
.set_compatibility(CompatLevel::HardRequirement)
.log_subdomains(true)
.unwrap();
assert_eq!(rs.requested_flags, 0);
assert_eq!(rs.actual_flags, 0);
}
#[test]
fn restrict_self_no_nnp() {
let rs = RestrictSelf {
requested_flags: 0,
actual_flags: 0,
no_new_privs: true,
compat: ABI::Unsupported.into(),
};
let status = rs.no_new_privs(false).apply().unwrap();
assert!(!status.no_new_privs);
assert_eq!(status.landlock, LandlockStatus::NotImplemented);
}
}