Skip to main content

landlock/
net.rs

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