Skip to main content

landlock/
compat.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use crate::{uapi, Access, CompatError};
4use std::fmt::{self, Display, Formatter};
5use std::io::Error;
6
7#[cfg(test)]
8use std::convert::TryInto;
9#[cfg(test)]
10use strum::{EnumCount, IntoEnumIterator};
11#[cfg(test)]
12use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
13
14/// Version of the Landlock [ABI](https://en.wikipedia.org/wiki/Application_binary_interface).
15///
16/// `ABI` enables getting the features supported by a specific Landlock ABI
17/// (without relying on the kernel version which may not be accessible or patched).
18/// For example, [`AccessFs::from_all(ABI::V1)`](Access::from_all)
19/// gets all the file system access rights defined by the first version.
20///
21/// Without `ABI`, it would be hazardous to rely on the the full set of access flags
22/// (e.g., `BitFlags::<AccessFs>::all()` or `BitFlags::ALL`),
23/// a moving target that would change the semantics of your Landlock rule
24/// when migrating to a newer version of this crate.
25/// Indeed, a simple `cargo update` or `cargo install` run by any developer
26/// can result in a new version of this crate (fixing bugs or bringing non-breaking changes).
27/// This crate cannot give any guarantee concerning the new restrictions resulting from
28/// these unknown bits (i.e. access rights) that would not be controlled by your application but by
29/// a future version of this crate instead.
30/// Because we cannot know what the effect on your application of an unknown restriction would be
31/// when handling an untested Landlock access right (i.e. denied-by-default access),
32/// it could trigger bugs in your application.
33///
34/// This crate provides a set of tools to sandbox as much as possible
35/// while guaranteeing a consistent behavior thanks to the [`Compatible`] methods.
36/// You should also test with different relevant kernel versions,
37/// see [landlock-test-tools](https://github.com/landlock-lsm/landlock-test-tools) and
38/// [CI integration](https://github.com/landlock-lsm/rust-landlock/pull/41).
39///
40/// This way, we can have the guarantee that the use of a set of tested Landlock ABI works as
41/// expected because features brought by newer Landlock ABI will never be enabled by default
42/// (cf. [Linux kernel compatibility contract](https://docs.kernel.org/userspace-api/landlock.html#compatibility)).
43///
44/// In a nutshell, test the access rights you request on a kernel that support them and
45/// on a kernel that doesn't support them.
46///
47/// Derived `Debug` formats are [not stable](https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability).
48#[cfg_attr(test, derive(EnumIter, EnumCountMacro))]
49#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
50#[non_exhaustive]
51pub enum ABI {
52    /// Kernel not supporting Landlock, either because it is not built with Landlock
53    /// or Landlock is not enabled at boot.
54    Unsupported = 0,
55    /// First Landlock ABI, introduced with
56    /// [Linux 5.13](https://git.kernel.org/stable/c/17ae69aba89dbfa2139b7f8024b757ab3cc42f59).
57    V1 = 1,
58    /// Second Landlock ABI, introduced with
59    /// [Linux 5.19](https://git.kernel.org/stable/c/cb44e4f061e16be65b8a16505e121490c66d30d0).
60    V2 = 2,
61    /// Third Landlock ABI, introduced with
62    /// [Linux 6.2](https://git.kernel.org/stable/c/299e2b1967578b1442128ba8b3e86ed3427d3651).
63    V3 = 3,
64    /// Fourth Landlock ABI, introduced with
65    /// [Linux 6.7](https://git.kernel.org/stable/c/136cc1e1f5be75f57f1e0404b94ee1c8792cb07d).
66    V4 = 4,
67    /// Fifth Landlock ABI, introduced with
68    /// [Linux 6.10](https://git.kernel.org/stable/c/2fc0e7892c10734c1b7c613ef04836d57d4676d5).
69    V5 = 5,
70    /// Sixth Landlock ABI, introduced with
71    /// [Linux 6.12](https://git.kernel.org/stable/c/e1b061b444fb01c237838f0d8238653afe6a8094).
72    V6 = 6,
73    /// Seventh Landlock ABI, introduced with
74    /// [Linux 6.15](https://git.kernel.org/stable/c/72885116069abdd05c245707c3989fc605632970).
75    V7 = 7,
76    /// Eighth Landlock ABI, introduced with [Linux
77    /// 7.0](https://git.kernel.org/stable/c/c22e26bd0906e9c8325462993f01adb16b8ea2c0).
78    V8 = 8,
79    /// Ninth Landlock ABI, introduced with
80    /// [Linux 7.1](https://git.kernel.org/stable/c/b8f82cb0d84d00c04cdbdce42f67df71b8507e8b).
81    V9 = 9,
82    /// Tenth Landlock ABI, introduced with
83    /// [Linux 7.2](https://git.kernel.org/stable/c/5e2e14749c3d969e263a879db104db6e9f0eb484).
84    V10 = 10,
85}
86
87// ABI should not be dynamically created (in other crates) according to the running kernel
88// to avoid inconsistent behaviors and non-determinism. Creating ABIs based on runtime detection
89// can lead to unreliable sandboxing where rules might differ between executions.
90impl ABI {
91    #[cfg(test)]
92    fn is_known(value: i32) -> bool {
93        value > 0 && value < ABI::COUNT as i32
94    }
95}
96
97/// Converting from an integer to an ABI should only be used for testing.
98/// Indeed, manually setting the ABI can lead to inconsistent and unexpected behaviors.
99/// Instead, just use the appropriate access rights, this library will handle the rest.
100impl From<i32> for ABI {
101    fn from(value: i32) -> ABI {
102        match value {
103            n if n <= 0 => ABI::Unsupported,
104            1 => ABI::V1,
105            2 => ABI::V2,
106            3 => ABI::V3,
107            4 => ABI::V4,
108            5 => ABI::V5,
109            6 => ABI::V6,
110            7 => ABI::V7,
111            8 => ABI::V8,
112            9 => ABI::V9,
113            // Returns the greatest known ABI.
114            _ => ABI::V10,
115        }
116    }
117}
118
119#[test]
120fn abi_from() {
121    // EOPNOTSUPP (-95), ENOSYS (-38)
122    for n in [-95, -38, -1, 0] {
123        assert_eq!(ABI::from(n), ABI::Unsupported);
124    }
125
126    let mut last_i = 1;
127    let mut last_abi = ABI::Unsupported;
128    for (i, abi) in ABI::iter().enumerate() {
129        last_i = i.try_into().unwrap();
130        last_abi = abi;
131        assert_eq!(ABI::from(last_i), last_abi);
132    }
133
134    assert_eq!(ABI::from(last_i + 1), last_abi);
135    assert_eq!(ABI::from(999), last_abi);
136}
137
138#[test]
139fn known_abi() {
140    assert!(!ABI::is_known(-1));
141    assert!(!ABI::is_known(0));
142    assert!(!ABI::is_known(999));
143
144    let mut last_i = -1;
145    for (i, _) in ABI::iter().enumerate().skip(1) {
146        last_i = i as i32;
147        assert!(ABI::is_known(last_i));
148    }
149    assert!(!ABI::is_known(last_i + 1));
150}
151
152impl Display for ABI {
153    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
154        match self {
155            ABI::Unsupported => write!(f, "unsupported"),
156            v => (*v as u32).fmt(f),
157        }
158    }
159}
160
161/// Status of Landlock support for the running system.
162///
163/// This enum is used to represent the status of the Landlock support for the system where the code
164/// is executed. It can indicate whether Landlock is available or not.
165///
166/// # Warning
167///
168/// Sandboxed programs should only use this data to log or provide information to users,
169/// not to change their behavior according to this status.  Indeed, the `Ruleset` and the other
170/// types are designed to handle the compatibility in a simple and safe way.
171#[derive(Copy, Clone, Debug, PartialEq, Eq)]
172pub enum LandlockStatus {
173    /// Landlock is supported but not enabled (`EOPNOTSUPP`).
174    NotEnabled,
175    /// Landlock is not implemented (i.e. not built into the running kernel: `ENOSYS`).
176    NotImplemented,
177    /// Landlock is available and working on the running system.
178    ///
179    /// This indicates that the kernel supports Landlock and it's properly enabled.
180    /// The crate uses the `effective_abi` for all operations, which represents
181    /// the highest ABI version that both the kernel and this crate understand.
182    Available {
183        /// The effective ABI version that this crate will use for Landlock operations.
184        /// This is the intersection of what the kernel supports and what this crate knows about.
185        effective_abi: ABI,
186        /// The actual kernel ABI version when it's newer than any ABI supported by this crate.
187        ///
188        /// If `Some(version)`, it means the running kernel supports Landlock ABI `version`
189        /// which is higher than the latest ABI known by this crate.
190        ///
191        /// This field is purely informational and is never used for Landlock operations.
192        /// The crate always and only uses `effective_abi` for all functionality.
193        kernel_abi: Option<i32>,
194    },
195}
196
197impl LandlockStatus {
198    // Must remain private to avoid inconsistent behavior using such unknown-at-build-time ABI
199    // e.g., AccessFs::from_all(ABI::new_current())
200    //
201    // This should not be Default::default() because the returned value would may not be the same
202    // for all users.
203    fn current() -> Self {
204        // Landlock ABI version starts at 1 but errno is only set for negative values.
205        let v = unsafe {
206            uapi::landlock_create_ruleset(
207                std::ptr::null(),
208                0,
209                uapi::LANDLOCK_CREATE_RULESET_VERSION,
210            )
211        };
212        if v < 0 {
213            // The only possible error values should be EOPNOTSUPP and ENOSYS.
214            match Error::last_os_error().raw_os_error() {
215                Some(libc::EOPNOTSUPP) => Self::NotEnabled,
216                _ => Self::NotImplemented,
217            }
218        } else {
219            let abi = ABI::from(v);
220            Self::Available {
221                effective_abi: abi,
222                kernel_abi: (v != abi as i32).then_some(v),
223            }
224        }
225    }
226}
227
228// Test against the running kernel.
229#[test]
230fn test_current_landlock_status() {
231    let status = LandlockStatus::current();
232    if *TEST_ABI == ABI::Unsupported {
233        assert_eq!(status, LandlockStatus::NotImplemented);
234    } else {
235        assert!(
236            matches!(status, LandlockStatus::Available { effective_abi, .. } if effective_abi == *TEST_ABI)
237        );
238        if std::env::var(TEST_ABI_ENV_NAME).is_ok() {
239            // We cannot reliably check for unknown kernel.
240            assert!(matches!(
241                status,
242                LandlockStatus::Available {
243                    kernel_abi: None,
244                    ..
245                }
246            ));
247        }
248    }
249}
250
251impl From<LandlockStatus> for ABI {
252    fn from(status: LandlockStatus) -> Self {
253        match status {
254            // The only possible error values should be EOPNOTSUPP and ENOSYS,
255            // but let's convert all kind of errors as unsupported.
256            LandlockStatus::NotEnabled | LandlockStatus::NotImplemented => ABI::Unsupported,
257            LandlockStatus::Available { effective_abi, .. } => effective_abi,
258        }
259    }
260}
261
262// This is only useful to tests and should not be exposed publicly because
263// the mapping can only be partial.
264#[cfg(test)]
265impl From<ABI> for LandlockStatus {
266    fn from(abi: ABI) -> Self {
267        match abi {
268            // Convert to ENOSYS because of check_ruleset_support() and ruleset_unsupported() tests.
269            ABI::Unsupported => Self::NotImplemented,
270            _ => Self::Available {
271                effective_abi: abi,
272                kernel_abi: None,
273            },
274        }
275    }
276}
277
278#[cfg(test)]
279pub(crate) static TEST_ABI_ENV_NAME: &str = "LANDLOCK_CRATE_TEST_ABI";
280
281#[cfg(test)]
282lazy_static! {
283    pub(crate) static ref TEST_ABI: ABI = match std::env::var("LANDLOCK_CRATE_TEST_ABI") {
284        Ok(s) => {
285            let n = s.parse::<i32>().unwrap();
286            if ABI::is_known(n) || n == 0 {
287                ABI::from(n)
288            } else {
289                panic!("Unknown ABI: {n}");
290            }
291        }
292        Err(std::env::VarError::NotPresent) => LandlockStatus::current().into(),
293        Err(e) => panic!("Failed to read LANDLOCK_CRATE_TEST_ABI: {e}"),
294    };
295}
296
297#[cfg(test)]
298pub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support: Option<ABI>) -> bool {
299    mock < partial_support
300        || mock <= *TEST_ABI
301        || if let Some(full) = full_support {
302            full <= *TEST_ABI
303        } else {
304            partial_support <= *TEST_ABI
305        }
306}
307
308#[cfg(test)]
309pub(crate) fn get_errno_from_landlock_status() -> Option<i32> {
310    match LandlockStatus::current() {
311        LandlockStatus::NotImplemented | LandlockStatus::NotEnabled => {
312            match Error::last_os_error().raw_os_error() {
313                // Returns ENOSYS when the kernel is not built with Landlock support,
314                // or EOPNOTSUPP when Landlock is supported but disabled at boot time.
315                ret @ Some(libc::ENOSYS | libc::EOPNOTSUPP) => ret,
316                // Other values can only come from bogus seccomp filters or debugging tampering.
317                ret => {
318                    eprintln!("Current kernel should support this Landlock ABI according to $LANDLOCK_CRATE_TEST_ABI");
319                    eprintln!("Unexpected result: {ret:?}");
320                    unreachable!();
321                }
322            }
323        }
324        LandlockStatus::Available { .. } => None,
325    }
326}
327
328#[test]
329fn current_kernel_abi() {
330    // Ensures that the tested Landlock ABI is the latest known version supported by the running
331    // kernel.  If this test failed, you need set the LANDLOCK_CRATE_TEST_ABI environment variable
332    // to the Landlock ABI version supported by your kernel.  With a missing variable, the latest
333    // Landlock ABI version known by this crate is automatically set.
334    // From Linux 5.13 to 5.18, you need to run: LANDLOCK_CRATE_TEST_ABI=1 cargo test
335    let test_abi = *TEST_ABI;
336    let current_abi = LandlockStatus::current().into();
337    println!(
338        "Current kernel version: {}",
339        std::fs::read_to_string("/proc/version")
340            .unwrap_or_else(|_| "unknown".into())
341            .trim()
342    );
343    println!("Expected Landlock ABI {test_abi:?} whereas the current ABI is {current_abi:#?}");
344    assert_eq!(test_abi, current_abi);
345}
346
347// CompatState is not public outside this crate.
348/// Returned by ruleset builder.
349#[derive(Copy, Clone, Debug, PartialEq, Eq)]
350pub enum CompatState {
351    /// Initial undefined state.
352    Init,
353    /// All requested restrictions are enforced.
354    Full,
355    /// Some requested restrictions are enforced, following a best-effort approach.
356    Partial,
357    /// The running system doesn't support Landlock.
358    No,
359    /// Final unsupported state.
360    Dummy,
361}
362
363impl CompatState {
364    fn update(&mut self, other: Self) {
365        *self = match (*self, other) {
366            (CompatState::Init, other) => other,
367            (CompatState::Dummy, _) => CompatState::Dummy,
368            (_, CompatState::Dummy) => CompatState::Dummy,
369            (CompatState::No, CompatState::No) => CompatState::No,
370            (CompatState::Full, CompatState::Full) => CompatState::Full,
371            (_, _) => CompatState::Partial,
372        }
373    }
374}
375
376#[test]
377fn compat_state_update_1() {
378    let mut state = CompatState::Full;
379
380    state.update(CompatState::Full);
381    assert_eq!(state, CompatState::Full);
382
383    state.update(CompatState::No);
384    assert_eq!(state, CompatState::Partial);
385
386    state.update(CompatState::Full);
387    assert_eq!(state, CompatState::Partial);
388
389    state.update(CompatState::Full);
390    assert_eq!(state, CompatState::Partial);
391
392    state.update(CompatState::No);
393    assert_eq!(state, CompatState::Partial);
394
395    state.update(CompatState::Dummy);
396    assert_eq!(state, CompatState::Dummy);
397
398    state.update(CompatState::Full);
399    assert_eq!(state, CompatState::Dummy);
400}
401
402#[test]
403fn compat_state_update_2() {
404    let mut state = CompatState::Full;
405
406    state.update(CompatState::Full);
407    assert_eq!(state, CompatState::Full);
408
409    state.update(CompatState::No);
410    assert_eq!(state, CompatState::Partial);
411
412    state.update(CompatState::Full);
413    assert_eq!(state, CompatState::Partial);
414}
415
416#[test]
417fn try_compat_binary_states() {
418    // Supported: state -> Full.
419    let mut compat: Compatibility = ABI::Unsupported.into();
420    assert_eq!(compat.state, CompatState::Init);
421    assert_eq!(compat.try_compat_binary(true, || "err"), Ok(true));
422    assert_eq!(compat.state, CompatState::Full);
423
424    // Unsupported + BestEffort: state -> Partial (Full + No).
425    assert_eq!(compat.try_compat_binary(false, || "err"), Ok(false));
426    assert_eq!(compat.state, CompatState::Partial);
427
428    // Unsupported + SoftRequirement: state -> Dummy.
429    let mut compat: Compatibility = ABI::Unsupported.into();
430    compat.level = Some(CompatLevel::SoftRequirement);
431    assert_eq!(compat.try_compat_binary(false, || "err"), Ok(false));
432    assert_eq!(compat.state, CompatState::Dummy);
433
434    // Unsupported + HardRequirement: returns error.
435    let mut compat: Compatibility = ABI::Unsupported.into();
436    compat.level = Some(CompatLevel::HardRequirement);
437    assert_eq!(compat.try_compat_binary(false, || "err"), Err("err"));
438}
439
440#[cfg_attr(test, derive(PartialEq))]
441#[derive(Copy, Clone, Debug)]
442pub(crate) struct Compatibility {
443    status: LandlockStatus,
444    pub(crate) level: Option<CompatLevel>,
445    pub(crate) state: CompatState,
446}
447
448impl From<LandlockStatus> for Compatibility {
449    fn from(status: LandlockStatus) -> Self {
450        Compatibility {
451            status,
452            level: Default::default(),
453            state: CompatState::Init,
454        }
455    }
456}
457
458#[cfg(test)]
459impl From<ABI> for Compatibility {
460    fn from(abi: ABI) -> Self {
461        Self::from(LandlockStatus::from(abi))
462    }
463}
464
465impl Compatibility {
466    // Compatibility is a semi-opaque struct.
467    #[allow(clippy::new_without_default)]
468    pub(crate) fn new() -> Self {
469        LandlockStatus::current().into()
470    }
471
472    pub(crate) fn update(&mut self, state: CompatState) {
473        self.state.update(state);
474    }
475
476    pub(crate) fn abi(&self) -> ABI {
477        self.status.into()
478    }
479
480    pub(crate) fn status(&self) -> LandlockStatus {
481        self.status
482    }
483
484    /// Handles the compat dispatch for a binary supported/not-supported check.
485    ///
486    /// This is factored out from the No branch of
487    /// [`TryCompat::try_compat()`](crate::TryCompat::try_compat) for use by
488    /// [`SyscallFlagExt::try_compat()`](crate::flags::SyscallFlagExt::try_compat),
489    /// where a single flag is either fully supported or not (no Partial case).
490    ///
491    /// Returns `Ok(true)` if supported (caller should apply the flag),
492    /// `Ok(false)` if unsupported but acceptable
493    /// ([`BestEffort`](crate::CompatLevel::BestEffort) /
494    /// [`SoftRequirement`](crate::CompatLevel::SoftRequirement)), or `Err` if
495    /// unsupported with [`HardRequirement`](crate::CompatLevel::HardRequirement).
496    pub(crate) fn try_compat_binary<E, F>(
497        &mut self,
498        supported: bool,
499        make_error: F,
500    ) -> Result<bool, E>
501    where
502        F: FnOnce() -> E,
503    {
504        if supported {
505            self.state.update(CompatState::Full);
506            Ok(true)
507        } else {
508            match self.level.into() {
509                CompatLevel::BestEffort => {
510                    self.state.update(CompatState::No);
511                    Ok(false)
512                }
513                CompatLevel::SoftRequirement => {
514                    self.state.update(CompatState::Dummy);
515                    Ok(false)
516                }
517                CompatLevel::HardRequirement => Err(make_error()),
518            }
519        }
520    }
521}
522
523pub(crate) mod private {
524    use crate::CompatLevel;
525
526    pub trait OptionCompatLevelMut {
527        fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel>;
528    }
529}
530
531/// Properly handles runtime unsupported features.
532///
533/// This guarantees consistent behaviors across crate users
534/// and runtime kernels even if this crate get new features.
535/// It eases backward compatibility and enables future-proofness.
536///
537/// Landlock is a security feature designed to help improve security of a running system
538/// thanks to application developers.
539/// To protect users as much as possible,
540/// compatibility with the running system should then be handled in a best-effort way,
541/// contrary to common system features.
542/// In some circumstances
543/// (e.g. applications carefully designed to only be run with a specific set of kernel features),
544/// it may be required to error out if some of these features are not available
545/// and will then not be enforced.
546pub trait Compatible: Sized + private::OptionCompatLevelMut {
547    /// To enable a best-effort security approach,
548    /// Landlock features that are not supported by the running system
549    /// are silently ignored by default,
550    /// which is a sane choice for most use cases.
551    /// However, on some rare circumstances,
552    /// developers may want to have some guarantees that their applications
553    /// will not run if a certain level of sandboxing is not possible.
554    /// If we really want to error out when not all our requested requirements are met,
555    /// then we can configure it with `set_compatibility()`.
556    ///
557    /// The `Compatible` trait is implemented for all object builders
558    /// (e.g. [`Ruleset`](crate::Ruleset)).
559    /// Such builders have a set of methods to incrementally build an object.
560    /// These build methods rely on kernel features that may not be available at runtime.
561    /// The `set_compatibility()` method enables to control the effect of
562    /// the following build method calls starting after the `set_compatibility()` call.
563    /// Such effect can be:
564    /// * to silently ignore unsupported features
565    ///   and continue building ([`CompatLevel::BestEffort`]);
566    /// * to silently ignore unsupported features
567    ///   and ignore the whole build ([`CompatLevel::SoftRequirement`]);
568    /// * to return an error for any unsupported feature ([`CompatLevel::HardRequirement`]).
569    ///
570    /// Taking [`Ruleset`](crate::Ruleset) as an example,
571    /// the [`handle_access()`](crate::RulesetAttr::handle_access()) build method
572    /// returns a [`Result`] that can be [`Err(RulesetError)`](crate::RulesetError)
573    /// with a nested [`CompatError`].
574    /// Such error can only occur with a running Linux kernel not supporting the requested
575    /// Landlock accesses *and* if the current compatibility level is
576    /// [`CompatLevel::HardRequirement`].
577    /// However, such error is not possible with [`CompatLevel::BestEffort`]
578    /// nor [`CompatLevel::SoftRequirement`].
579    ///
580    /// The order of this call is important because
581    /// it defines the behavior of the following build method calls that return a [`Result`].
582    /// If `set_compatibility(CompatLevel::HardRequirement)` is called on an object,
583    /// then a [`CompatError`] may be returned for the next method calls,
584    /// until the next call to `set_compatibility()`.
585    /// This enables to change the behavior of a set of build method calls,
586    /// for instance to be sure that the sandbox will at least restrict some access rights.
587    ///
588    /// New objects inherit the compatibility configuration of their parents, if any.
589    /// For instance, [`Ruleset::create()`](crate::Ruleset::create()) returns
590    /// a [`RulesetCreated`](crate::RulesetCreated) object that inherits the
591    /// `Ruleset`'s compatibility configuration.
592    ///
593    /// # Example with `SoftRequirement`
594    ///
595    /// Let's say an application legitimately needs to rename files between directories.
596    /// Because of [previous Landlock limitations](https://docs.kernel.org/userspace-api/landlock.html#file-renaming-and-linking-abi-2),
597    /// this was forbidden with the [first version of Landlock](ABI::V1),
598    /// but it is now handled starting with the [second version](ABI::V2).
599    /// For this use case, we only want the application to be sandboxed
600    /// if we have the guarantee that it will not break a legitimate usage (i.e. rename files).
601    /// We then create a ruleset which will either support file renaming
602    /// (thanks to [`AccessFs::Refer`](crate::AccessFs::Refer)) or silently do nothing.
603    ///
604    /// ```
605    /// use landlock::*;
606    ///
607    /// fn ruleset_handling_renames() -> Result<RulesetCreated, RulesetError> {
608    ///     Ok(Ruleset::default()
609    ///         // This ruleset must either handle the AccessFs::Refer right,
610    ///         // or it must silently ignore the whole sandboxing.
611    ///         .set_compatibility(CompatLevel::SoftRequirement)
612    ///         .handle_access(AccessFs::Refer)?
613    ///         // However, this ruleset may also handle other (future) access rights
614    ///         // if they are supported by the running kernel.
615    ///         .set_compatibility(CompatLevel::BestEffort)
616    ///         .handle_access(AccessFs::from_all(ABI::V10))?
617    ///         .create()?)
618    /// }
619    /// ```
620    ///
621    /// # Example with `HardRequirement`
622    ///
623    /// Security-dedicated applications may want to ensure that
624    /// an untrusted software component is subject to a minimum of restrictions before launching it.
625    /// In this case, we want to create a ruleset which will at least support
626    /// all restrictions provided by the [first version of Landlock](ABI::V1),
627    /// and opportunistically handle restrictions supported by newer kernels.
628    ///
629    /// ```
630    /// use landlock::*;
631    ///
632    /// fn ruleset_fragile() -> Result<RulesetCreated, RulesetError> {
633    ///     Ok(Ruleset::default()
634    ///         // This ruleset must either handle at least all accesses defined by
635    ///         // the first Landlock version (e.g. AccessFs::WriteFile),
636    ///         // or the following handle_access() call must return a wrapped
637    ///         // AccessError<AccessFs>::Incompatible error.
638    ///         .set_compatibility(CompatLevel::HardRequirement)
639    ///         .handle_access(AccessFs::from_all(ABI::V1))?
640    ///         // However, this ruleset may also handle new access rights
641    ///         // (e.g. AccessFs::Refer defined by the second version of Landlock)
642    ///         // if they are supported by the running kernel,
643    ///         // but without returning any error otherwise.
644    ///         .set_compatibility(CompatLevel::BestEffort)
645    ///         .handle_access(AccessFs::from_all(ABI::V10))?
646    ///         .create()?)
647    /// }
648    /// ```
649    fn set_compatibility(mut self, level: CompatLevel) -> Self {
650        *self.as_option_compat_level_mut() = Some(level);
651        self
652    }
653
654    /// Cf. [`set_compatibility()`](Compatible::set_compatibility()):
655    ///
656    /// - `set_best_effort(true)` translates to `set_compatibility(CompatLevel::BestEffort)`.
657    ///
658    /// - `set_best_effort(false)` translates to `set_compatibility(CompatLevel::HardRequirement)`.
659    #[deprecated(note = "Use set_compatibility() instead")]
660    fn set_best_effort(self, best_effort: bool) -> Self
661    where
662        Self: Sized,
663    {
664        self.set_compatibility(match best_effort {
665            true => CompatLevel::BestEffort,
666            false => CompatLevel::HardRequirement,
667        })
668    }
669}
670
671#[test]
672#[allow(deprecated)]
673fn deprecated_set_best_effort() {
674    use crate::{CompatLevel, Compatible, Ruleset};
675
676    assert_eq!(
677        Ruleset::default().set_best_effort(true).compat,
678        Ruleset::default()
679            .set_compatibility(CompatLevel::BestEffort)
680            .compat
681    );
682    assert_eq!(
683        Ruleset::default().set_best_effort(false).compat,
684        Ruleset::default()
685            .set_compatibility(CompatLevel::HardRequirement)
686            .compat
687    );
688}
689
690/// See the [`Compatible`] documentation.
691#[cfg_attr(test, derive(EnumIter))]
692#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
693pub enum CompatLevel {
694    /// Takes into account the build requests if they are supported by the running system,
695    /// or silently ignores them otherwise.
696    /// Never returns a compatibility error.
697    #[default]
698    BestEffort,
699    /// Takes into account the build requests if they are supported by the running system,
700    /// or silently ignores the whole build object otherwise.
701    /// Never returns a compatibility error.
702    /// If not supported,
703    /// the call to [`RulesetCreated::restrict_self()`](crate::RulesetCreated::restrict_self())
704    /// will return a
705    /// [`RestrictionStatus { ruleset: RulesetStatus::NotEnforced, no_new_privs: false, }`](crate::RestrictionStatus).
706    SoftRequirement,
707    /// Takes into account the build requests if they are supported by the running system,
708    /// or returns a compatibility error otherwise ([`CompatError`]).
709    HardRequirement,
710}
711
712impl From<Option<CompatLevel>> for CompatLevel {
713    fn from(opt: Option<CompatLevel>) -> Self {
714        match opt {
715            None => CompatLevel::default(),
716            Some(ref level) => *level,
717        }
718    }
719}
720
721// TailoredCompatLevel could be replaced with AsMut<Option<CompatLevel>>, but only traits defined
722// in the current crate can be implemented for types defined outside of the crate.  Furthermore it
723// provides a default implementation which is handy for types such as BitFlags.
724pub trait TailoredCompatLevel {
725    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
726    where
727        L: Into<CompatLevel>,
728    {
729        parent_level.into()
730    }
731}
732
733impl<T> TailoredCompatLevel for T
734where
735    Self: Compatible,
736{
737    // Every Compatible trait implementation returns its own compatibility level, if set.
738    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
739    where
740        L: Into<CompatLevel>,
741    {
742        // Using a mutable reference is not required but it makes the code simpler (no double AsRef
743        // implementations for each Compatible types), and more importantly it guarantees
744        // consistency with Compatible::set_compatibility().
745        match self.as_option_compat_level_mut() {
746            None => parent_level.into(),
747            // Returns the most constrained compatibility level.
748            Some(ref level) => parent_level.into().max(*level),
749        }
750    }
751}
752
753#[test]
754fn tailored_compat_level() {
755    use crate::{AccessFs, PathBeneath, PathFd};
756
757    fn new_path(level: CompatLevel) -> PathBeneath<PathFd> {
758        PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Execute).set_compatibility(level)
759    }
760
761    for parent_level in CompatLevel::iter() {
762        assert_eq!(
763            new_path(CompatLevel::BestEffort).tailored_compat_level(parent_level),
764            parent_level
765        );
766        assert_eq!(
767            new_path(CompatLevel::HardRequirement).tailored_compat_level(parent_level),
768            CompatLevel::HardRequirement
769        );
770    }
771
772    assert_eq!(
773        new_path(CompatLevel::SoftRequirement).tailored_compat_level(CompatLevel::SoftRequirement),
774        CompatLevel::SoftRequirement
775    );
776
777    for child_level in CompatLevel::iter() {
778        assert_eq!(
779            new_path(child_level).tailored_compat_level(CompatLevel::BestEffort),
780            child_level
781        );
782        assert_eq!(
783            new_path(child_level).tailored_compat_level(CompatLevel::HardRequirement),
784            CompatLevel::HardRequirement
785        );
786    }
787}
788
789// CompatResult is not public outside this crate.
790pub enum CompatResult<A>
791where
792    A: Access,
793{
794    // Fully matches the request.
795    Full,
796    // Partially matches the request.
797    Partial(CompatError<A>),
798    // Doesn't matches the request.
799    No(CompatError<A>),
800}
801
802// TryCompat is not public outside this crate.
803pub trait TryCompat<A>
804where
805    Self: Sized + TailoredCompatLevel,
806    A: Access,
807{
808    fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>>;
809
810    // Default implementation for objects without children.
811    //
812    // If returning something other than Ok(Some(self)), the implementation must use its own
813    // compatibility level, if any, with self.tailored_compat_level(default_compat_level), and pass
814    // it with the abi and compat_state to each child.try_compat().  See PathBeneath implementation
815    // and the self.allowed_access.try_compat() call.
816    //
817    // # Warning
818    //
819    // Errors must be prioritized over incompatibility (i.e. return Err(e) over Ok(None)) for all
820    // children.
821    fn try_compat_children<L>(
822        self,
823        _abi: ABI,
824        _parent_level: L,
825        _compat_state: &mut CompatState,
826    ) -> Result<Option<Self>, CompatError<A>>
827    where
828        L: Into<CompatLevel>,
829    {
830        Ok(Some(self))
831    }
832
833    // Update compat_state and return an error according to try_compat_*() error, or to the
834    // compatibility level, i.e. either route compatible object or error.
835    fn try_compat<L>(
836        mut self,
837        abi: ABI,
838        parent_level: L,
839        compat_state: &mut CompatState,
840    ) -> Result<Option<Self>, CompatError<A>>
841    where
842        L: Into<CompatLevel>,
843    {
844        let compat_level = self.tailored_compat_level(parent_level);
845        let some_inner = match self.try_compat_inner(abi) {
846            Ok(CompatResult::Full) => {
847                compat_state.update(CompatState::Full);
848                true
849            }
850            Ok(CompatResult::Partial(error)) => match compat_level {
851                CompatLevel::BestEffort => {
852                    compat_state.update(CompatState::Partial);
853                    true
854                }
855                CompatLevel::SoftRequirement => {
856                    compat_state.update(CompatState::Dummy);
857                    false
858                }
859                CompatLevel::HardRequirement => {
860                    compat_state.update(CompatState::Dummy);
861                    return Err(error);
862                }
863            },
864            Ok(CompatResult::No(error)) => match compat_level {
865                CompatLevel::BestEffort => {
866                    compat_state.update(CompatState::No);
867                    false
868                }
869                CompatLevel::SoftRequirement => {
870                    compat_state.update(CompatState::Dummy);
871                    false
872                }
873                CompatLevel::HardRequirement => {
874                    compat_state.update(CompatState::Dummy);
875                    return Err(error);
876                }
877            },
878            Err(error) => {
879                // Safeguard to help for test consistency.
880                compat_state.update(CompatState::Dummy);
881                return Err(error);
882            }
883        };
884
885        // At this point, any inner error have been returned, so we can proceed with
886        // try_compat_children()?.
887        match self.try_compat_children(abi, compat_level, compat_state)? {
888            Some(n) if some_inner => Ok(Some(n)),
889            _ => Ok(None),
890        }
891    }
892}