Skip to content

Commit

Permalink
net: Handle TCP bind and connect access rights
Browse files Browse the repository at this point in the history
Add the AccessNet::BindTcp and AccessNet::ConnectTcp rights.

Add ruleset_created_handle_access_net test to check that handled and
actual access righs are consistent according to the Landlock ABI.

Rename the ruleset_created_handle_access_or test to
ruleset_created_handle_access_fs.

It should be noted that handle_access(AccessNet::from_all(ABI::V3))
returns an error because of the empty access bitflags.

Signed-off-by: Mickaël Salaün <[email protected]>
  • Loading branch information
l0kod committed May 31, 2024
1 parent f0f4b74 commit afd2999
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 8 deletions.
6 changes: 5 additions & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Access, AccessFs, BitFlags};
use crate::{Access, AccessFs, AccessNet, BitFlags};
use std::io;
use std::path::PathBuf;
use thiserror::Error;
Expand Down Expand Up @@ -43,6 +43,8 @@ where
pub enum HandleAccessesError {
#[error(transparent)]
Fs(HandleAccessError<AccessFs>),
#[error(transparent)]
Net(HandleAccessError<AccessNet>),
}

// Generically implement for all the access implementations rather than for the cases listed in
Expand Down Expand Up @@ -108,6 +110,8 @@ where
pub enum AddRulesError {
#[error(transparent)]
Fs(AddRuleError<AccessFs>),
#[error(transparent)]
Net(AddRuleError<AccessNet>),
}

#[derive(Debug, Error)]
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ pub use errors::{
HandleAccessesError, PathBeneathError, PathFdError, RestrictSelfError, RulesetError,
};
pub use fs::{path_beneath_rules, AccessFs, PathBeneath, PathFd};
pub use net::AccessNet;
pub use ruleset::{
RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr,
RulesetStatus,
Expand All @@ -107,6 +108,7 @@ mod access;
mod compat;
mod errors;
mod fs;
mod net;
mod ruleset;
mod uapi;

Expand Down
88 changes: 88 additions & 0 deletions src/net.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use crate::{
uapi, Access, AddRuleError, AddRulesError, HandleAccessError, HandleAccessesError,
PrivateAccess, Ruleset, TryCompat, ABI,
};
use enumflags2::{bitflags, BitFlags};

/// Network access right.
///
/// Each variant of `AccessNet` is an [access right](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights)
/// for the file system.
/// A set of access rights can be created with [`BitFlags<AccessNet>`](BitFlags).
///
/// # Example
///
/// ```
/// use landlock::{ABI, Access, AccessNet, BitFlags, make_bitflags};
///
/// let bind = AccessNet::BindTcp;
///
/// let bind_set: BitFlags<AccessNet> = bind.into();
///
/// let bind_connect = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
///
/// let net_v4 = AccessNet::from_all(ABI::V4);
///
/// assert_eq!(bind_connect, net_v4);
/// ```
///
/// # Warning
///
/// To avoid unknown restrictions **don't use `BitFlags::<AccessNet>::all()` nor `BitFlags::ALL`**,
/// but use a version you tested and vetted instead,
/// for instance [`AccessNet::from_all(ABI::V4)`](Access::from_all).
/// Direct use of **the [`BitFlags`] API is deprecated**.
/// See [`ABI`] for the rationale and help to test it.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum AccessNet {
/// Bind to a TCP port.
BindTcp = uapi::LANDLOCK_ACCESS_NET_BIND_TCP as u64,
/// Connect to a TCP port.
ConnectTcp = uapi::LANDLOCK_ACCESS_NET_CONNECT_TCP as u64,
}

/// # Warning
///
/// If `ABI <= ABI::V3`, `AccessNet::from_all()` returns an empty `BitFlags<AccessNet>`, which
/// makes `Ruleset::handle_access(AccessNet::from_all(ABI::V3))` return an error.
impl Access for AccessNet {
fn from_all(abi: ABI) -> BitFlags<Self> {
match abi {
ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 => BitFlags::EMPTY,
ABI::V4 => AccessNet::BindTcp | AccessNet::ConnectTcp,
}
}
}

impl PrivateAccess for AccessNet {
fn ruleset_handle_access(
ruleset: &mut Ruleset,
access: BitFlags<Self>,
) -> Result<(), HandleAccessesError> {
// We need to record the requested accesses for PrivateRule::check_consistency().
ruleset.requested_handled_net |= access;
ruleset.actual_handled_net |= match access
.try_compat(
ruleset.compat.abi(),
ruleset.compat.level,
&mut ruleset.compat.state,
)
.map_err(HandleAccessError::Compat)?
{
Some(a) => a,
None => return Ok(()),
};
Ok(())
}

fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
AddRulesError::Net(error)
}

fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
HandleAccessesError::Net(error)
}
}
59 changes: 52 additions & 7 deletions src/ruleset.rs
Original file line number Diff line number Diff line change
@@ -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, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel,
CompatState, Compatibility, Compatible, CreateRulesetError, RestrictSelfError, RulesetError,
TryCompat,
};
use libc::close;
use std::io::Error;
Expand Down Expand Up @@ -171,7 +172,9 @@ fn support_no_new_privs() -> bool {
#[cfg_attr(test, derive(Debug))]
pub struct Ruleset {
pub(crate) requested_handled_fs: BitFlags<AccessFs>,
pub(crate) requested_handled_net: BitFlags<AccessNet>,
pub(crate) actual_handled_fs: BitFlags<AccessFs>,
pub(crate) actual_handled_net: BitFlags<AccessNet>,
pub(crate) compat: Compatibility,
}

Expand All @@ -180,7 +183,9 @@ impl From<Compatibility> for Ruleset {
Ruleset {
// Non-working default handled FS accesses to force users to set them explicitely.
requested_handled_fs: Default::default(),
requested_handled_net: Default::default(),
actual_handled_fs: Default::default(),
actual_handled_net: Default::default(),
compat,
}
}
Expand Down Expand Up @@ -240,21 +245,21 @@ impl Ruleset {
pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
let body = || -> Result<RulesetCreated, CreateRulesetError> {
// Checks that there is at least one requested access.
if self.requested_handled_fs.is_empty() {
if self.requested_handled_fs.is_empty() && self.requested_handled_net.is_empty() {
// No handle_access() call.
return Err(CreateRulesetError::MissingHandledAccess);
}

// The compatibility state is initialized by handle_access() and verified by the
// requested_handled_fs check.
// requested_handled_fs and requested_handled_net checks.
#[cfg(test)]
assert!(!matches!(self.compat.state, CompatState::Init));
if self.compat.state == CompatState::Init {
return Err(CreateRulesetError::MissingHandledAccess);
}

// Checks that the ruleset handles at least one access.
if self.actual_handled_fs.is_empty() {
if self.actual_handled_fs.is_empty() && self.actual_handled_net.is_empty() {
match self.compat.level.into() {
CompatLevel::BestEffort => {
self.compat.update(CompatState::No);
Expand All @@ -270,7 +275,7 @@ impl Ruleset {

let attr = uapi::landlock_ruleset_attr {
handled_access_fs: self.actual_handled_fs.bits(),
handled_access_net: 0,
handled_access_net: self.actual_handled_net.bits(),
};

match self.compat.state {
Expand Down Expand Up @@ -374,7 +379,7 @@ fn ruleset_attr() {
}

#[test]
fn ruleset_created_handle_access_or() {
fn ruleset_created_handle_access_fs() {
// Tests AccessFs::ruleset_handle_access()
let ruleset = Ruleset::from(ABI::V1)
.handle_access(AccessFs::Execute)
Expand All @@ -399,6 +404,34 @@ fn ruleset_created_handle_access_or() {
));
}

#[test]
fn ruleset_created_handle_access_net_tcp() {
let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});

// Tests AccessNet::ruleset_handle_access() with ABI that doesn't support TCP rights.
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);

// Tests AccessNet::ruleset_handle_access() with ABI that supports TCP rights.
let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
assert_eq!(ruleset.requested_handled_net, access);
assert_eq!(ruleset.actual_handled_net, access);

// Tests that only the required handled accesses are reported as incompatible:
// access should not contains AccessNet::BindTcp.
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
));
}

impl OptionCompatLevelMut for RulesetCreated {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat.level
Expand Down Expand Up @@ -921,3 +954,15 @@ fn ignore_abi_v2_with_abi_v1() {
}
);
}

#[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)
)))
);
}
2 changes: 2 additions & 0 deletions src/uapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub use self::landlock::{
LANDLOCK_ACCESS_FS_MAKE_SYM,
LANDLOCK_ACCESS_FS_REFER,
LANDLOCK_ACCESS_FS_TRUNCATE,
LANDLOCK_ACCESS_NET_BIND_TCP,
LANDLOCK_ACCESS_NET_CONNECT_TCP,
LANDLOCK_CREATE_RULESET_VERSION,
};

Expand Down

0 comments on commit afd2999

Please sign in to comment.