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