landlock/net.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
use crate::compat::private::OptionCompatLevelMut;
use crate::{
uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState,
Compatible, HandleAccessError, HandleAccessesError, PrivateAccess, PrivateRule, Rule, Ruleset,
RulesetCreated, TailoredCompatLevel, TryCompat, ABI,
};
use enumflags2::{bitflags, BitFlags};
use std::mem::zeroed;
/// 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 network.
/// 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 | ABI::V5 => 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)
}
}
/// Landlock rule for a network port.
///
/// # Example
///
/// ```
/// use landlock::{AccessNet, NetPort};
///
/// fn bind_http() -> NetPort {
/// NetPort::new(80, AccessNet::BindTcp)
/// }
/// ```
#[cfg_attr(test, derive(Debug))]
pub struct NetPort {
attr: uapi::landlock_net_port_attr,
// Only 16-bit port make sense for now.
port: u16,
allowed_access: BitFlags<AccessNet>,
compat_level: Option<CompatLevel>,
}
// If we need support for 32 or 64 ports, we'll add a new_32() or a new_64() method returning a
// Result with a potential overflow error.
impl NetPort {
/// Creates a new TCP port rule.
///
/// As defined by the Linux ABI, `port` with a value of `0` means that TCP bindings will be
/// allowed for a port range defined by `/proc/sys/net/ipv4/ip_local_port_range`.
pub fn new<A>(port: u16, access: A) -> Self
where
A: Into<BitFlags<AccessNet>>,
{
NetPort {
// Invalid access-rights until as_ptr() is called.
attr: unsafe { zeroed() },
port,
allowed_access: access.into(),
compat_level: None,
}
}
}
impl Rule<AccessNet> for NetPort {}
impl PrivateRule<AccessNet> 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 as u64;
self.attr.allowed_access = self.allowed_access.bits();
&self.attr as *const _ as _
}
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())
}
}
}
#[test]
fn net_port_check_consistency() {
use crate::*;
let bind = AccessNet::BindTcp;
let bind_connect = bind | AccessNet::ConnectTcp;
assert!(matches!(
Ruleset::from(ABI::Unsupported)
.handle_access(bind)
.unwrap()
.create()
.unwrap()
.add_rule(NetPort::new(1, bind_connect))
.unwrap_err(),
RulesetError::AddRules(AddRulesError::Net(AddRuleError::UnhandledAccess { access, incompatible }))
if access == bind_connect && incompatible == AccessNet::ConnectTcp
));
}
impl TryCompat<AccessNet> for NetPort {
fn try_compat_children<L>(
mut self,
abi: ABI,
parent_level: L,
compat_state: &mut CompatState,
) -> Result<Option<Self>, CompatError<AccessNet>>
where
L: Into<CompatLevel>,
{
// 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(
&mut self,
_abi: ABI,
) -> Result<CompatResult<AccessNet>, CompatError<AccessNet>> {
Ok(CompatResult::Full)
}
}
impl OptionCompatLevelMut for NetPort {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat_level
}
}
impl OptionCompatLevelMut for &mut NetPort {
fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
&mut self.compat_level
}
}
impl Compatible for NetPort {}
impl Compatible for &mut NetPort {}