Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility mode #58

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 60 additions & 4 deletions src/access.rs
Original file line number Diff line number Diff line change
@@ -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<BitFlags<Self>>
+ Into<CompatAccess<Self>>,
+ CompatibleArgument<CompatSelf = BitFlags<Self>>,
BitFlags<Self>:
Into<CompatAccess<BitFlags<Self>>> + CompatibleArgument<CompatSelf = BitFlags<Self>>,
Into<CompatAccess<Self>>,
*/
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<Self> {
Expand All @@ -30,7 +45,8 @@ pub trait Access: PrivateAccess {
pub trait PrivateAccess: BitFlag {
fn ruleset_handle_access(
ruleset: &mut Ruleset,
access: BitFlags<Self>,
access: CompatAccess<Self>,
//access: CompatAccess<BitFlags<Self>>,
) -> Result<(), HandleAccessesError>
where
Self: Access;
Expand Down Expand Up @@ -191,3 +207,43 @@ fn compat_bit_flags() {
if access == v2_access && incompatible == AccessFs::Refer
));
}

/*
impl<A> From<BitFlags<A>> for CompatAccess<A>
where
A: Access,
{
fn from(access: BitFlags<A>) -> Self {
// XXX: How?
access.into()
}
}
*/

impl<A> CompatibleArgument for A
where
A: Access,
{
type CompatSelf = A;
}

impl<A> CompatibleArgument for BitFlags<A>
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();
}
220 changes: 219 additions & 1 deletion src/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<A>
where
A: Access,
{
inner_continue: Option<BitFlags<A>>,
inner_disable_ruleset: Option<BitFlags<A>>,
inner_return_error: Option<BitFlags<A>>,
}

impl<A> CompatAccess<A>
where
A: Access,
{
pub fn unwrap_update(self, _current_abi: ABI, _compat_state: &mut CompatState) -> BitFlags<A> {
/*
match self.disable_sandbox_if_unmet {
// Similar to CompatLevel::SoftRequirement
compat_state.update(CompatState::Dummy);
}
*/
// FIXME:
self.inner_continue.unwrap()
}
}

impl<A> Default for CompatAccess<A>
where
A: Access,
{
fn default() -> Self {
Self {
inner_continue: None,
inner_disable_ruleset: None,
inner_return_error: None,
}
}
}

impl<A> CompatAccess<A>
where
A: Access,
{
// FIXME: Use CompatError
//fn try_inner(self, abi: ABI) -> Result<Option<BitFlags<A>>, CompatError<A>> {
//fn try_compat_inner(self, abi: ABI) -> Result<CompatResult<BitFlags<A>, A>, CompatError<A>> {
fn try_compat_inner(self, abi: ABI) -> Result<UnmetAction<A>, 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<CompatAccess<Self::CompatSelf>>,
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<A>.
//fn if_unmet(self, consequence: Consequence) -> Self {
fn if_unmet(self, consequence: Consequence) -> CompatAccess<Self::CompatSelf> {
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<A, B> From<B> for CompatAccess<A>
where
A: Access,
B: Into<BitFlags<A>>,
// TODO: Move as Access requirement
//A: CompatibleArgument<CompatSelf = A>,
{
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<CompatMode> 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)]
Expand Down Expand Up @@ -659,3 +840,40 @@ where
}
}
}

use thiserror::Error;

/*
#[derive(Debug, Error)]
#[error(transparent)]
pub struct UnmetError<A>
where
A: Access,
(
#[from] A
);
*/

#[derive(Debug, Error)]
#[error("failed to meet the required access")]
pub struct UnmetError;

impl<A> From<CompatError<A>> for UnmetError
where
A: Access,
{
fn from(_: CompatError<A>) -> Self {
Self
}
}

pub enum UnmetAction<A>
where
A: Access,
{
// Fully matches the request.
Continue(BitFlags<A>),
// Partially matches the request.
//DisableRuleset(BitFlags<A>),
DisableRuleset,
}
27 changes: 19 additions & 8 deletions src/fs.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -142,8 +142,10 @@ impl AccessFs {
impl PrivateAccess for AccessFs {
fn ruleset_handle_access(
ruleset: &mut Ruleset,
access: BitFlags<Self>,
access: CompatAccess<Self>,
) -> 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
Expand Down Expand Up @@ -207,6 +209,8 @@ pub struct PathBeneath<F> {
parent_fd: F,
allowed_access: BitFlags<AccessFs>,
compat_level: Option<CompatLevel>,
// FIXME: Handle compat_state
//compat_state: CompatState,
}

impl<F> PathBeneath<F>
Expand All @@ -218,17 +222,24 @@ where
/// The `parent` file descriptor will be automatically closed with the returned `PathBeneath`.
pub fn new<A>(parent: F, access: A) -> Self
where
A: Into<BitFlags<AccessFs>>,
A: Into<CompatAccess<AccessFs>>,
{
// 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.
allowed_access: 0,
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,
}
}

Expand Down Expand Up @@ -537,7 +548,7 @@ pub fn path_beneath_rules<I, P, A>(
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
A: Into<BitFlags<AccessFs>>,
A: Into<CompatAccess<AccessFs>>,
{
let access = access.into();
paths.into_iter().filter_map(move |p| match PathFd::new(p) {
Expand Down
Loading
Loading