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.
846    ///
847    /// Calling with `false` sets the `LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF` flag.
848    /// Calling with `true` is a no-op (the default behavior).
849    ///
850    /// This setter only applies when restricting with a domain.
851    ///
852    /// On error, returns a wrapped
853    /// [`SyscallFlagError<RestrictSelfFlag>`](crate::SyscallFlagError).
854    ///
855    /// See [`RestrictSelfAttr::log_subdomains()`](crate::RestrictSelfAttr::log_subdomains)
856    /// for compat-state behavior when toggling this setter on unsupported kernels.
857    fn log_same_exec(mut self, set: bool) -> Result<Self, RulesetError> {
858        self.try_set_flag(RestrictSelfFlag::LogSameExec, set)?;
859        Ok(self)
860    }
861
862    /// Controls logging of denied accesses after an `execve(2)` call.
863    /// Logging is **disabled** by default.
864    ///
865    /// Calling with `true` sets the `LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON` flag.
866    /// Calling with `false` is a no-op (the default behavior).
867    ///
868    /// This setter only applies when restricting with a domain.
869    ///
870    /// On error, returns a wrapped
871    /// [`SyscallFlagError<RestrictSelfFlag>`](crate::SyscallFlagError).
872    ///
873    /// See [`RestrictSelfAttr::log_subdomains()`](crate::RestrictSelfAttr::log_subdomains)
874    /// for compat-state behavior when toggling this setter on unsupported kernels.
875    fn log_new_exec(mut self, set: bool) -> Result<Self, RulesetError> {
876        self.try_set_flag(RestrictSelfFlag::LogNewExec, set)?;
877        Ok(self)
878    }
879}
880
881/// Ruleset created with [`Ruleset::create()`].
882#[derive(Debug)]
883pub struct RulesetCreated {
884    fd: Option<OwnedFd>,
885    no_new_privs: bool,
886    pub(crate) requested_handled_fs: BitFlags<AccessFs>,
887    pub(crate) requested_handled_net: BitFlags<AccessNet>,
888    requested_restrict_self_flags: u32,
889    actual_restrict_self_flags: u32,
890    compat: Compatibility,
891}
892
893impl RulesetCreated {
894    pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
895        // The compatibility state is initialized by Ruleset::create().
896        #[cfg(test)]
897        assert!(!matches!(ruleset.compat.state, CompatState::Init));
898
899        RulesetCreated {
900            fd,
901            no_new_privs: true,
902            requested_handled_fs: ruleset.requested_handled_fs,
903            requested_handled_net: ruleset.requested_handled_net,
904            requested_restrict_self_flags: 0,
905            actual_restrict_self_flags: 0,
906            compat: ruleset.compat,
907        }
908    }
909
910    /// Attempts to restrict the calling thread with the ruleset
911    /// according to the best-effort configuration
912    /// (see [`RulesetCreated::set_compatibility()`] and [`CompatLevel::BestEffort`]).
913    /// Call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS`
914    /// according to the ruleset configuration.
915    ///
916    /// On error, returns a wrapped [`RestrictSelfError`].
917    pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
918        let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
919            // Enforce no_new_privs even if something failed with SoftRequirement. The rationale is
920            // that no_new_privs should not be an issue on its own if it is not explicitly
921            // deactivated.
922            let enforced_nnp = if self.no_new_privs {
923                try_set_no_new_privs(&mut self.compat)?
924            } else {
925                false
926            };
927
928            let raw = self.actual_restrict_self_flags;
929            let log_same_exec = RestrictSelfFlag::LogSameExec.is_set(raw);
930            let log_new_exec = RestrictSelfFlag::LogNewExec.is_set(raw);
931            let log_subdomains = RestrictSelfFlag::LogSubdomains.is_set(raw);
932
933            match self.compat.state {
934                CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
935                    ruleset: self.compat.state.into(),
936                    landlock: self.compat.status(),
937                    no_new_privs: enforced_nnp,
938                    log_same_exec,
939                    log_new_exec,
940                    log_subdomains,
941                }),
942                CompatState::Full | CompatState::Partial => {
943                    #[cfg(test)]
944                    assert!(self.fd.is_some());
945                    // Does not consume ruleset FD, which will be automatically closed after this block.
946                    let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
947                    match unsafe {
948                        uapi::landlock_restrict_self(fd, self.actual_restrict_self_flags)
949                    } {
950                        0 => {
951                            self.compat.update(CompatState::Full);
952                            Ok(RestrictionStatus {
953                                ruleset: self.compat.state.into(),
954                                landlock: self.compat.status(),
955                                no_new_privs: enforced_nnp,
956                                log_same_exec,
957                                log_new_exec,
958                                log_subdomains,
959                            })
960                        }
961                        // TODO: match specific Landlock restrict self errors
962                        _ => Err(RestrictSelfError::RestrictSelfCall {
963                            source: Error::last_os_error(),
964                        }),
965                    }
966                }
967            }
968        };
969        Ok(body()?)
970    }
971
972    /// Creates a new `RulesetCreated` instance by duplicating the underlying file descriptor.
973    /// Rule modification will affect both `RulesetCreated` instances simultaneously.
974    ///
975    /// On error, returns [`std::io::Error`].
976    pub fn try_clone(&self) -> std::io::Result<Self> {
977        Ok(RulesetCreated {
978            fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
979            no_new_privs: self.no_new_privs,
980            requested_handled_fs: self.requested_handled_fs,
981            requested_handled_net: self.requested_handled_net,
982            requested_restrict_self_flags: self.requested_restrict_self_flags,
983            actual_restrict_self_flags: self.actual_restrict_self_flags,
984            compat: self.compat,
985        })
986    }
987}
988
989impl From<RulesetCreated> for Option<OwnedFd> {
990    fn from(ruleset: RulesetCreated) -> Self {
991        ruleset.fd
992    }
993}
994
995#[test]
996fn ruleset_created_ownedfd_none() {
997    let ruleset = Ruleset::from(ABI::Unsupported)
998        .handle_access(AccessFs::Execute)
999        .unwrap()
1000        .create()
1001        .unwrap();
1002    let fd: Option<OwnedFd> = ruleset.into();
1003    assert!(fd.is_none());
1004}
1005
1006impl AsMut<RulesetCreated> for RulesetCreated {
1007    fn as_mut(&mut self) -> &mut RulesetCreated {
1008        self
1009    }
1010}
1011
1012impl RulesetCreatedAttr for RulesetCreated {}
1013
1014impl RulesetCreatedAttr for &mut RulesetCreated {}
1015
1016#[test]
1017fn ruleset_created_attr() {
1018    let mut ruleset_created = Ruleset::from(ABI::Unsupported)
1019        .handle_access(AccessFs::Execute)
1020        .unwrap()
1021        .create()
1022        .unwrap();
1023    let ruleset_created_ref = &mut ruleset_created;
1024
1025    // Can pass this reference to populate the ruleset...
1026    ruleset_created_ref
1027        .set_compatibility(CompatLevel::BestEffort)
1028        .add_rule(PathBeneath::new(
1029            PathFd::new("/usr").unwrap(),
1030            AccessFs::Execute,
1031        ))
1032        .unwrap()
1033        .add_rule(PathBeneath::new(
1034            PathFd::new("/etc").unwrap(),
1035            AccessFs::Execute,
1036        ))
1037        .unwrap();
1038
1039    // ...and finally restrict with the last rules (thanks to non-lexical lifetimes).
1040    assert_eq!(
1041        ruleset_created
1042            .set_compatibility(CompatLevel::BestEffort)
1043            .add_rule(PathBeneath::new(
1044                PathFd::new("/tmp").unwrap(),
1045                AccessFs::Execute,
1046            ))
1047            .unwrap()
1048            .add_rule(PathBeneath::new(
1049                PathFd::new("/var").unwrap(),
1050                AccessFs::Execute,
1051            ))
1052            .unwrap()
1053            .restrict_self()
1054            .unwrap(),
1055        RestrictionStatus {
1056            ruleset: RulesetStatus::NotEnforced,
1057            landlock: LandlockStatus::NotImplemented,
1058            no_new_privs: true,
1059            log_same_exec: true,
1060            log_new_exec: false,
1061            log_subdomains: true,
1062        }
1063    );
1064}
1065
1066#[test]
1067fn ruleset_compat_dummy() {
1068    for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
1069        println!("level: {:?}", level);
1070
1071        // ABI:Unsupported does not support AccessFs::Execute.
1072        let ruleset = Ruleset::from(ABI::Unsupported);
1073        assert_eq!(ruleset.compat.state, CompatState::Init);
1074
1075        let ruleset = ruleset.set_compatibility(level);
1076        assert_eq!(ruleset.compat.state, CompatState::Init);
1077
1078        let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1079        assert_eq!(
1080            ruleset.compat.state,
1081            match level {
1082                CompatLevel::BestEffort => CompatState::No,
1083                CompatLevel::SoftRequirement => CompatState::Dummy,
1084                _ => unreachable!(),
1085            }
1086        );
1087
1088        let ruleset_created = ruleset.create().unwrap();
1089        // Because the compatibility state was either No or Dummy, calling create() updates it to
1090        // Dummy.
1091        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1092
1093        let ruleset_created = ruleset_created
1094            .add_rule(PathBeneath::new(
1095                PathFd::new("/usr").unwrap(),
1096                AccessFs::Execute,
1097            ))
1098            .unwrap();
1099        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1100    }
1101}
1102
1103#[test]
1104fn ruleset_compat_partial() {
1105    // CompatLevel::BestEffort
1106    let ruleset = Ruleset::from(ABI::V1);
1107    assert_eq!(ruleset.compat.state, CompatState::Init);
1108
1109    // ABI::V1 does not support AccessFs::Refer.
1110    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1111    assert_eq!(ruleset.compat.state, CompatState::No);
1112
1113    let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1114    assert_eq!(ruleset.compat.state, CompatState::Partial);
1115
1116    // Requesting to handle another unsupported handled access does not change anything.
1117    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1118    assert_eq!(ruleset.compat.state, CompatState::Partial);
1119}
1120
1121#[test]
1122fn ruleset_unsupported() {
1123    assert_eq!(
1124        Ruleset::from(ABI::Unsupported)
1125            // BestEffort for Ruleset.
1126            .handle_access(AccessFs::Execute)
1127            .unwrap()
1128            .create()
1129            .unwrap()
1130            .restrict_self()
1131            .unwrap(),
1132        RestrictionStatus {
1133            ruleset: RulesetStatus::NotEnforced,
1134            landlock: LandlockStatus::NotImplemented,
1135            // With BestEffort, no_new_privs is still enabled.
1136            no_new_privs: true,
1137            log_same_exec: true,
1138            log_new_exec: false,
1139            log_subdomains: true,
1140        }
1141    );
1142
1143    assert_eq!(
1144        Ruleset::from(ABI::Unsupported)
1145            // SoftRequirement for Ruleset.
1146            .set_compatibility(CompatLevel::SoftRequirement)
1147            .handle_access(AccessFs::Execute)
1148            .unwrap()
1149            .create()
1150            .unwrap()
1151            .restrict_self()
1152            .unwrap(),
1153        RestrictionStatus {
1154            ruleset: RulesetStatus::NotEnforced,
1155            landlock: LandlockStatus::NotImplemented,
1156            // With SoftRequirement, no_new_privs is still enabled.
1157            no_new_privs: true,
1158            log_same_exec: true,
1159            log_new_exec: false,
1160            log_subdomains: true,
1161        }
1162    );
1163
1164    // Incompatible handled access because of the compatibility level.
1165    assert!(matches!(
1166        Ruleset::from(ABI::Unsupported)
1167            // HardRequirement for Ruleset.
1168            .set_compatibility(CompatLevel::HardRequirement)
1169            .handle_access(AccessFs::Execute)
1170            .unwrap_err(),
1171        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1172            CompatError::Access(AccessError::Incompatible { .. })
1173        )))
1174    ));
1175
1176    // Incompatible scope because of the compatibility level.
1177    assert!(matches!(
1178        Ruleset::from(ABI::Unsupported)
1179            // HardRequirement for Ruleset.
1180            .set_compatibility(CompatLevel::HardRequirement)
1181            .scope(Scope::Signal)
1182            .unwrap_err(),
1183        RulesetError::Scope(ScopeError::Compat(CompatError::Access(
1184            AccessError::Incompatible { .. }
1185        )))
1186    ));
1187
1188    assert_eq!(
1189        Ruleset::from(ABI::Unsupported)
1190            .handle_access(AccessFs::Execute)
1191            .unwrap()
1192            .create()
1193            .unwrap()
1194            // SoftRequirement for RulesetCreated without any rule.
1195            .set_compatibility(CompatLevel::SoftRequirement)
1196            .restrict_self()
1197            .unwrap(),
1198        RestrictionStatus {
1199            ruleset: RulesetStatus::NotEnforced,
1200            landlock: LandlockStatus::NotImplemented,
1201            // With SoftRequirement, no_new_privs is untouched if there is no error (e.g. no rule).
1202            no_new_privs: true,
1203            log_same_exec: true,
1204            log_new_exec: false,
1205            log_subdomains: true,
1206        }
1207    );
1208
1209    // Don't explicitly call create() on a CI that doesn't support Landlock.
1210    if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
1211        assert_eq!(
1212            Ruleset::from(ABI::V1)
1213                .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
1214                .unwrap()
1215                .create()
1216                .unwrap()
1217                // SoftRequirement for RulesetCreated with a rule.
1218                .set_compatibility(CompatLevel::SoftRequirement)
1219                .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
1220                .unwrap()
1221                .restrict_self()
1222                .unwrap(),
1223            RestrictionStatus {
1224                ruleset: RulesetStatus::NotEnforced,
1225                landlock: LandlockStatus::Available {
1226                    effective_abi: ABI::V1,
1227                    kernel_abi: None,
1228                },
1229                // With SoftRequirement, no_new_privs is still enabled, even if there is an error
1230                // (e.g. unsupported access right).
1231                no_new_privs: true,
1232                log_same_exec: true,
1233                log_new_exec: false,
1234                log_subdomains: true,
1235            }
1236        );
1237    }
1238
1239    assert_eq!(
1240        Ruleset::from(ABI::Unsupported)
1241            .handle_access(AccessFs::Execute)
1242            .unwrap()
1243            .create()
1244            .unwrap()
1245            .no_new_privs(false)
1246            .restrict_self()
1247            .unwrap(),
1248        RestrictionStatus {
1249            ruleset: RulesetStatus::NotEnforced,
1250            landlock: LandlockStatus::NotImplemented,
1251            no_new_privs: false,
1252            log_same_exec: true,
1253            log_new_exec: false,
1254            log_subdomains: true,
1255        }
1256    );
1257
1258    // Checks empty handled access with moot ruleset.
1259    assert!(matches!(
1260        Ruleset::from(ABI::Unsupported)
1261            // Empty access-rights
1262            .handle_access(AccessFs::from_all(ABI::Unsupported))
1263            .unwrap_err(),
1264        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1265            CompatError::Access(AccessError::Empty)
1266        )))
1267    ));
1268
1269    assert!(matches!(
1270        Ruleset::from(ABI::Unsupported)
1271            // No handle_access() nor scope() call.
1272            .create()
1273            .unwrap_err(),
1274        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1275    ));
1276
1277    // Checks empty handled access with minimal ruleset.
1278    assert!(matches!(
1279        Ruleset::from(ABI::V1)
1280            // Empty access-rights
1281            .handle_access(AccessFs::from_all(ABI::Unsupported))
1282            .unwrap_err(),
1283        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1284            CompatError::Access(AccessError::Empty)
1285        )))
1286    ));
1287
1288    // Checks empty scope with moot ruleset.
1289    assert!(matches!(
1290        Ruleset::from(ABI::Unsupported)
1291            .scope(Scope::from_all(ABI::Unsupported))
1292            .unwrap_err(),
1293        RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1294    ));
1295
1296    // Checks empty scope with minimal ruleset.
1297    assert!(matches!(
1298        Ruleset::from(ABI::V1)
1299            .scope(Scope::from_all(ABI::Unsupported))
1300            .unwrap_err(),
1301        RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1302    ));
1303
1304    // Scope with SoftRequirement on unsupported ABI: silently dropped, state becomes Dummy.
1305    let ruleset = Ruleset::from(ABI::V1)
1306        .handle_access(AccessFs::Execute)
1307        .unwrap()
1308        .set_compatibility(CompatLevel::SoftRequirement)
1309        .scope(Scope::Signal)
1310        .unwrap();
1311    assert_eq!(ruleset.requested_scoped, BitFlags::from(Scope::Signal));
1312    assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
1313
1314    // Log flags with BestEffort on unsupported ABI are silently ignored.
1315    assert_eq!(
1316        Ruleset::from(ABI::Unsupported)
1317            .handle_access(AccessFs::Execute)
1318            .unwrap()
1319            .create()
1320            .unwrap()
1321            .log_same_exec(false)
1322            .unwrap()
1323            .restrict_self()
1324            .unwrap(),
1325        RestrictionStatus {
1326            ruleset: RulesetStatus::NotEnforced,
1327            landlock: LandlockStatus::NotImplemented,
1328            no_new_privs: true,
1329            log_same_exec: true,
1330            log_new_exec: false,
1331            log_subdomains: true,
1332        }
1333    );
1334
1335    // Log flags with HardRequirement on unsupported ABI return an error.
1336    assert!(matches!(
1337        Ruleset::from(ABI::Unsupported)
1338            .handle_access(AccessFs::Execute)
1339            .unwrap()
1340            .create()
1341            .unwrap()
1342            .set_compatibility(CompatLevel::HardRequirement)
1343            .log_new_exec(true)
1344            .unwrap_err(),
1345        RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
1346            flag: RestrictSelfFlag::LogNewExec,
1347            set: true,
1348        })
1349    ));
1350
1351    // Tests inconsistency between the ruleset handled access-rights and the rule access-rights.
1352    for handled_access in &[
1353        make_bitflags!(AccessFs::{Execute | WriteFile}),
1354        AccessFs::Execute.into(),
1355    ] {
1356        let ruleset = Ruleset::from(ABI::V1)
1357            .handle_access(*handled_access)
1358            .unwrap();
1359        // Fakes a call to create() to test without involving the kernel (i.e. no
1360        // landlock_ruleset_create() call).
1361        let ruleset_created = RulesetCreated::new(ruleset, None);
1362        assert!(matches!(
1363            ruleset_created
1364                .add_rule(PathBeneath::new(
1365                    PathFd::new("/").unwrap(),
1366                    AccessFs::ReadFile
1367                ))
1368                .unwrap_err(),
1369            RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
1370        ));
1371    }
1372}
1373
1374#[test]
1375fn ignore_abi_v2_with_abi_v1() {
1376    // We don't need kernel/CI support for Landlock because no related syscalls should actually be
1377    // performed.
1378    assert_eq!(
1379        Ruleset::from(ABI::V1)
1380            .set_compatibility(CompatLevel::HardRequirement)
1381            .handle_access(AccessFs::from_all(ABI::V1))
1382            .unwrap()
1383            .set_compatibility(CompatLevel::SoftRequirement)
1384            // Because Ruleset only supports V1, Refer will be ignored.
1385            .handle_access(AccessFs::Refer)
1386            .unwrap()
1387            .create()
1388            .unwrap()
1389            .add_rule(PathBeneath::new(
1390                PathFd::new("/tmp").unwrap(),
1391                AccessFs::from_all(ABI::V2)
1392            ))
1393            .unwrap()
1394            .add_rule(PathBeneath::new(
1395                PathFd::new("/usr").unwrap(),
1396                make_bitflags!(AccessFs::{ReadFile | ReadDir})
1397            ))
1398            .unwrap()
1399            .restrict_self()
1400            .unwrap(),
1401        RestrictionStatus {
1402            ruleset: RulesetStatus::NotEnforced,
1403            landlock: LandlockStatus::Available {
1404                effective_abi: ABI::V1,
1405                kernel_abi: None,
1406            },
1407            no_new_privs: true,
1408            log_same_exec: true,
1409            log_new_exec: false,
1410            log_subdomains: true,
1411        }
1412    );
1413}
1414
1415#[test]
1416fn unsupported_handled_access() {
1417    assert!(matches!(
1418        Ruleset::from(ABI::V3)
1419            .handle_access(AccessNet::from_all(ABI::V3))
1420            .unwrap_err(),
1421        RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
1422            CompatError::Access(AccessError::Empty)
1423        )))
1424    ));
1425}
1426
1427#[test]
1428fn unsupported_handled_access_errno() {
1429    assert_eq!(
1430        Errno::from(
1431            Ruleset::from(ABI::V3)
1432                .handle_access(AccessNet::from_all(ABI::V3))
1433                .unwrap_err()
1434        ),
1435        Errno::new(libc::EINVAL)
1436    );
1437}