diff --git a/src/errors.rs b/src/errors.rs index de018741..77f54771 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -124,6 +124,8 @@ where PathBeneath(#[from] PathBeneathError), #[error(transparent)] Access(#[from] AccessError), + #[error(transparent)] + NetPort(#[from] NetPortError), } #[derive(Debug, Error)] @@ -147,6 +149,15 @@ pub enum PathBeneathError { }, } +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum NetPortError { + /// For now, only 32-bit ports are supported. + #[error("port value is greater than 2^32 - 1")] + #[non_exhaustive] + PortOverflow, +} + #[derive(Debug, Error)] // Exhaustive enum pub enum AccessError diff --git a/src/lib.rs b/src/lib.rs index bb8e4377..23376701 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,10 +84,11 @@ pub use compat::{CompatLevel, Compatible, ABI}; pub use enumflags2::{make_bitflags, BitFlags}; pub use errors::{ AccessError, AddRuleError, AddRulesError, CompatError, CreateRulesetError, HandleAccessError, - HandleAccessesError, PathBeneathError, PathFdError, RestrictSelfError, RulesetError, + HandleAccessesError, NetPortError, PathBeneathError, PathFdError, RestrictSelfError, + RulesetError, }; pub use fs::{path_beneath_rules, AccessFs, PathBeneath, PathFd}; -pub use net::AccessNet; +pub use net::{AccessNet, NetPort}; pub use ruleset::{ RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr, RulesetStatus, diff --git a/src/net.rs b/src/net.rs index c2863e36..44a25cca 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,8 +1,11 @@ +use crate::compat::private::OptionCompatLevelMut; use crate::{ - uapi, Access, AddRuleError, AddRulesError, HandleAccessError, HandleAccessesError, - PrivateAccess, Ruleset, TryCompat, ABI, + uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState, + Compatible, HandleAccessError, HandleAccessesError, NetPortError, PrivateAccess, PrivateRule, + Ruleset, RulesetCreated, TailoredCompatLevel, TryCompat, ABI, }; use enumflags2::{bitflags, BitFlags}; +use std::mem::zeroed; /// Network access right. /// @@ -86,3 +89,135 @@ impl PrivateAccess for AccessNet { HandleAccessesError::Net(error) } } + +/// Landlock rule for a network port. +/// +/// TODO: Add example, see PathBeneath +#[cfg_attr(test, derive(Debug))] +pub struct NetPort { + attr: uapi::landlock_net_port_attr, + port: u64, + allowed_access: BitFlags, + compat_level: Option, +} + +impl NetPort { + // FIXME: use type system to forbid ports > 32-bit + pub fn new(port: u64, access: A) -> Self + where + A: Into>, + { + NetPort { + // Invalid access-rights until as_ptr() is called. + attr: unsafe { zeroed() }, + port, + allowed_access: access.into(), + compat_level: None, + } + } +} + +impl PrivateRule for NetPort { + const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_NET_PORT; + + fn as_ptr(&mut self) -> *const libc::c_void { + self.attr.port = self.port; + self.attr.allowed_access = self.allowed_access.bits(); + &self.attr as *const _ as _ + } + + // TODO: Add tests + fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> { + // Checks that this rule doesn't contain a superset of the access-rights handled by the + // ruleset. This check is about requested access-rights but not actual access-rights. + // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel + // (which is handled by Ruleset and RulesetCreated). + if ruleset.requested_handled_net.contains(self.allowed_access) { + Ok(()) + } else { + Err(AddRuleError::UnhandledAccess { + access: self.allowed_access, + incompatible: self.allowed_access & !ruleset.requested_handled_net, + } + .into()) + } + } +} + +impl TryCompat for NetPort { + fn try_compat_children( + mut self, + abi: ABI, + parent_level: L, + compat_state: &mut CompatState, + ) -> Result, CompatError> + where + L: Into, + { + // Checks with our own compatibility level, if any. + self.allowed_access = match self.allowed_access.try_compat( + abi, + self.tailored_compat_level(parent_level), + compat_state, + )? { + Some(a) => a, + None => return Ok(None), + }; + Ok(Some(self)) + } + + fn try_compat_inner( + self, + _abi: ABI, + ) -> Result, CompatError> { + // TODO: check port 0? + if self.port > u16::MAX.into() { + return Err(CompatError::NetPort(NetPortError::PortOverflow)); + } + Ok(CompatResult::Full(self)) + } +} + +#[cfg(test)] +fn port_overflow(abi: ABI) { + use crate::*; + + let net_port = NetPort::new(65535, AccessNet::ConnectTcp); + net_port.try_compat_inner(abi).unwrap(); + + let mut compat_state = CompatState::Init; + let net_port = NetPort::new(65536, AccessNet::ConnectTcp); + assert!(matches!( + net_port + // FIXME: + .try_compat(abi, CompatLevel::BestEffort, &mut compat_state) + .unwrap_err(), + CompatError::NetPort(NetPortError::PortOverflow) + )); +} + +#[test] +fn port_overflow_v3() { + port_overflow(ABI::V3); +} + +#[test] +fn port_overflow_v4() { + port_overflow(ABI::V4); +} + +impl OptionCompatLevelMut for NetPort { + fn as_option_compat_level_mut(&mut self) -> &mut Option { + &mut self.compat_level + } +} + +impl OptionCompatLevelMut for &mut NetPort { + fn as_option_compat_level_mut(&mut self) -> &mut Option { + &mut self.compat_level + } +} + +impl Compatible for NetPort {} + +impl Compatible for &mut NetPort {} diff --git a/src/ruleset.rs b/src/ruleset.rs index 8caeb780..90c30041 100644 --- a/src/ruleset.rs +++ b/src/ruleset.rs @@ -592,6 +592,7 @@ pub struct RulesetCreated { fd: RawFd, no_new_privs: bool, pub(crate) requested_handled_fs: BitFlags, + pub(crate) requested_handled_net: BitFlags, compat: Compatibility, } @@ -605,6 +606,7 @@ impl RulesetCreated { fd, no_new_privs: true, requested_handled_fs: ruleset.requested_handled_fs, + requested_handled_net: ruleset.requested_handled_net, compat: ruleset.compat, } } @@ -702,6 +704,7 @@ impl RulesetCreated { }, no_new_privs: self.no_new_privs, requested_handled_fs: self.requested_handled_fs, + requested_handled_net: self.requested_handled_net, compat: self.compat, }) } diff --git a/src/uapi/mod.rs b/src/uapi/mod.rs index 95245ec5..b1d67501 100644 --- a/src/uapi/mod.rs +++ b/src/uapi/mod.rs @@ -6,10 +6,12 @@ mod landlock; #[rustfmt::skip] pub use self::landlock::{ + landlock_net_port_attr, landlock_path_beneath_attr, - landlock_ruleset_attr, landlock_rule_type, + landlock_rule_type_LANDLOCK_RULE_NET_PORT, landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH, + landlock_ruleset_attr, LANDLOCK_ACCESS_FS_EXECUTE, LANDLOCK_ACCESS_FS_WRITE_FILE, LANDLOCK_ACCESS_FS_READ_FILE,