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    /// Bind to a UDP port.
51    ///
52    /// This also covers the implicit bind to an ephemeral port performed by the
53    /// kernel when setting the remote peer of a socket or sending a first
54    /// datagram from an unbound socket.  See [`AccessNet::ConnectSendUdp`].
55    BindUdp = uapi::LANDLOCK_ACCESS_NET_BIND_UDP as u64,
56    /// Set the remote port of a UDP socket with `connect(2)`, or send a datagram
57    /// to an explicit remote port with e.g. `sendto(2)` or `sendmsg(2)`,
58    /// ignoring any destination already pre-set on the socket.
59    ///
60    /// Because the kernel auto-binds an unbound UDP socket to an ephemeral local
61    /// port when doing so, a ruleset handling both this right and
62    /// [`AccessNet::BindUdp`] must also allow that bind: either grant [`AccessNet::BindUdp`]
63    /// on port 0 (i.e. any ephemeral port), or grant it on a specific port and
64    /// `bind(2)` that port first, or use a socket bound before the ruleset was
65    /// enforced.
66    ///
67    /// Note that sending a datagram to an `AF_UNSPEC` destination address family
68    /// is not supported for IPv6 UDP sockets; a `NULL` address must be used
69    /// instead.
70    ConnectSendUdp = uapi::LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP as u64,
71}
72
73/// # Warning
74///
75/// If `ABI <= ABI::V3`, `AccessNet::from_all()` returns an empty `BitFlags<AccessNet>`, which
76/// makes `Ruleset::handle_access(AccessNet::from_all(ABI::V3))` return an error.
77impl Access for AccessNet {
78    fn from_all(abi: ABI) -> BitFlags<Self> {
79        match abi {
80            ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 => BitFlags::EMPTY,
81            ABI::V4 | ABI::V5 | ABI::V6 | ABI::V7 | ABI::V8 | ABI::V9 => {
82                AccessNet::BindTcp | AccessNet::ConnectTcp
83            }
84            ABI::V10 => Self::from_all(ABI::V9) | AccessNet::BindUdp | AccessNet::ConnectSendUdp,
85        }
86    }
87}
88
89#[test]
90fn access_net_from_all() {
91    let tcp = AccessNet::BindTcp | AccessNet::ConnectTcp;
92    let udp = AccessNet::BindUdp | AccessNet::ConnectSendUdp;
93
94    assert_eq!(AccessNet::from_all(ABI::V3), BitFlags::EMPTY);
95    assert_eq!(AccessNet::from_all(ABI::V4), tcp);
96    assert_eq!(AccessNet::from_all(ABI::V9), tcp);
97    assert_eq!(AccessNet::from_all(ABI::V10), tcp | udp);
98}
99
100impl HandledAccess for AccessNet {}
101
102impl PrivateHandledAccess for AccessNet {
103    fn ruleset_handle_access(
104        ruleset: &mut Ruleset,
105        access: BitFlags<Self>,
106    ) -> Result<(), HandleAccessesError> {
107        // We need to record the requested accesses for PrivateRule::check_consistency().
108        ruleset.requested_handled_net |= access;
109        ruleset.actual_handled_net |= match access
110            .try_compat(
111                ruleset.compat.abi(),
112                ruleset.compat.level,
113                &mut ruleset.compat.state,
114            )
115            .map_err(HandleAccessError::Compat)?
116        {
117            Some(a) => a,
118            None => return Ok(()),
119        };
120        Ok(())
121    }
122
123    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
124        AddRulesError::Net(error)
125    }
126
127    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
128        HandleAccessesError::Net(error)
129    }
130}
131
132/// Landlock rule for a network port.
133///
134/// # Example
135///
136/// ```
137/// use landlock::{AccessNet, NetPort};
138///
139/// fn bind_http() -> NetPort {
140///     NetPort::new(80, AccessNet::BindTcp)
141/// }
142/// ```
143#[derive(Debug)]
144pub struct NetPort {
145    attr: uapi::landlock_net_port_attr,
146    // Only 16-bit port make sense for now.
147    port: u16,
148    allowed_access: BitFlags<AccessNet>,
149    compat_level: Option<CompatLevel>,
150}
151
152// If we need support for 32 or 64 ports, we'll add a new_32() or a new_64() method returning a
153// Result with a potential overflow error.
154impl NetPort {
155    /// Creates a new TCP or UDP port rule.
156    ///
157    /// As defined by the Linux ABI, `port` with a value of `0` means that bindings will be
158    /// allowed for a port range defined by `/proc/sys/net/ipv4/ip_local_port_range`.
159    pub fn new<A>(port: u16, access: A) -> Self
160    where
161        A: Into<BitFlags<AccessNet>>,
162    {
163        NetPort {
164            // Invalid access-rights until as_ptr() is called.
165            attr: unsafe { zeroed() },
166            port,
167            allowed_access: access.into(),
168            compat_level: None,
169        }
170    }
171}
172
173impl Rule<AccessNet> for NetPort {}
174
175impl PrivateRule<AccessNet> for NetPort {
176    const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_NET_PORT;
177
178    fn as_ptr(&mut self) -> *const libc::c_void {
179        self.attr.port = self.port as u64;
180        self.attr.allowed_access = self.allowed_access.bits();
181        &self.attr as *const _ as _
182    }
183
184    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {
185        // Checks that this rule doesn't contain a superset of the access-rights handled by the
186        // ruleset.  This check is about requested access-rights but not actual access-rights.
187        // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel
188        // (which is handled by Ruleset and RulesetCreated).
189        if ruleset.requested_handled_net.contains(self.allowed_access) {
190            Ok(())
191        } else {
192            Err(AddRuleError::UnhandledAccess {
193                access: self.allowed_access,
194                incompatible: self.allowed_access & !ruleset.requested_handled_net,
195            }
196            .into())
197        }
198    }
199}
200
201#[test]
202fn net_port_check_consistency() {
203    use crate::*;
204
205    let bind = AccessNet::BindTcp;
206    let bind_connect = bind | AccessNet::ConnectTcp;
207
208    assert!(matches!(
209        Ruleset::from(ABI::Unsupported)
210            .handle_access(bind)
211            .unwrap()
212            .create()
213            .unwrap()
214            .add_rule(NetPort::new(1, bind_connect))
215            .unwrap_err(),
216        RulesetError::AddRules(AddRulesError::Net(AddRuleError::UnhandledAccess { access, incompatible }))
217            if access == bind_connect && incompatible == AccessNet::ConnectTcp
218    ));
219}
220
221impl TryCompat<AccessNet> for NetPort {
222    fn try_compat_children<L>(
223        mut self,
224        abi: ABI,
225        parent_level: L,
226        compat_state: &mut CompatState,
227    ) -> Result<Option<Self>, CompatError<AccessNet>>
228    where
229        L: Into<CompatLevel>,
230    {
231        // Checks with our own compatibility level, if any.
232        self.allowed_access = match self.allowed_access.try_compat(
233            abi,
234            self.tailored_compat_level(parent_level),
235            compat_state,
236        )? {
237            Some(a) => a,
238            None => return Ok(None),
239        };
240        Ok(Some(self))
241    }
242
243    fn try_compat_inner(
244        &mut self,
245        _abi: ABI,
246    ) -> Result<CompatResult<AccessNet>, CompatError<AccessNet>> {
247        Ok(CompatResult::Full)
248    }
249}
250
251impl OptionCompatLevelMut for NetPort {
252    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
253        &mut self.compat_level
254    }
255}
256
257impl OptionCompatLevelMut for &mut NetPort {
258    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
259        &mut self.compat_level
260    }
261}
262
263impl Compatible for NetPort {}
264
265impl Compatible for &mut NetPort {}