Skip to content

Commit

Permalink
Replace enumflags2 with bitflags
Browse files Browse the repository at this point in the history
This avoids a proc macro and replaces the BitFlags wrapper type with
directly implementing all methods on AccessFs and AccessNet.

Signed-off-by: Björn Roy Baron <bjorn3_gh@protonmail.com>
  • Loading branch information
bjorn3 committed Aug 31, 2024
1 parent 537d293 commit a8ac3cf
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 204 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exclude = [".gitignore"]
readme = "README.md"

[dependencies]
enumflags2 = "0.7"
bitflags = "2.6.0"
libc = "0.2.133"
thiserror = "1.0"

Expand Down
6 changes: 3 additions & 3 deletions examples/sandboxer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use anyhow::{anyhow, bail};
use landlock::{
Access, AccessFs, AccessNet, BitFlags, NetPort, PathBeneath, PathFd, Ruleset, RulesetAttr,
Access, AccessFs, AccessNet, NetPort, PathBeneath, PathFd, Ruleset, RulesetAttr,
RulesetCreatedAttr, RulesetStatus, ABI,
};
use std::env;
Expand All @@ -19,7 +19,7 @@ const ENV_TCP_CONNECT_NAME: &str = "LL_TCP_CONNECT";

struct PathEnv {
paths: Vec<u8>,
access: BitFlags<AccessFs>,
access: AccessFs,
}

