diff --git a/src/access.rs b/src/access.rs index 782f8d1d..88d57ec2 100644 --- a/src/access.rs +++ b/src/access.rs @@ -1,13 +1,28 @@ use crate::{ - AccessError, AddRuleError, AddRulesError, BitFlags, CompatError, CompatResult, - HandleAccessError, HandleAccessesError, Ruleset, TailoredCompatLevel, TryCompat, ABI, + AccessError, AddRuleError, AddRulesError, BitFlags, CompatAccess, CompatError, CompatResult, + CompatibleArgument, HandleAccessError, HandleAccessesError, Ruleset, TailoredCompatLevel, + TryCompat, ABI, }; use enumflags2::BitFlag; #[cfg(test)] use crate::{make_bitflags, AccessFs, CompatLevel, CompatState, Compatibility}; -pub trait Access: PrivateAccess { +/* +pub trait Access +where + Self: PrivateAccess, + + Into> + + Into>, + + CompatibleArgument>, + BitFlags: + Into>> + CompatibleArgument>, + Into>, +*/ +pub trait Access +where + Self: PrivateAccess, +{ /// Gets the access rights defined by a specific [`ABI`]. /// Union of [`from_read()`](Access::from_read) and [`from_write()`](Access::from_write). fn from_all(abi: ABI) -> BitFlags { @@ -30,7 +45,8 @@ pub trait Access: PrivateAccess { pub trait PrivateAccess: BitFlag { fn ruleset_handle_access( ruleset: &mut Ruleset, - access: BitFlags, + access: CompatAccess, + //access: CompatAccess>, ) -> Result<(), HandleAccessesError> where Self: Access; @@ -191,3 +207,43 @@ fn compat_bit_flags() { if access == v2_access && incompatible == AccessFs::Refer )); } + +/* +impl From> for CompatAccess +where + A: Access, +{ + fn from(access: BitFlags) -> Self { + // XXX: How? + access.into() + } +} +*/ + +impl CompatibleArgument for A +where + A: Access, +{ + type CompatSelf = A; +} + +impl CompatibleArgument for BitFlags +where + A: Access, +{ + type CompatSelf = A; +} + +#[test] +fn compat_arg() { + use crate::{AccessFs, BitFlags, CompatibleArgument, Consequence, Ruleset, RulesetAttr}; + + let exec: BitFlags<_> = AccessFs::Execute.into(); + Ruleset::from(ABI::Unsupported) + .handle_access(exec.if_unmet(Consequence::DisableRuleset)) + .unwrap(); + + Ruleset::from(ABI::Unsupported) + .handle_access(AccessFs::Execute.if_unmet(Consequence::DisableRuleset)) + .unwrap(); +} diff --git a/src/compat.rs b/src/compat.rs index e08ceead..c98c643f 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -187,9 +187,10 @@ fn current_kernel_abi() { // CompatState is not public outside this crate. /// Returned by ruleset builder. #[cfg_attr(test, derive(Debug))] -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Default, PartialEq, Eq)] pub enum CompatState { /// Initial undefined state. + #[default] Init, /// All requested restrictions are enforced. Full, @@ -459,6 +460,186 @@ fn deprecated_set_best_effort() { ); } +// TODO: Add doc +#[derive(Default)] +pub enum Consequence { + /// Best-effort approach. + #[default] + Continue, + DisableRuleset, + ReturnError, +} + +use crate::BitFlags; + +// TODO: Remove Clone and Copy +#[derive(Clone, Copy)] +pub struct CompatAccess +where + A: Access, +{ + inner_continue: Option>, + inner_disable_ruleset: Option>, + inner_return_error: Option>, +} + +impl CompatAccess +where + A: Access, +{ + pub fn unwrap_update(self, _current_abi: ABI, _compat_state: &mut CompatState) -> BitFlags { + /* + match self.disable_sandbox_if_unmet { + // Similar to CompatLevel::SoftRequirement + compat_state.update(CompatState::Dummy); + } + */ + // FIXME: + self.inner_continue.unwrap() + } +} + +impl Default for CompatAccess +where + A: Access, +{ + fn default() -> Self { + Self { + inner_continue: None, + inner_disable_ruleset: None, + inner_return_error: None, + } + } +} + +impl CompatAccess +where + A: Access, +{ + // FIXME: Use CompatError + //fn try_inner(self, abi: ABI) -> Result>, CompatError> { + //fn try_compat_inner(self, abi: ABI) -> Result, A>, CompatError> { + fn try_compat_inner(self, abi: ABI) -> Result, UnmetError> { + let mut has_access = false; + if let Some(inner_return_error) = self.inner_return_error { + match inner_return_error.try_compat_inner(abi)? { + CompatResult::Full(_) => has_access = true, + //CompatResult::Partial(_, _) => return Err(UnmetError), + //CompatResult::No(_) => return Err(UnmetError), + // TODO: Include unmet access rights in UnmetError. + _ => return Err(UnmetError), + }; + } + if let Some(inner_disable_ruleset) = self.inner_disable_ruleset { + match inner_disable_ruleset.try_compat_inner(abi)? { + CompatResult::Full(_) => has_access = true, + // TODO: Include unmet access rights in DisableRuleset. + _ => return Ok(UnmetAction::DisableRuleset), + }; + } + if let Some(inner_continue) = self.inner_continue { + let _ = inner_continue.try_compat_inner(abi)?; + has_access = true; + } + if !has_access { + // FIXME: add dedicated error + return Err(UnmetError); + } + Ok(UnmetAction::Continue( + self.inner_return_error.unwrap_or_default() + | self.inner_disable_ruleset.unwrap_or_default() + | self.inner_continue.unwrap_or_default(), + )) + } +} + +pub trait CompatibleArgument +where + Self: Into>, + Self::CompatSelf: Access, +{ + type CompatSelf; + + // TODO: Move if_unmet() to the CompatibleArgument trait (and keep its name for genericity), + // implemented for all Access implementation and all BitFlags. + //fn if_unmet(self, consequence: Consequence) -> Self { + fn if_unmet(self, consequence: Consequence) -> CompatAccess { + let compat_self = self.into(); + let has_access = compat_self.inner_continue.is_some() + || compat_self.inner_disable_ruleset.is_some() + || compat_self.inner_return_error.is_some(); + let option_access = || { + if has_access { + Some( + compat_self.inner_continue.unwrap_or_default() + | compat_self.inner_disable_ruleset.unwrap_or_default() + | compat_self.inner_return_error.unwrap_or_default(), + ) + } else { + None + } + }; + match consequence { + Consequence::Continue => CompatAccess { + inner_continue: option_access(), + ..Default::default() + }, + Consequence::DisableRuleset => CompatAccess { + inner_disable_ruleset: option_access(), + ..Default::default() + }, + Consequence::ReturnError => CompatAccess { + inner_return_error: option_access(), + ..Default::default() + }, + } + } +} + +impl From for CompatAccess +where + A: Access, + B: Into>, + // TODO: Move as Access requirement + //A: CompatibleArgument, +{ + fn from(access: B) -> Self { + // Best-effort approach by default: Consequence::Continue + Self { + inner_continue: Some(access.into()), + ..Default::default() + } + } +} + +#[test] +fn from_compat_arg_to_compat_arg() { + // TODO: test that a CompatibleArgument with non-Consequence::default() cannot be converted to + // a Consequence::default() through the From trait. +} + +/// See the [`Compatible`] documentation. +#[derive(Default)] +pub enum CompatMode { + /// Takes into account the build requests if they are supported by the running system, + /// or silently ignores them otherwise. + /// Never returns a compatibility error. + #[default] + BestEffort, + /// Takes into account the build requests if they are supported by the running system, + /// or returns a compatibility error otherwise ([`CompatError`]). + ErrorIfUnmet, +} + +impl From for CompatLevel { + fn from(mode: CompatMode) -> Self { + match mode { + CompatMode::BestEffort => CompatLevel::BestEffort, + CompatMode::ErrorIfUnmet => CompatLevel::HardRequirement, + } + } +} + /// See the [`Compatible`] documentation. #[cfg_attr(test, derive(EnumIter))] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -659,3 +840,40 @@ where } } } + +use thiserror::Error; + +/* +#[derive(Debug, Error)] +#[error(transparent)] +pub struct UnmetError +where + A: Access, +( + #[from] A +); +*/ + +#[derive(Debug, Error)] +#[error("failed to meet the required access")] +pub struct UnmetError; + +impl From> for UnmetError +where + A: Access, +{ + fn from(_: CompatError) -> Self { + Self + } +} + +pub enum UnmetAction +where + A: Access, +{ + // Fully matches the request. + Continue(BitFlags), + // Partially matches the request. + //DisableRuleset(BitFlags), + DisableRuleset, +} diff --git a/src/fs.rs b/src/fs.rs index 3ebf9c08..d4ce8d5f 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,9 +1,9 @@ use crate::compat::private::OptionCompatLevelMut; use crate::{ - uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState, - Compatible, HandleAccessError, HandleAccessesError, PathBeneathError, PathFdError, - PrivateAccess, PrivateRule, Rule, Ruleset, RulesetCreated, RulesetError, TailoredCompatLevel, - TryCompat, ABI, + uapi, Access, AddRuleError, AddRulesError, CompatAccess, CompatError, CompatLevel, + CompatResult, CompatState, Compatible, HandleAccessError, HandleAccessesError, + PathBeneathError, PathFdError, PrivateAccess, PrivateRule, Rule, Ruleset, RulesetCreated, + RulesetError, TailoredCompatLevel, TryCompat, ABI, }; use enumflags2::{bitflags, make_bitflags, BitFlags}; use std::fs::OpenOptions; @@ -142,8 +142,10 @@ impl AccessFs { impl PrivateAccess for AccessFs { fn ruleset_handle_access( ruleset: &mut Ruleset, - access: BitFlags, + access: CompatAccess, ) -> Result<(), HandleAccessesError> { + // FIXME: test compat state + let access = access.unwrap_update(ABI::V1, &mut ruleset.compat.state); // We need to record the requested accesses for PrivateRule::check_consistency(). ruleset.requested_handled_fs |= access; ruleset.actual_handled_fs |= match access @@ -207,6 +209,8 @@ pub struct PathBeneath { parent_fd: F, allowed_access: BitFlags, compat_level: Option, + // FIXME: Handle compat_state + //compat_state: CompatState, } impl PathBeneath @@ -218,8 +222,14 @@ where /// The `parent` file descriptor will be automatically closed with the returned `PathBeneath`. pub fn new(parent: F, access: A) -> Self where - A: Into>, + A: Into>, { + // FIXME: + // - properly handle and test compat state and ABI version + // - don't call any kind of unwrap_update() here but set self.allowed_access with this + // CompatAccess and deal with it later. + let mut compat_state = Default::default(); + let access = access.into().unwrap_update(ABI::V1, &mut compat_state); PathBeneath { attr: uapi::landlock_path_beneath_attr { // Invalid access-rights until try_compat() is called. @@ -227,8 +237,9 @@ where parent_fd: parent.as_fd().as_raw_fd(), }, parent_fd: parent, - allowed_access: access.into(), + allowed_access: access, compat_level: None, + //compat_state: compat_state, } } @@ -537,7 +548,7 @@ pub fn path_beneath_rules( where I: IntoIterator, P: AsRef, - A: Into>, + A: Into>, { let access = access.into(); paths.into_iter().filter_map(move |p| match PathFd::new(p) { diff --git a/src/lib.rs b/src/lib.rs index 3c26100d..8e001d23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,9 @@ extern crate lazy_static; pub use access::Access; -pub use compat::{CompatLevel, Compatible, ABI}; +pub use compat::{ + CompatAccess, CompatLevel, CompatMode, Compatible, CompatibleArgument, Consequence, ABI, +}; pub use enumflags2::{make_bitflags, BitFlags}; pub use errors::{ AccessError, AddRuleError, AddRulesError, CompatError, CreateRulesetError, HandleAccessError, diff --git a/src/ruleset.rs b/src/ruleset.rs index f802c3cc..36a78f9a 100644 --- a/src/ruleset.rs +++ b/src/ruleset.rs @@ -1,7 +1,8 @@ use crate::compat::private::OptionCompatLevelMut; use crate::{ - uapi, Access, AccessFs, AddRuleError, AddRulesError, BitFlags, CompatLevel, CompatState, - Compatibility, Compatible, CreateRulesetError, RestrictSelfError, RulesetError, TryCompat, + uapi, Access, AccessFs, AddRuleError, AddRulesError, BitFlags, CompatAccess, CompatLevel, + CompatMode, CompatState, Compatibility, Compatible, CreateRulesetError, RestrictSelfError, + RulesetError, TryCompat, }; use libc::close; use std::io::Error; @@ -222,11 +223,26 @@ impl Default for Ruleset { } impl Ruleset { - #[allow(clippy::new_without_default)] - #[deprecated(note = "Use Ruleset::default() instead")] - pub fn new() -> Self { - Ruleset::default() + /// Returns a new `Ruleset` with a specific compatibility mode. + /// + /// In most cases we should use [`Ruleset::default()`] instead. + pub fn new(mode: CompatMode, handle_access: T) -> Result + where + T: Into>, + U: Access, + { + Self::default() + .set_compatibility(mode.into()) + .handle_access(handle_access.into()) + } + + /* + // TODO: + #[cfg(test)] + pub(crate) fn new_from(mode: CompatMode, abi: ABI) -> Self { + Self::from(abi).set_compatibility(mode.into()) } + */ /// Attempts to create a real Landlock ruleset (if supported by the running kernel). /// The returned [`RulesetCreated`] is also a builder. @@ -332,7 +348,8 @@ pub trait RulesetAttr: Sized + AsMut + Compatible { /// E.g., `RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError))` fn handle_access(mut self, access: T) -> Result where - T: Into>, + T: Into>, + //T: Into> // XXX: CompatibleArgument is not required but could help users U: Access, { U::ruleset_handle_access(self.as_mut(), access.into())?;