Skip to main content

landlock/
ruleset.rs

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