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