landlock/
net.rs

1use crate::compat::private::OptionCompatLevelMut;
2use crate::{
3    uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState,
4    Compatible, HandleAccessError, HandleAccessesError, PrivateAccess, PrivateRule, Rule, Ruleset,
5    RulesetCreated, TailoredCompatLevel, TryCompat, ABI,
6};
7use enumflags2::{bitflags, BitFlags};
8use std::mem::zeroed;
9
10/// Network access right.
11///
12/// Each variant of `AccessNet` is an [access right](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights)
13/// for the network.
14/// A set of access rights can be created with [`BitFlags<AccessNet>`](BitFlags).
15///
16/// # Example
17///
18/// ```
19/// use landlock::{ABI, Access, AccessNet, BitFlags, make_bitflags};
20///
21/// let bind = AccessNet::BindTcp;
22///
23/// let bind_set: BitFlags<AccessNet> = bind.into();
24///
25/// let bind_connect = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
26///
27/// let net_v4 = AccessNet::from_all(ABI::V4);
28///
29/// assert_eq!(bind_connect, net_v4);
30/// ```
31///
32/// # Warning
33///
34/// To avoid unknown restrictions **don't use `BitFlags::<AccessNet>::all()` nor `BitFlags::ALL`**,
35/// but use a version you tested and vetted instead,
36/// for instance [`AccessNet::from_all(ABI::V4)`](Access::from_all).
37/// Direct use of **the [`BitFlags`] API is deprecated**.
38/// See [`ABI`] for the rationale and help to test it.
39#[bitflags]
40#[repr(u64)]
41#[derive(Copy, Clone, Debug, PartialEq, Eq)]
42#[non_exhaustive]
43pub enum AccessNet {
44    /// Bind to a TCP port.
45    BindTcp = uapi::LANDLOCK_ACCESS_NET_BIND_TCP as u64,
46    /// Connect to a TCP port.
47    ConnectTcp = uapi::LANDLOCK_ACCESS_NET_CONNECT_TCP as u64,
48}
49
50/// # Warning
51///
52/// If `ABI <= ABI::V3`, `AccessNet::from_all()` returns an empty `BitFlags<AccessNet>`, which
53/// makes `Ruleset::handle_access(AccessNet::from_all(ABI::V3))` return an error.
54impl Access for AccessNet {
55    fn from_all(abi: ABI) -> BitFlags<Self> {
56        match abi {
57            ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 => BitFlags::EMPTY,
58            ABI::V4 | ABI::V5 => AccessNet::BindTcp | AccessNet::ConnectTcp,
59        }
60    }
61}
62
63impl PrivateAccess for AccessNet {
64    fn ruleset_handle_access(
65        ruleset: &mut Ruleset,
66        access: BitFlags<Self>,
67    ) -> Result<(), HandleAccessesError> {
68        // We need to record the requested accesses for PrivateRule::check_consistency().
69        ruleset.requested_handled_net |= access;
70        ruleset.actual_handled_net |= match access
71            .try_compat(
72                ruleset.compat.abi(),
73                ruleset.compat.level,
74                &mut ruleset.compat.state,
75            )
76            .map_err(HandleAccessError::Compat)?
77        {
78            Some(a) => a,
79            None => return Ok(()),
80        };
81        Ok(())
82    }
83
84    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
85        AddRulesError::Net(error)
86    }
87
88    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
89        HandleAccessesError::Net(error)
90    }
91}
92
93/// Landlock rule for a network port.
94///
95/// # Example
96///
97/// ```
98/// use landlock::{AccessNet, NetPort};
99///
100/// fn bind_http() -> NetPort {
101///     NetPort::new(80, AccessNet::BindTcp)
102/// }
103/// ```
104#[cfg_attr(test, derive(Debug))]
105pub struct NetPort {
106    attr: uapi::landlock_net_port_attr,
107    // Only 16-bit port make sense for now.
108    port: u16,
109    allowed_access: BitFlags<AccessNet>,
110    compat_level: Option<CompatLevel>,
111}
112
113// If we need support for 32 or 64 ports, we'll add a new_32() or a new_64() method returning a
114// Result with a potential overflow error.
115impl NetPort {
116    /// Creates a new TCP port rule.
117    ///
118    /// As defined by the Linux ABI, `port` with a value of `0` means that TCP bindings will be
119    /// allowed for a port range defined by `/proc/sys/net/ipv4/ip_local_port_range`.
120    pub fn new<A>(port: u16, access: A) -> Self
121    where
122        A: Into<BitFlags<AccessNet>>,
123    {
124        NetPort {
125            // Invalid access-rights until as_ptr() is called.
126            attr: unsafe { zeroed() },
127            port,
128            allowed_access: access.into(),
129            compat_level: None,
130        }
131    }
132}
133
134impl Rule<AccessNet> for NetPort {}
135
136impl PrivateRule<AccessNet> for NetPort {
137    const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_NET_PORT;
138
139    fn as_ptr(&mut self) -> *const libc::c_void {
140        self.attr.port = self.port as u64;
141        self.attr.allowed_access = self.allowed_access.bits();
142        &self.attr as *const _ as _
143    }
144
145    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {
146        // Checks that this rule doesn't contain a superset of the access-rights handled by the
147        // ruleset.  This check is about requested access-rights but not actual access-rights.
148        // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel
149        // (which is handled by Ruleset and RulesetCreated).
150        if ruleset.requested_handled_net.contains(self.allowed_access) {
151            Ok(())
152        } else {
153            Err(AddRuleError::UnhandledAccess {
154                access: self.allowed_access,
155                incompatible: self.allowed_access & !ruleset.requested_handled_net,
156            }
157            .into())
158        }
159    }
160}
161
162#[test]
163fn net_port_check_consistency() {
164    use crate::*;
165
166    let bind = AccessNet::BindTcp;
167    let bind_connect = bind | AccessNet::ConnectTcp;
168
169    assert!(matches!(
170        Ruleset::from(ABI::Unsupported)
171            .handle_access(bind)
172            .unwrap()
173            .create()
174            .unwrap()
175            .add_rule(NetPort::new(1, bind_connect))
176            .unwrap_err(),
177        RulesetError::AddRules(AddRulesError::Net(AddRuleError::UnhandledAccess { access, incompatible }))
178            if access == bind_connect && incompatible == AccessNet::ConnectTcp
179    ));
180}
181
182impl TryCompat<AccessNet> for NetPort {
183    fn try_compat_children<L>(
184        mut self,
185        abi: ABI,
186        parent_level: L,
187        compat_state: &mut CompatState,
188    ) -> Result<Option<Self>, CompatError<AccessNet>>
189    where
190        L: Into<CompatLevel>,
191    {
192        // Checks with our own compatibility level, if any.
193        self.allowed_access = match self.allowed_access.try_compat(
194            abi,
195            self.tailored_compat_level(parent_level),
196            compat_state,
197        )? {
198            Some(a) => a,
199            None => return Ok(None),
200        };
201        Ok(Some(self))
202    }
203
204    fn try_compat_inner(
205        &mut self,
206        _abi: ABI,
207    ) -> Result<CompatResult<AccessNet>, CompatError<AccessNet>> {
208        Ok(CompatResult::Full)
209    }
210}
211
212impl OptionCompatLevelMut for NetPort {
213    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
214        &mut self.compat_level
215    }
216}
217
218impl OptionCompatLevelMut for &mut NetPort {
219    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
220        &mut self.compat_level
221    }
222}
223
224impl Compatible for NetPort {}
225
226impl Compatible for &mut NetPort {}