impl PathEnv {
Expand All @@ -31,7 +31,7 @@ impl PathEnv {
/// allowed. Paths are separated with ":", e.g. "/bin:/lib:/usr:/proc". In case an empty
/// string is provided, NO restrictions are applied.
/// * `access`: Set of access-rights allowed for each of the parsed paths.
fn new<'a>(name: &'a str, access: BitFlags<AccessFs>) -> anyhow::Result<Self> {
fn new<'a>(name: &'a str, access: AccessFs) -> anyhow::Result<Self> {
Ok(Self {
paths: env::var_os(name)
.ok_or(anyhow!("missing environment variable {name}"))?
Expand Down
83 changes: 47 additions & 36 deletions src/access.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
use std::ops::{BitAnd, BitOr};

use crate::{
AccessError, AddRuleError, AddRulesError, BitFlags, CompatError, CompatResult,
HandleAccessError, HandleAccessesError, Ruleset, TailoredCompatLevel, TryCompat, ABI,
AccessError, AddRuleError, AddRulesError, CompatError, CompatResult, HandleAccessError,
HandleAccessesError, Ruleset, TailoredCompatLevel, TryCompat, ABI,
};
use enumflags2::BitFlag;

#[cfg(test)]
use crate::{make_bitflags, AccessFs, CompatLevel, CompatState, Compatibility};
use crate::{AccessFs, CompatLevel, CompatState, Compatibility};

#[macro_export]
macro_rules! make_bitflags {
($bitflag_type:ident :: {$($flag:ident)|*}) => {
$bitflag_type::empty() $(.union($bitflag_type::$flag))*
};
}

pub trait Access: PrivateAccess {
pub trait Access: PrivateAccess + TailoredCompatLevel {
/// Gets the access rights defined by a specific [`ABI`].
fn from_all(abi: ABI) -> BitFlags<Self>;
fn from_all(abi: ABI) -> Self;
}

pub trait PrivateAccess: BitFlag {
pub trait PrivateAccess: Copy + Eq + BitOr<Output = Self> + BitAnd<Output = Self> {
fn is_empty_flags(self) -> bool
where
Self: Access;

fn all() -> Self
where
Self: Access;

fn known_unknown_flags(self, all: Self) -> (Self, Self)
where
Self: Access;

fn ruleset_handle_access(
ruleset: &mut Ruleset,
access: BitFlags<Self>,
access: Self,
) -> Result<(), HandleAccessesError>
where
Self: Access;
Expand All @@ -29,58 +49,49 @@ pub trait PrivateAccess: BitFlag {
Self: Access;
}

// Creates an illegal/overflowed BitFlags<T> with all its bits toggled, including undefined ones.
fn full_negation<T>(flags: BitFlags<T>) -> BitFlags<T>
where
T: Access,
{
unsafe { BitFlags::<T>::from_bits_unchecked(!flags.bits()) }
}

#[test]
fn bit_flags_full_negation() {
let scoped_negation = !BitFlags::<AccessFs>::all();
assert_eq!(scoped_negation, BitFlags::<AccessFs>::empty());
// !BitFlags::<AccessFs>::all() could be equal to full_negation(BitFlags::<AccessFs>::all()))
// if all the 64-bits would be used, which is not currently the case.
assert_ne!(scoped_negation, full_negation(BitFlags::<AccessFs>::all()));
let scoped_negation = !AccessFs::all();
assert_eq!(scoped_negation, AccessFs::empty());
// !AccessFs::all() could be equal to !AccessFs::all().bits() if
// all the 64-bits would be used, which is not currently the case.
assert_ne!(scoped_negation.bits(), !AccessFs::all().bits());
}

impl<A> TailoredCompatLevel for BitFlags<A> where A: Access {}

impl<A> TryCompat<A> for BitFlags<A>
impl<A> TryCompat<A> for A
where
A: Access,
{
fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>> {
if self.is_empty() {
let (_known_flags, unknown_flags) = self.known_unknown_flags(Self::all());
if self.is_empty_flags() {
// Empty access-rights would result to a runtime error.
Err(AccessError::Empty.into())
} else if !Self::all().contains(*self) {
} else if !unknown_flags.is_empty_flags() {
// Unknown access-rights (at build time) would result to a runtime error.
// This can only be reached by using the unsafe BitFlags::from_bits_unchecked().
// This can only be reached by using Access*::from_bits_retain().
Err(AccessError::Unknown {
access: *self,
unknown: *self & full_negation(Self::all()),
unknown: unknown_flags,
}
.into())
} else {
let compat = *self & A::from_all(abi);
let ret = if compat.is_empty() {
let (compatible_flags, incompatible_flags) = self.known_unknown_flags(A::from_all(abi));
let ret = if compatible_flags.is_empty_flags() {
Ok(CompatResult::No(
AccessError::Incompatible { access: *self }.into(),
))
} else if compat != *self {
} else if !incompatible_flags.is_empty_flags() {
let error = AccessError::PartiallyCompatible {
access: *self,
incompatible: *self & full_negation(compat),
incompatible: incompatible_flags,
}
.into();
Ok(CompatResult::Partial(error))
} else {
Ok(CompatResult::Full)
};
*self = compat;
*self = compatible_flags;
ret
}
}
Expand All @@ -103,23 +114,23 @@ fn compat_bit_flags() {
);
assert!(compat.state == CompatState::Full);

let empty_access = BitFlags::<AccessFs>::empty();
let empty_access = AccessFs::empty();
assert!(matches!(
empty_access
.try_compat(compat.abi(), compat.level, &mut compat.state)
.unwrap_err(),
CompatError::Access(AccessError::Empty)
));

let all_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked(1 << 63) };
let all_unknown_access = AccessFs::from_bits_retain(1 << 63);
assert!(matches!(
all_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
CompatError::Access(AccessError::Unknown { access, unknown }) if access == all_unknown_access && unknown == all_unknown_access
));
// An error makes the state final.
assert!(compat.state == CompatState::Dummy);

let some_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked(1 << 63 | 1) };
let some_unknown_access = AccessFs::from_bits_retain(1 << 63 | 1);
assert!(matches!(
some_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
CompatError::Access(AccessError::Unknown { access, unknown }) if access == some_unknown_access && unknown == all_unknown_access
Expand Down
25 changes: 8 additions & 17 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Access, AccessFs, AccessNet, BitFlags};
use crate::{Access, AccessFs, AccessNet};
use std::io;
use std::path::PathBuf;
use thiserror::Error;
Expand Down Expand Up @@ -84,10 +84,7 @@ where
AddRuleCall { source: io::Error },
/// The rule's access-rights are not all handled by the (requested) ruleset access-rights.
#[error("access-rights not handled by the ruleset: {incompatible:?}")]
UnhandledAccess {
access: BitFlags<T>,
incompatible: BitFlags<T>,
},
UnhandledAccess { access: T, incompatible: T },
#[error(transparent)]
Compat(#[from] CompatError<T>),
}
Expand Down Expand Up @@ -142,8 +139,8 @@ pub enum PathBeneathError {
/// whereas the file descriptor doesn't point to a directory.
#[error("incompatible directory-only access-rights: {incompatible:?}")]
DirectoryAccess {
access: BitFlags<AccessFs>,
incompatible: BitFlags<AccessFs>,
access: AccessFs,
incompatible: AccessFs,
},
}

Expand All @@ -157,24 +154,18 @@ where
/// kernel.
#[error("empty access-right")]
Empty,
/// The access-rights set was forged with the unsafe `BitFlags::from_bits_unchecked()` and it
/// The access-rights set was forged with `Access*::from_bits_retain()` and it
/// contains unknown bits.
#[error("unknown access-rights (at build time): {unknown:?}")]
Unknown {
access: BitFlags<T>,
unknown: BitFlags<T>,
},
Unknown { access: T, unknown: T },
/// The best-effort approach was (deliberately) disabled and the requested access-rights are
/// fully incompatible with the running kernel.
#[error("fully incompatible access-rights: {access:?}")]
Incompatible { access: BitFlags<T> },
Incompatible { access: T },
/// The best-effort approach was (deliberately) disabled and the requested access-rights are
/// partially incompatible with the running kernel.
#[error("partially incompatible access-rights: {incompatible:?}")]
PartiallyCompatible {
access: BitFlags<T>,
incompatible: BitFlags<T>,
},
PartiallyCompatible { access: T, incompatible: T },
}

#[derive(Debug, Error)]
Expand Down
Loading

0 comments on commit a8ac3cf

Please sign in to comment.