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