landlock/
ruleset.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use crate::compat::private::OptionCompatLevelMut;
4use crate::{
5    uapi, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel, CompatState,
6    Compatibility, Compatible, CreateRulesetError, HandledAccess, PrivateHandledAccess,
7    RestrictSelfError, RulesetError, Scope, ScopeError, TryCompat,
8};
9use std::io::Error;
10use std::mem::size_of_val;
11use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
12
13#[cfg(test)]
14use crate::*;
15
16// Public interface without methods and which is impossible to implement outside this crate.
17pub trait Rule<T>: PrivateRule<T>
18where
19    T: HandledAccess,
20{
21}
22
23// PrivateRule is not public outside this crate.
24pub trait PrivateRule<T>
25where
26    Self: TryCompat<T> + Compatible,
27    T: HandledAccess,
28{
29    const TYPE_ID: uapi::landlock_rule_type;
30
31    /// Returns a raw pointer to the rule's inner attribute.
32    ///
33    /// The caller must ensure that the rule outlives the pointer this function returns, or else it
34    /// will end up pointing to garbage.
35    fn as_ptr(&mut self) -> *const libc::c_void;
36
37    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError>;
38}
39
40/// Enforcement status of a ruleset.
41#[derive(Debug, PartialEq, Eq)]
42pub enum RulesetStatus {
43    /// All requested restrictions are enforced.
44    FullyEnforced,
45    /// Some requested restrictions are enforced,
46    /// following a best-effort approach.
47    PartiallyEnforced,
48    /// The running system doesn't support Landlock
49    /// or a subset of the requested Landlock features.
50    NotEnforced,
51}
52
53impl From<CompatState> for RulesetStatus {
54    fn from(state: CompatState) -> Self {
55        match state {
56            CompatState::Init | CompatState::No | CompatState::Dummy => RulesetStatus::NotEnforced,
57            CompatState::Full => RulesetStatus::FullyEnforced,
58            CompatState::Partial => RulesetStatus::PartiallyEnforced,
59        }
60    }
61}
62
63// The Debug, PartialEq and Eq implementations are useful for crate users to debug and check the
64// result of a Landlock ruleset enforcement.
65/// Status of a [`RulesetCreated`]
66/// after calling [`restrict_self()`](RulesetCreated::restrict_self).
67#[derive(Debug, PartialEq, Eq)]
68#[non_exhaustive]
69pub struct RestrictionStatus {
70    /// Status of the Landlock ruleset enforcement.
71    pub ruleset: RulesetStatus,
72    /// Status of `prctl(2)`'s `PR_SET_NO_NEW_PRIVS` enforcement.
73    pub no_new_privs: bool,
74}
75
76fn prctl_set_no_new_privs() -> Result<(), Error> {
77    match unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } {
78        0 => Ok(()),
79        _ => Err(Error::last_os_error()),
80    }
81}
82
83fn support_no_new_privs() -> bool {
84    // Only Linux < 3.5 or kernel with seccomp filters should return an error.
85    matches!(
86        unsafe { libc::prctl(libc::PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) },
87        0 | 1
88    )
89}
90
91/// Landlock ruleset builder.
92///
93/// `Ruleset` enables to create a Landlock ruleset in a flexible way
94/// following the builder pattern.
95/// Most build steps return a [`Result`] with [`RulesetError`].
96///
97/// You should probably not create more than one ruleset per application.
98/// Creating multiple rulesets is only useful when gradually restricting an application
99/// (e.g., a first set of generic restrictions before reading any file,
100/// then a second set of tailored restrictions after reading the configuration).
101///
102/// # Simple example
103///
104/// Simple helper handling only Landlock-related errors.
105///
106/// ```
107/// use landlock::{
108///     Access, AccessFs, PathBeneath, PathFd, RestrictionStatus, Ruleset, RulesetAttr,
109///     RulesetCreatedAttr, RulesetError, ABI,
110/// };
111/// use std::os::unix::io::AsFd;
112///
113/// fn restrict_fd<T>(hierarchy: T) -> Result<RestrictionStatus, RulesetError>
114/// where
115///     T: AsFd,
116/// {
117///     // The Landlock ABI should be incremented (and tested) regularly.
118///     let abi = ABI::V1;
119///     let access_all = AccessFs::from_all(abi);
120///     let access_read = AccessFs::from_read(abi);
121///     Ok(Ruleset::default()
122///         .handle_access(access_all)?
123///         .create()?
124///         .add_rule(PathBeneath::new(hierarchy, access_read))?
125///         .restrict_self()?)
126/// }
127///
128/// let fd = PathFd::new("/home").expect("failed to open /home");
129/// let status = restrict_fd(fd).expect("failed to build the ruleset");
130/// ```
131///
132/// # Generic example
133///
134/// More generic helper handling a set of file hierarchies
135/// and multiple types of error (i.e. [`RulesetError`](crate::RulesetError)
136/// and [`PathFdError`](crate::PathFdError).
137///
138/// ```
139/// use landlock::{
140///     Access, AccessFs, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
141///     RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
142/// };
143/// use thiserror::Error;
144///
145/// #[derive(Debug, Error)]
146/// enum MyRestrictError {
147///     #[error(transparent)]
148///     Ruleset(#[from] RulesetError),
149///     #[error(transparent)]
150///     AddRule(#[from] PathFdError),
151/// }
152///
153/// fn restrict_paths(hierarchies: &[&str]) -> Result<RestrictionStatus, MyRestrictError> {
154///     // The Landlock ABI should be incremented (and tested) regularly.
155///     let abi = ABI::V1;
156///     let access_all = AccessFs::from_all(abi);
157///     let access_read = AccessFs::from_read(abi);
158///     Ok(Ruleset::default()
159///         .handle_access(access_all)?
160///         .create()?
161///         .add_rules(
162///             hierarchies
163///                 .iter()
164///                 .map::<Result<_, MyRestrictError>, _>(|p| {
165///                     Ok(PathBeneath::new(PathFd::new(p)?, access_read))
166///                 }),
167///         )?
168///         .restrict_self()?)
169/// }
170///
171/// let status = restrict_paths(&["/usr", "/home"]).expect("failed to build the ruleset");
172/// ```
173#[cfg_attr(test, derive(Debug))]
174pub struct Ruleset {
175    pub(crate) requested_handled_fs: BitFlags<AccessFs>,
176    pub(crate) requested_handled_net: BitFlags<AccessNet>,
177    pub(crate) requested_scoped: BitFlags<Scope>,
178    pub(crate) actual_handled_fs: BitFlags<AccessFs>,
179    pub(crate) actual_handled_net: BitFlags<AccessNet>,
180    pub(crate) actual_scoped: BitFlags<Scope>,
181    pub(crate) compat: Compatibility,
182}
183
184impl From<Compatibility> for Ruleset {
185    fn from(compat: Compatibility) -> Self {
186        Ruleset {
187            // Non-working default handled FS accesses to force users to set them explicitely.
188            requested_handled_fs: Default::default(),
189            requested_handled_net: Default::default(),
190            requested_scoped: Default::default(),
191            actual_handled_fs: Default::default(),
192            actual_handled_net: Default::default(),
193            actual_scoped: Default::default(),
194            compat,
195        }
196    }
197}
198
199#[cfg(test)]
200impl From<ABI> for Ruleset {
201    fn from(abi: ABI) -> Self {
202        Ruleset::from(Compatibility::from(abi))
203    }
204}
205
206#[test]
207fn ruleset_add_rule_iter() {
208    assert!(matches!(
209        Ruleset::from(ABI::Unsupported)
210            .handle_access(AccessFs::Execute)
211            .unwrap()
212            .create()
213            .unwrap()
214            .add_rule(PathBeneath::new(
215                PathFd::new("/").unwrap(),
216                AccessFs::ReadFile
217            ))
218            .unwrap_err(),
219        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
220    ));
221}
222
223impl Default for Ruleset {
224    /// Returns a new `Ruleset`.
225    /// This call automatically probes the running kernel to know if it supports Landlock.
226    ///
227    /// To be able to successfully call [`create()`](Ruleset::create),
228    /// it is required to set the handled accesses with
229    /// [`handle_access()`](Ruleset::handle_access).
230    fn default() -> Self {
231        // The API should be future-proof: one Rust program or library should have the same
232        // behavior if built with an old or a newer crate (e.g. with an extended ruleset_attr
233        // enum).  It should then not be possible to give an "all-possible-handled-accesses" to the
234        // Ruleset builder because this value would be relative to the running kernel.
235        Compatibility::new().into()
236    }
237}
238
239impl Ruleset {
240    #[allow(clippy::new_without_default)]
241    #[deprecated(note = "Use Ruleset::default() instead")]
242    pub fn new() -> Self {
243        Ruleset::default()
244    }
245
246    /// Attempts to create a real Landlock ruleset (if supported by the running kernel).
247    /// The returned [`RulesetCreated`] is also a builder.
248    ///
249    /// On error, returns a wrapped [`CreateRulesetError`].
250    pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
251        let body = || -> Result<RulesetCreated, CreateRulesetError> {
252            match self.compat.state {
253                CompatState::Init => {
254                    // Checks that there is at least one requested access (e.g.
255                    // requested_handled_fs): one call to handle_access().
256                    Err(CreateRulesetError::MissingHandledAccess)
257                }
258                CompatState::No | CompatState::Dummy => {
259                    // There is at least one requested access.
260                    #[cfg(test)]
261                    assert!(
262                        !self.requested_handled_fs.is_empty()
263                            || !self.requested_handled_net.is_empty()
264                            || !self.requested_scoped.is_empty()
265                    );
266
267                    // CompatState::No should be handled as CompatState::Dummy because it is not
268                    // possible to create an actual ruleset.
269                    self.compat.update(CompatState::Dummy);
270                    match self.compat.level.into() {
271                        CompatLevel::HardRequirement => {
272                            Err(CreateRulesetError::MissingHandledAccess)
273                        }
274                        _ => Ok(RulesetCreated::new(self, None)),
275                    }
276                }
277                CompatState::Full | CompatState::Partial => {
278                    // There is at least one actual handled access.
279                    #[cfg(test)]
280                    assert!(
281                        !self.actual_handled_fs.is_empty()
282                            || !self.actual_handled_net.is_empty()
283                            || !self.actual_scoped.is_empty()
284                    );
285
286                    let attr = uapi::landlock_ruleset_attr {
287                        handled_access_fs: self.actual_handled_fs.bits(),
288                        handled_access_net: self.actual_handled_net.bits(),
289                        scoped: self.actual_scoped.bits(),
290                    };
291                    match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {
292                        fd if fd >= 0 => Ok(RulesetCreated::new(
293                            self,
294                            Some(unsafe { OwnedFd::from_raw_fd(fd) }),
295                        )),
296                        _ => Err(CreateRulesetError::CreateRulesetCall {
297                            source: Error::last_os_error(),
298                        }),
299                    }
300                }
301            }
302        };
303        Ok(body()?)
304    }
305}
306
307impl OptionCompatLevelMut for Ruleset {
308    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
309        &mut self.compat.level
310    }
311}
312
313impl OptionCompatLevelMut for &mut Ruleset {
314    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
315        &mut self.compat.level
316    }
317}
318
319impl Compatible for Ruleset {}
320
321impl Compatible for &mut Ruleset {}
322
323impl AsMut<Ruleset> for Ruleset {
324    fn as_mut(&mut self) -> &mut Ruleset {
325        self
326    }
327}
328
329// Tests unambiguous type.
330#[test]
331fn ruleset_as_mut() {
332    let mut ruleset = Ruleset::from(ABI::Unsupported);
333    let _ = ruleset.as_mut();
334
335    let mut ruleset_created = Ruleset::from(ABI::Unsupported)
336        .handle_access(AccessFs::Execute)
337        .unwrap()
338        .create()
339        .unwrap();
340    let _ = ruleset_created.as_mut();
341}
342
343pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
344    /// Attempts to add a set of access rights that will be supported by this ruleset.
345    /// By default, all actions requiring these access rights will be denied.
346    /// Consecutive calls to `handle_access()` will be interpreted as logical ORs
347    /// with the previous handled accesses.
348    ///
349    /// On error, returns a wrapped [`HandleAccessesError`](crate::HandleAccessesError).
350    /// E.g., `RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError<AccessFs>))`
351    fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
352    where
353        T: Into<BitFlags<U>>,
354        U: HandledAccess + PrivateHandledAccess,
355    {
356        U::ruleset_handle_access(self.as_mut(), access.into())?;
357        Ok(self)
358    }
359
360    /// Attempts to add a set of scopes that will be supported by this ruleset.
361    /// Consecutive calls to `scope()` will be interpreted as logical ORs
362    /// with the previous scopes.
363    ///
364    /// On error, returns a wrapped [`ScopeError`](crate::ScopeError).
365    /// E.g., `RulesetError::Scope(ScopeError)`
366    fn scope<T>(mut self, scope: T) -> Result<Self, RulesetError>
367    where
368        T: Into<BitFlags<Scope>>,
369    {
370        let scope = scope.into();
371        let ruleset = self.as_mut();
372        ruleset.requested_scoped |= scope;
373        if let Some(a) = scope
374            .try_compat(
375                ruleset.compat.abi(),
376                ruleset.compat.level,
377                &mut ruleset.compat.state,
378            )
379            .map_err(ScopeError::Compat)?
380        {
381            ruleset.actual_scoped |= a;
382        }
383        Ok(self)
384    }
385}
386
387impl RulesetAttr for Ruleset {}
388
389impl RulesetAttr for &mut Ruleset {}
390
391#[test]
392fn ruleset_attr() {
393    let mut ruleset = Ruleset::from(ABI::Unsupported);
394    let ruleset_ref = &mut ruleset;
395
396    // Can pass this reference to prepare the ruleset...
397    ruleset_ref
398        .set_compatibility(CompatLevel::BestEffort)
399        .handle_access(AccessFs::Execute)
400        .unwrap()
401        .handle_access(AccessFs::ReadFile)
402        .unwrap();
403
404    // ...and finally create the ruleset (thanks to non-lexical lifetimes).
405    ruleset
406        .set_compatibility(CompatLevel::BestEffort)
407        .handle_access(AccessFs::Execute)
408        .unwrap()
409        .handle_access(AccessFs::WriteFile)
410        .unwrap()
411        .create()
412        .unwrap();
413}
414
415#[test]
416fn ruleset_created_handle_access_fs() {
417    let access = make_bitflags!(AccessFs::{Execute | ReadDir});
418
419    // Tests AccessFs::ruleset_handle_access()
420    let ruleset = Ruleset::from(ABI::V1).handle_access(access).unwrap();
421    assert_eq!(ruleset.requested_handled_fs, access);
422    assert_eq!(ruleset.actual_handled_fs, access);
423
424    // Tests composition (binary OR) of handled accesses.
425    let ruleset = Ruleset::from(ABI::V1)
426        .handle_access(AccessFs::Execute)
427        .unwrap()
428        .handle_access(AccessFs::ReadDir)
429        .unwrap()
430        .handle_access(AccessFs::Execute)
431        .unwrap();
432    assert_eq!(ruleset.requested_handled_fs, access);
433    assert_eq!(ruleset.actual_handled_fs, access);
434
435    // Tests that only the required handled accesses are reported as incompatible:
436    // access should not contains AccessFs::Execute.
437    assert!(matches!(Ruleset::from(ABI::Unsupported)
438        .handle_access(AccessFs::Execute)
439        .unwrap()
440        .set_compatibility(CompatLevel::HardRequirement)
441        .handle_access(AccessFs::ReadDir)
442        .unwrap_err(),
443        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
444            CompatError::Access(AccessError::Incompatible { access })
445        ))) if access == AccessFs::ReadDir
446    ));
447}
448
449#[test]
450fn ruleset_created_handle_access_net_tcp() {
451    let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
452
453    // Tests AccessNet::ruleset_handle_access() with ABI that doesn't support TCP rights.
454    let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();
455    assert_eq!(ruleset.requested_handled_net, access);
456    assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);
457
458    // Tests AccessNet::ruleset_handle_access() with ABI that supports TCP rights.
459    let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
460    assert_eq!(ruleset.requested_handled_net, access);
461    assert_eq!(ruleset.actual_handled_net, access);
462
463    // Tests composition (binary OR) of handled accesses.
464    let ruleset = Ruleset::from(ABI::V4)
465        .handle_access(AccessNet::BindTcp)
466        .unwrap()
467        .handle_access(AccessNet::ConnectTcp)
468        .unwrap()
469        .handle_access(AccessNet::BindTcp)
470        .unwrap();
471    assert_eq!(ruleset.requested_handled_net, access);
472    assert_eq!(ruleset.actual_handled_net, access);
473
474    // Tests that only the required handled accesses are reported as incompatible:
475    // access should not contains AccessNet::BindTcp.
476    assert!(matches!(Ruleset::from(ABI::Unsupported)
477        .handle_access(AccessNet::BindTcp)
478        .unwrap()
479        .set_compatibility(CompatLevel::HardRequirement)
480        .handle_access(AccessNet::ConnectTcp)
481        .unwrap_err(),
482        RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
483            CompatError::Access(AccessError::Incompatible { access })
484        ))) if access == AccessNet::ConnectTcp
485    ));
486}
487
488#[test]
489fn ruleset_created_scope() {
490    let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
491
492    // Tests Ruleset::scope() with ABI that doesn't support scopes.
493    let ruleset = Ruleset::from(ABI::V5).scope(scopes).unwrap();
494    assert_eq!(ruleset.requested_scoped, scopes);
495    assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
496
497    // Tests Ruleset::scope() with ABI that supports scopes.
498    let ruleset = Ruleset::from(ABI::V6).scope(scopes).unwrap();
499    assert_eq!(ruleset.requested_scoped, scopes);
500    assert_eq!(ruleset.actual_scoped, scopes);
501
502    // Tests composition (binary OR) of scopes.
503    let ruleset = Ruleset::from(ABI::V6)
504        .scope(Scope::AbstractUnixSocket)
505        .unwrap()
506        .scope(Scope::Signal)
507        .unwrap()
508        .scope(Scope::AbstractUnixSocket)
509        .unwrap();
510    assert_eq!(ruleset.requested_scoped, scopes);
511    assert_eq!(ruleset.actual_scoped, scopes);
512
513    // Tests that only the required scopes are reported as incompatible:
514    // scope should not contain Scope::AbstractUnixSocket.
515    assert!(matches!(Ruleset::from(ABI::Unsupported)
516        .scope(Scope::AbstractUnixSocket)
517        .unwrap()
518        .set_compatibility(CompatLevel::HardRequirement)
519        .scope(Scope::Signal)
520        .unwrap_err(),
521        RulesetError::Scope(ScopeError::Compat(
522            CompatError::Access(AccessError::Incompatible { access })
523        )) if access == Scope::Signal
524    ));
525}
526
527#[test]
528fn ruleset_created_fs_net_scope() {
529    let access_fs = make_bitflags!(AccessFs::{Execute | ReadDir});
530    let access_net = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
531    let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
532
533    // Tests composition (binary OR) of handled accesses.
534    let ruleset = Ruleset::from(ABI::V5)
535        .handle_access(access_fs)
536        .unwrap()
537        .scope(scopes)
538        .unwrap()
539        .handle_access(access_net)
540        .unwrap();
541    assert_eq!(ruleset.requested_handled_fs, access_fs);
542    assert_eq!(ruleset.actual_handled_fs, access_fs);
543    assert_eq!(ruleset.requested_handled_net, access_net);
544    assert_eq!(ruleset.actual_handled_net, access_net);
545    assert_eq!(ruleset.requested_scoped, scopes);
546    assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
547
548    // Tests composition (binary OR) of handled accesses and scopes.
549    let ruleset = Ruleset::from(ABI::V6)
550        .handle_access(access_fs)
551        .unwrap()
552        .scope(scopes)
553        .unwrap()
554        .handle_access(access_net)
555        .unwrap();
556    assert_eq!(ruleset.requested_handled_fs, access_fs);
557    assert_eq!(ruleset.actual_handled_fs, access_fs);
558    assert_eq!(ruleset.requested_handled_net, access_net);
559    assert_eq!(ruleset.actual_handled_net, access_net);
560    assert_eq!(ruleset.requested_scoped, scopes);
561    assert_eq!(ruleset.actual_scoped, scopes);
562}
563
564impl OptionCompatLevelMut for RulesetCreated {
565    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
566        &mut self.compat.level
567    }
568}
569
570impl OptionCompatLevelMut for &mut RulesetCreated {
571    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
572        &mut self.compat.level
573    }
574}
575
576impl Compatible for RulesetCreated {}
577
578impl Compatible for &mut RulesetCreated {}
579
580pub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {
581    /// Attempts to add a new rule to the ruleset.
582    ///
583    /// On error, returns a wrapped [`AddRulesError`].
584    fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
585    where
586        T: Rule<U>,
587        U: HandledAccess + PrivateHandledAccess,
588    {
589        let body = || -> Result<Self, AddRulesError> {
590            let self_ref = self.as_mut();
591            rule.check_consistency(self_ref)?;
592            let mut compat_rule = match rule
593                .try_compat(
594                    self_ref.compat.abi(),
595                    self_ref.compat.level,
596                    &mut self_ref.compat.state,
597                )
598                .map_err(AddRuleError::Compat)?
599            {
600                Some(r) => r,
601                None => return Ok(self),
602            };
603            match self_ref.compat.state {
604                CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
605                CompatState::Full | CompatState::Partial => {
606                    #[cfg(test)]
607                    assert!(self_ref.fd.is_some());
608                    let fd = self_ref.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
609                    match unsafe {
610                        uapi::landlock_add_rule(fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
611                    } {
612                        0 => Ok(self),
613                        _ => Err(AddRuleError::<U>::AddRuleCall {
614                            source: Error::last_os_error(),
615                        }
616                        .into()),
617                    }
618                }
619            }
620        };
621        Ok(body()?)
622    }
623
624    /// Attempts to add a set of new rules to the ruleset.
625    ///
626    /// On error, returns a (double) wrapped [`AddRulesError`].
627    ///
628    /// # Example
629    ///
630    /// Create a custom iterator to read paths from environment variable.
631    ///
632    /// ```
633    /// use landlock::{
634    ///     Access, AccessFs, BitFlags, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
635    ///     RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
636    /// };
637    /// use std::env;
638    /// use std::ffi::OsStr;
639    /// use std::os::unix::ffi::{OsStrExt, OsStringExt};
640    /// use thiserror::Error;
641    ///
642    /// #[derive(Debug, Error)]
643    /// enum PathEnvError<'a> {
644    ///     #[error(transparent)]
645    ///     Ruleset(#[from] RulesetError),
646    ///     #[error(transparent)]
647    ///     AddRuleIter(#[from] PathFdError),
648    ///     #[error("missing environment variable {0}")]
649    ///     MissingVar(&'a str),
650    /// }
651    ///
652    /// struct PathEnv {
653    ///     paths: Vec<u8>,
654    ///     access: BitFlags<AccessFs>,
655    /// }
656    ///
657    /// impl PathEnv {
658    ///     // env_var is the name of an environment variable
659    ///     // containing paths requested to be allowed.
660    ///     // Paths are separated with ":", e.g. "/bin:/lib:/usr:/proc".
661    ///     // In case an empty string is provided,
662    ///     // no restrictions are applied.
663    ///     // `access` is the set of access rights allowed for each of the parsed paths.
664    ///     fn new<'a>(
665    ///         env_var: &'a str, access: BitFlags<AccessFs>
666    ///     ) -> Result<Self, PathEnvError<'a>> {
667    ///         Ok(Self {
668    ///             paths: env::var_os(env_var)
669    ///                 .ok_or(PathEnvError::MissingVar(env_var))?
670    ///                 .into_vec(),
671    ///             access,
672    ///         })
673    ///     }
674    ///
675    ///     fn iter(
676    ///         &self,
677    ///     ) -> impl Iterator<Item = Result<PathBeneath<PathFd>, PathEnvError<'static>>> + '_ {
678    ///         let is_empty = self.paths.is_empty();
679    ///         self.paths
680    ///             .split(|b| *b == b':')
681    ///             // Skips the first empty element from of an empty string.
682    ///             .skip_while(move |_| is_empty)
683    ///             .map(OsStr::from_bytes)
684    ///             .map(move |path|
685    ///                 Ok(PathBeneath::new(PathFd::new(path)?, self.access)))
686    ///     }
687    /// }
688    ///
689    /// fn restrict_env() -> Result<RestrictionStatus, PathEnvError<'static>> {
690    ///     Ok(Ruleset::default()
691    ///         .handle_access(AccessFs::from_all(ABI::V1))?
692    ///         .create()?
693    ///         // In the shell: export EXECUTABLE_PATH="/usr:/bin:/sbin"
694    ///         .add_rules(PathEnv::new("EXECUTABLE_PATH", AccessFs::Execute.into())?.iter())?
695    ///         .restrict_self()?)
696    /// }
697    /// ```
698    fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
699    where
700        I: IntoIterator<Item = Result<T, E>>,
701        T: Rule<U>,
702        U: HandledAccess + PrivateHandledAccess,
703        E: From<RulesetError>,
704    {
705        for rule in rules {
706            self = self.add_rule(rule?)?;
707        }
708        Ok(self)
709    }
710
711    /// Configures the ruleset to call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS` command
712    /// in [`restrict_self()`](RulesetCreated::restrict_self).
713    ///
714    /// This `prctl(2)` call is never ignored, even if an error was encountered on a [`Ruleset`] or
715    /// [`RulesetCreated`] method call while [`CompatLevel::SoftRequirement`] was set.
716    fn set_no_new_privs(mut self, no_new_privs: bool) -> Self {
717        <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = no_new_privs;
718        self
719    }
720}
721
722/// Ruleset created with [`Ruleset::create()`].
723#[cfg_attr(test, derive(Debug))]
724pub struct RulesetCreated {
725    fd: Option<OwnedFd>,
726    no_new_privs: bool,
727    pub(crate) requested_handled_fs: BitFlags<AccessFs>,
728    pub(crate) requested_handled_net: BitFlags<AccessNet>,
729    compat: Compatibility,
730}
731
732impl RulesetCreated {
733    pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
734        // The compatibility state is initialized by Ruleset::create().
735        #[cfg(test)]
736        assert!(!matches!(ruleset.compat.state, CompatState::Init));
737
738        RulesetCreated {
739            fd,
740            no_new_privs: true,
741            requested_handled_fs: ruleset.requested_handled_fs,
742            requested_handled_net: ruleset.requested_handled_net,
743            compat: ruleset.compat,
744        }
745    }
746
747    /// Attempts to restrict the calling thread with the ruleset
748    /// according to the best-effort configuration
749    /// (see [`RulesetCreated::set_compatibility()`] and [`CompatLevel::BestEffort`]).
750    /// Call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS`
751    /// according to the ruleset configuration.
752    ///
753    /// On error, returns a wrapped [`RestrictSelfError`].
754    pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
755        let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
756            // Enforce no_new_privs even if something failed with SoftRequirement. The rationale is
757            // that no_new_privs should not be an issue on its own if it is not explicitly
758            // deactivated.
759            let enforced_nnp = if self.no_new_privs {
760                if let Err(e) = prctl_set_no_new_privs() {
761                    match self.compat.level.into() {
762                        CompatLevel::BestEffort => {}
763                        CompatLevel::SoftRequirement => {
764                            self.compat.update(CompatState::Dummy);
765                        }
766                        CompatLevel::HardRequirement => {
767                            return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
768                        }
769                    }
770                    // To get a consistent behavior, calls this prctl whether or not
771                    // Landlock is supported by the running kernel.
772                    let support_nnp = support_no_new_privs();
773                    match self.compat.state {
774                        // It should not be an error for kernel (older than 3.5) not supporting
775                        // no_new_privs.
776                        CompatState::Init | CompatState::No | CompatState::Dummy => {
777                            if support_nnp {
778                                // The kernel seems to be between 3.5 (included) and 5.13 (excluded),
779                                // or Landlock is not enabled; no_new_privs should be supported anyway.
780                                return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
781                            }
782                        }
783                        // A kernel supporting Landlock should also support no_new_privs (unless
784                        // filtered by seccomp).
785                        CompatState::Full | CompatState::Partial => {
786                            return Err(RestrictSelfError::SetNoNewPrivsCall { source: e })
787                        }
788                    }
789                    false
790                } else {
791                    true
792                }
793            } else {
794                false
795            };
796
797            match self.compat.state {
798                CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
799                    ruleset: self.compat.state.into(),
800                    no_new_privs: enforced_nnp,
801                }),
802                CompatState::Full | CompatState::Partial => {
803                    #[cfg(test)]
804                    assert!(self.fd.is_some());
805                    // Does not consume ruleset FD, which will be automatically closed after this block.
806                    let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
807                    match unsafe { uapi::landlock_restrict_self(fd, 0) } {
808                        0 => {
809                            self.compat.update(CompatState::Full);
810                            Ok(RestrictionStatus {
811                                ruleset: self.compat.state.into(),
812                                no_new_privs: enforced_nnp,
813                            })
814                        }
815                        // TODO: match specific Landlock restrict self errors
816                        _ => Err(RestrictSelfError::RestrictSelfCall {
817                            source: Error::last_os_error(),
818                        }),
819                    }
820                }
821            }
822        };
823        Ok(body()?)
824    }
825
826    /// Creates a new `RulesetCreated` instance by duplicating the underlying file descriptor.
827    /// Rule modification will affect both `RulesetCreated` instances simultaneously.
828    ///
829    /// On error, returns [`std::io::Error`].
830    pub fn try_clone(&self) -> std::io::Result<Self> {
831        Ok(RulesetCreated {
832            fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
833            no_new_privs: self.no_new_privs,
834            requested_handled_fs: self.requested_handled_fs,
835            requested_handled_net: self.requested_handled_net,
836            compat: self.compat,
837        })
838    }
839}
840
841impl From<RulesetCreated> for Option<OwnedFd> {
842    fn from(ruleset: RulesetCreated) -> Self {
843        ruleset.fd
844    }
845}
846
847#[test]
848fn ruleset_created_ownedfd_none() {
849    let ruleset = Ruleset::from(ABI::Unsupported)
850        .handle_access(AccessFs::Execute)
851        .unwrap()
852        .create()
853        .unwrap();
854    let fd: Option<OwnedFd> = ruleset.into();
855    assert!(fd.is_none());
856}
857
858impl AsMut<RulesetCreated> for RulesetCreated {
859    fn as_mut(&mut self) -> &mut RulesetCreated {
860        self
861    }
862}
863
864impl RulesetCreatedAttr for RulesetCreated {}
865
866impl RulesetCreatedAttr for &mut RulesetCreated {}
867
868#[test]
869fn ruleset_created_attr() {
870    let mut ruleset_created = Ruleset::from(ABI::Unsupported)
871        .handle_access(AccessFs::Execute)
872        .unwrap()
873        .create()
874        .unwrap();
875    let ruleset_created_ref = &mut ruleset_created;
876
877    // Can pass this reference to populate the ruleset...
878    ruleset_created_ref
879        .set_compatibility(CompatLevel::BestEffort)
880        .add_rule(PathBeneath::new(
881            PathFd::new("/usr").unwrap(),
882            AccessFs::Execute,
883        ))
884        .unwrap()
885        .add_rule(PathBeneath::new(
886            PathFd::new("/etc").unwrap(),
887            AccessFs::Execute,
888        ))
889        .unwrap();
890
891    // ...and finally restrict with the last rules (thanks to non-lexical lifetimes).
892    assert_eq!(
893        ruleset_created
894            .set_compatibility(CompatLevel::BestEffort)
895            .add_rule(PathBeneath::new(
896                PathFd::new("/tmp").unwrap(),
897                AccessFs::Execute,
898            ))
899            .unwrap()
900            .add_rule(PathBeneath::new(
901                PathFd::new("/var").unwrap(),
902                AccessFs::Execute,
903            ))
904            .unwrap()
905            .restrict_self()
906            .unwrap(),
907        RestrictionStatus {
908            ruleset: RulesetStatus::NotEnforced,
909            no_new_privs: true,
910        }
911    );
912}
913
914#[test]
915fn ruleset_compat_dummy() {
916    for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
917        println!("level: {:?}", level);
918
919        // ABI:Unsupported does not support AccessFs::Execute.
920        let ruleset = Ruleset::from(ABI::Unsupported);
921        assert_eq!(ruleset.compat.state, CompatState::Init);
922
923        let ruleset = ruleset.set_compatibility(level);
924        assert_eq!(ruleset.compat.state, CompatState::Init);
925
926        let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
927        assert_eq!(
928            ruleset.compat.state,
929            match level {
930                CompatLevel::BestEffort => CompatState::No,
931                CompatLevel::SoftRequirement => CompatState::Dummy,
932                _ => unreachable!(),
933            }
934        );
935
936        let ruleset_created = ruleset.create().unwrap();
937        // Because the compatibility state was either No or Dummy, calling create() updates it to
938        // Dummy.
939        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
940
941        let ruleset_created = ruleset_created
942            .add_rule(PathBeneath::new(
943                PathFd::new("/usr").unwrap(),
944                AccessFs::Execute,
945            ))
946            .unwrap();
947        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
948    }
949}
950
951#[test]
952fn ruleset_compat_partial() {
953    // CompatLevel::BestEffort
954    let ruleset = Ruleset::from(ABI::V1);
955    assert_eq!(ruleset.compat.state, CompatState::Init);
956
957    // ABI::V1 does not support AccessFs::Refer.
958    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
959    assert_eq!(ruleset.compat.state, CompatState::No);
960
961    let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
962    assert_eq!(ruleset.compat.state, CompatState::Partial);
963
964    // Requesting to handle another unsupported handled access does not change anything.
965    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
966    assert_eq!(ruleset.compat.state, CompatState::Partial);
967}
968
969#[test]
970fn ruleset_unsupported() {
971    assert_eq!(
972        Ruleset::from(ABI::Unsupported)
973            // BestEffort for Ruleset.
974            .handle_access(AccessFs::Execute)
975            .unwrap()
976            .create()
977            .unwrap()
978            .restrict_self()
979            .unwrap(),
980        RestrictionStatus {
981            ruleset: RulesetStatus::NotEnforced,
982            // With BestEffort, no_new_privs is still enabled.
983            no_new_privs: true,
984        }
985    );
986
987    assert_eq!(
988        Ruleset::from(ABI::Unsupported)
989            // SoftRequirement for Ruleset.
990            .set_compatibility(CompatLevel::SoftRequirement)
991            .handle_access(AccessFs::Execute)
992            .unwrap()
993            .create()
994            .unwrap()
995            .restrict_self()
996            .unwrap(),
997        RestrictionStatus {
998            ruleset: RulesetStatus::NotEnforced,
999            // With SoftRequirement, no_new_privs is still enabled.
1000            no_new_privs: true,
1001        }
1002    );
1003
1004    // Missing handled access because of the compatibility level.
1005    matches!(
1006        Ruleset::from(ABI::Unsupported)
1007            // HardRequirement for Ruleset.
1008            .set_compatibility(CompatLevel::HardRequirement)
1009            .handle_access(AccessFs::Execute)
1010            .unwrap_err(),
1011        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1012    );
1013
1014    // Missing scope access because of the compatibility level.
1015    matches!(
1016        Ruleset::from(ABI::Unsupported)
1017            // HardRequirement for Ruleset.
1018            .set_compatibility(CompatLevel::HardRequirement)
1019            .scope(Scope::Signal)
1020            .unwrap_err(),
1021        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1022    );
1023
1024    assert_eq!(
1025        Ruleset::from(ABI::Unsupported)
1026            .handle_access(AccessFs::Execute)
1027            .unwrap()
1028            .create()
1029            .unwrap()
1030            // SoftRequirement for RulesetCreated without any rule.
1031            .set_compatibility(CompatLevel::SoftRequirement)
1032            .restrict_self()
1033            .unwrap(),
1034        RestrictionStatus {
1035            ruleset: RulesetStatus::NotEnforced,
1036            // With SoftRequirement, no_new_privs is untouched if there is no error (e.g. no rule).
1037            no_new_privs: true,
1038        }
1039    );
1040
1041    // Don't explicitly call create() on a CI that doesn't support Landlock.
1042    if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
1043        assert_eq!(
1044            Ruleset::from(ABI::V1)
1045                .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
1046                .unwrap()
1047                .create()
1048                .unwrap()
1049                // SoftRequirement for RulesetCreated with a rule.
1050                .set_compatibility(CompatLevel::SoftRequirement)
1051                .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
1052                .unwrap()
1053                .restrict_self()
1054                .unwrap(),
1055            RestrictionStatus {
1056                ruleset: RulesetStatus::NotEnforced,
1057                // With SoftRequirement, no_new_privs is still enabled, even if there is an error
1058                // (e.g. unsupported access right).
1059                no_new_privs: true,
1060            }
1061        );
1062    }
1063
1064    assert_eq!(
1065        Ruleset::from(ABI::Unsupported)
1066            .handle_access(AccessFs::Execute)
1067            .unwrap()
1068            .create()
1069            .unwrap()
1070            .set_no_new_privs(false)
1071            .restrict_self()
1072            .unwrap(),
1073        RestrictionStatus {
1074            ruleset: RulesetStatus::NotEnforced,
1075            no_new_privs: false,
1076        }
1077    );
1078
1079    // Checks empty handled access with moot ruleset.
1080    assert!(matches!(
1081        Ruleset::from(ABI::Unsupported)
1082            // Empty access-rights
1083            .handle_access(AccessFs::from_all(ABI::Unsupported))
1084            .unwrap_err(),
1085        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1086            CompatError::Access(AccessError::Empty)
1087        )))
1088    ));
1089
1090    assert!(matches!(
1091        Ruleset::from(ABI::Unsupported)
1092            // No handle_access() nor scope() call.
1093            .create()
1094            .unwrap_err(),
1095        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1096    ));
1097
1098    // Checks empty handled access with minimal ruleset.
1099    assert!(matches!(
1100        Ruleset::from(ABI::V1)
1101            // Empty access-rights
1102            .handle_access(AccessFs::from_all(ABI::Unsupported))
1103            .unwrap_err(),
1104        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1105            CompatError::Access(AccessError::Empty)
1106        )))
1107    ));
1108
1109    // Checks empty scope with moot ruleset.
1110    assert!(matches!(
1111        Ruleset::from(ABI::Unsupported)
1112            .scope(Scope::from_all(ABI::Unsupported))
1113            .unwrap_err(),
1114        RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1115    ));
1116
1117    // Checks empty scope with minimal ruleset.
1118    assert!(matches!(
1119        Ruleset::from(ABI::V1)
1120            .scope(Scope::from_all(ABI::Unsupported))
1121            .unwrap_err(),
1122        RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1123    ));
1124
1125    // Tests inconsistency between the ruleset handled access-rights and the rule access-rights.
1126    for handled_access in &[
1127        make_bitflags!(AccessFs::{Execute | WriteFile}),
1128        AccessFs::Execute.into(),
1129    ] {
1130        let ruleset = Ruleset::from(ABI::V1)
1131            .handle_access(*handled_access)
1132            .unwrap();
1133        // Fakes a call to create() to test without involving the kernel (i.e. no
1134        // landlock_ruleset_create() call).
1135        let ruleset_created = RulesetCreated::new(ruleset, None);
1136        assert!(matches!(
1137            ruleset_created
1138                .add_rule(PathBeneath::new(
1139                    PathFd::new("/").unwrap(),
1140                    AccessFs::ReadFile
1141                ))
1142                .unwrap_err(),
1143            RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
1144        ));
1145    }
1146}
1147
1148#[test]
1149fn ignore_abi_v2_with_abi_v1() {
1150    // We don't need kernel/CI support for Landlock because no related syscalls should actually be
1151    // performed.
1152    assert_eq!(
1153        Ruleset::from(ABI::V1)
1154            .set_compatibility(CompatLevel::HardRequirement)
1155            .handle_access(AccessFs::from_all(ABI::V1))
1156            .unwrap()
1157            .set_compatibility(CompatLevel::SoftRequirement)
1158            // Because Ruleset only supports V1, Refer will be ignored.
1159            .handle_access(AccessFs::Refer)
1160            .unwrap()
1161            .create()
1162            .unwrap()
1163            .add_rule(PathBeneath::new(
1164                PathFd::new("/tmp").unwrap(),
1165                AccessFs::from_all(ABI::V2)
1166            ))
1167            .unwrap()
1168            .add_rule(PathBeneath::new(
1169                PathFd::new("/usr").unwrap(),
1170                make_bitflags!(AccessFs::{ReadFile | ReadDir})
1171            ))
1172            .unwrap()
1173            .restrict_self()
1174            .unwrap(),
1175        RestrictionStatus {
1176            ruleset: RulesetStatus::NotEnforced,
1177            no_new_privs: true,
1178        }
1179    );
1180}
1181
1182#[test]
1183fn unsupported_handled_access() {
1184    matches!(
1185        Ruleset::from(ABI::V3)
1186            .handle_access(AccessNet::from_all(ABI::V3))
1187            .unwrap_err(),
1188        RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
1189            CompatError::Access(AccessError::Empty)
1190        )))
1191    );
1192}
1193
1194#[test]
1195fn unsupported_handled_access_errno() {
1196    assert_eq!(
1197        Errno::from(
1198            Ruleset::from(ABI::V3)
1199                .handle_access(AccessNet::from_all(ABI::V3))
1200                .unwrap_err()
1201        ),
1202        Errno::new(libc::EINVAL)
1203    );
1204}