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 => AccessNet::BindTcp | AccessNet::ConnectTcp,
61        }
62    }
63}
64
65impl HandledAccess for AccessNet {}
66
67impl PrivateHandledAccess for AccessNet {
68    fn ruleset_handle_access(
69        ruleset: &mut Ruleset,
70        access: BitFlags<Self>,
71    ) -> Result<(), HandleAccessesError> {
72        // We need to record the requested accesses for PrivateRule::check_consistency().
73        ruleset.requested_handled_net |= access;
74        ruleset.actual_handled_net |= match access
75            .try_compat(
76                ruleset.compat.abi(),
77                ruleset.compat.level,
78                &mut ruleset.compat.state,
79            )
80            .map_err(HandleAccessError::Compat)?
81        {
82            Some(a) => a,
83            None => return Ok(()),
84        };
85        Ok(())
86    }
87
88    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
89        AddRulesError::Net(error)
90    }
91
92    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
93        HandleAccessesError::Net(error)
94    }
95}
96
97/// Landlock rule for a network port.
98///
99/// # Example
100///
101/// ```
102/// use landlock::{AccessNet, NetPort};
103///
104/// fn bind_http() -> NetPort {
105///     NetPort::new(80, AccessNet::BindTcp)
106/// }
107/// ```
108#[cfg_attr(test, derive(Debug))]
109pub struct NetPort {
110    attr: uapi::landlock_net_port_attr,
111    // Only 16-bit port make sense for now.
112    port: u16,
113    allowed_access: BitFlags<AccessNet>,
114    compat_level: Option<CompatLevel>,
115}
116
117// If we need support for 32 or 64 ports, we'll add a new_32() or a new_64() method returning a
118// Result with a potential overflow error.
119impl NetPort {
120    /// Creates a new TCP port rule.
121    ///
122    /// As defined by the Linux ABI, `port` with a value of `0` means that TCP bindings will be
123    /// allowed for a port range defined by `/proc/sys/net/ipv4/ip_local_port_range`.
124    pub fn new<A>(port: u16, access: A) -> Self
125    where
126        A: Into<BitFlags<AccessNet>>,
127    {
128        NetPort {
129            // Invalid access-rights until as_ptr() is called.
130            attr: unsafe { zeroed() },
131            port,
132            allowed_access: access.into(),
133            compat_level: None,
134        }
135    }
136}
137
138impl Rule<AccessNet> for NetPort {}
139
140impl PrivateRule<AccessNet> for NetPort {
141    const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_NET_PORT;
142
143    fn as_ptr(&mut self) -> *const libc::c_void {
144        self.attr.port = self.port as u64;
145        self.attr.allowed_access = self.allowed_access.bits();
146        &self.attr as *const _ as _
147    }
148
149    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {
150        // Checks that this rule doesn't contain a superset of the access-rights handled by the
151        // ruleset.  This check is about requested access-rights but not actual access-rights.
152        // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel
153        // (which is handled by Ruleset and RulesetCreated).
154        if ruleset.requested_handled_net.contains(self.allowed_access) {
155            Ok(())
156        } else {
157            Err(AddRuleError::UnhandledAccess {
158                access: self.allowed_access,
159                incompatible: self.allowed_access & !ruleset.requested_handled_net,
160            }
161            .into())
162        }
163    }
164}
165
166#[test]
167fn net_port_check_consistency() {
168    use crate::*;
169
170    let bind = AccessNet::BindTcp;
171    let bind_connect = bind | AccessNet::ConnectTcp;
172
173    assert!(matches!(
174        Ruleset::from(ABI::Unsupported)
175            .handle_access(bind)
176            .unwrap()
177            .create()
178            .unwrap()
179            .add_rule(NetPort::new(1, bind_connect))
180            .unwrap_err(),
181        RulesetError::AddRules(AddRulesError::Net(AddRuleError::UnhandledAccess { access, incompatible }))
182            if access == bind_connect && incompatible == AccessNet::ConnectTcp
183    ));
184}
185
186impl TryCompat<AccessNet> for NetPort {
187    fn try_compat_children<L>(
188        mut self,
189        abi: ABI,
190        parent_level: L,
191        compat_state: &mut CompatState,
192    ) -> Result<Option<Self>, CompatError<AccessNet>>
193    where
194        L: Into<CompatLevel>,
195    {
196        // Checks with our own compatibility level, if any.
197        self.allowed_access = match self.allowed_access.try_compat(
198            abi,
199            self.tailored_compat_level(parent_level),
200            compat_state,
201        )? {
202            Some(a) => a,
203            None => return Ok(None),
204        };
205        Ok(Some(self))
206    }
207
208    fn try_compat_inner(
209        &mut self,
210        _abi: ABI,
211    ) -> Result<CompatResult<AccessNet>, CompatError<AccessNet>> {
212        Ok(CompatResult::Full)
213    }
214}
215
216impl OptionCompatLevelMut for NetPort {
217    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
218        &mut self.compat_level
219    }
220}
221
222impl OptionCompatLevelMut for &mut NetPort {
223    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
224        &mut self.compat_level
225    }
226}
227
228impl Compatible for NetPort {}
229
230impl Compatible for &mut NetPort {}