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