landlock/
compat.rs

1use crate::{uapi, Access, CompatError};
2
3#[cfg(test)]
4use std::convert::TryInto;
5#[cfg(test)]
6use strum::{EnumCount, IntoEnumIterator};
7#[cfg(test)]
8use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
9
10/// Version of the Landlock [ABI](https://en.wikipedia.org/wiki/Application_binary_interface).
11///
12/// `ABI` enables getting the features supported by a specific Landlock ABI
13/// (without relying on the kernel version which may not be accessible or patched).
14/// For example, [`AccessFs::from_all(ABI::V1)`](Access::from_all)
15/// gets all the file system access rights defined by the first version.
16///
17/// Without `ABI`, it would be hazardous to rely on the the full set of access flags
18/// (e.g., `BitFlags::<AccessFs>::all()` or `BitFlags::ALL`),
19/// a moving target that would change the semantics of your Landlock rule
20/// when migrating to a newer version of this crate.
21/// Indeed, a simple `cargo update` or `cargo install` run by any developer
22/// can result in a new version of this crate (fixing bugs or bringing non-breaking changes).
23/// This crate cannot give any guarantee concerning the new restrictions resulting from
24/// these unknown bits (i.e. access rights) that would not be controlled by your application but by
25/// a future version of this crate instead.
26/// Because we cannot know what the effect on your application of an unknown restriction would be
27/// when handling an untested Landlock access right (i.e. denied-by-default access),
28/// it could trigger bugs in your application.
29///
30/// This crate provides a set of tools to sandbox as much as possible
31/// while guaranteeing a consistent behavior thanks to the [`Compatible`] methods.
32/// You should also test with different relevant kernel versions,
33/// see [landlock-test-tools](https://github.com/landlock-lsm/landlock-test-tools) and
34/// [CI integration](https://github.com/landlock-lsm/rust-landlock/pull/41).
35///
36/// This way, we can have the guarantee that the use of a set of tested Landlock ABI works as
37/// expected because features brought by newer Landlock ABI will never be enabled by default
38/// (cf. [Linux kernel compatibility contract](https://docs.kernel.org/userspace-api/landlock.html#compatibility)).
39///
40/// In a nutshell, test the access rights you request on a kernel that support them and
41/// on a kernel that doesn't support them.
42#[cfg_attr(
43    test,
44    derive(Debug, PartialEq, Eq, PartialOrd, EnumIter, EnumCountMacro)
45)]
46#[derive(Copy, Clone)]
47#[non_exhaustive]
48pub enum ABI {
49    /// Kernel not supporting Landlock, either because it is not built with Landlock
50    /// or Landlock is not enabled at boot.
51    Unsupported = 0,
52    /// First Landlock ABI, introduced with
53    /// [Linux 5.13](https://git.kernel.org/stable/c/17ae69aba89dbfa2139b7f8024b757ab3cc42f59).
54    V1 = 1,
55    /// Second Landlock ABI, introduced with
56    /// [Linux 5.19](https://git.kernel.org/stable/c/cb44e4f061e16be65b8a16505e121490c66d30d0).
57    V2 = 2,
58    /// Third Landlock ABI, introduced with
59    /// [Linux 6.2](https://git.kernel.org/stable/c/299e2b1967578b1442128ba8b3e86ed3427d3651).
60    V3 = 3,
61    /// Fourth Landlock ABI, introduced with
62    /// [Linux 6.7](https://git.kernel.org/stable/c/136cc1e1f5be75f57f1e0404b94ee1c8792cb07d).
63    V4 = 4,
64    /// Fifth Landlock ABI, introduced with
65    /// [Linux 6.10](https://git.kernel.org/stable/c/2fc0e7892c10734c1b7c613ef04836d57d4676d5).
66    V5 = 5,
67}
68
69impl ABI {
70    // Must remain private to avoid inconsistent behavior by passing Ok(self) to a builder method,
71    // e.g. to make it impossible to call ruleset.handle_fs(ABI::new_current()?)
72    fn new_current() -> Self {
73        ABI::from(unsafe {
74            // Landlock ABI version starts at 1 but errno is only set for negative values.
75            uapi::landlock_create_ruleset(
76                std::ptr::null(),
77                0,
78                uapi::LANDLOCK_CREATE_RULESET_VERSION,
79            )
80        })
81    }
82
83    #[cfg(test)]
84    fn is_known(value: i32) -> bool {
85        value > 0 && value < ABI::COUNT as i32
86    }
87}
88
89/// Converting from an integer to an ABI should only be used for testing.
90/// Indeed, manually setting the ABI can lead to inconsistent and unexpected behaviors.
91/// Instead, just use the appropriate access rights, this library will handle the rest.
92impl From<i32> for ABI {
93    fn from(value: i32) -> ABI {
94        match value {
95            // The only possible error values should be EOPNOTSUPP and ENOSYS, but let's interpret
96            // all kind of errors as unsupported.
97            n if n <= 0 => ABI::Unsupported,
98            1 => ABI::V1,
99            2 => ABI::V2,
100            3 => ABI::V3,
101            4 => ABI::V4,
102            // Returns the greatest known ABI.
103            _ => ABI::V5,
104        }
105    }
106}
107
108#[test]
109fn abi_from() {
110    // EOPNOTSUPP (-95), ENOSYS (-38)
111    for n in [-95, -38, -1, 0] {
112        assert_eq!(ABI::from(n), ABI::Unsupported);
113    }
114
115    let mut last_i = 1;
116    let mut last_abi = ABI::Unsupported;
117    for (i, abi) in ABI::iter().enumerate() {
118        last_i = i.try_into().unwrap();
119        last_abi = abi;
120        assert_eq!(ABI::from(last_i), last_abi);
121    }
122
123    assert_eq!(ABI::from(last_i + 1), last_abi);
124    assert_eq!(ABI::from(9), last_abi);
125}
126
127#[test]
128fn known_abi() {
129    assert!(!ABI::is_known(-1));
130    assert!(!ABI::is_known(0));
131    assert!(!ABI::is_known(99));
132
133    let mut last_i = -1;
134    for (i, _) in ABI::iter().enumerate().skip(1) {
135        last_i = i as i32;
136        assert!(ABI::is_known(last_i));
137    }
138    assert!(!ABI::is_known(last_i + 1));
139}
140
141#[cfg(test)]
142lazy_static! {
143    static ref TEST_ABI: ABI = match std::env::var("LANDLOCK_CRATE_TEST_ABI") {
144        Ok(s) => {
145            let n = s.parse::<i32>().unwrap();
146            if ABI::is_known(n) || n == 0 {
147                ABI::from(n)
148            } else {
149                panic!("Unknown ABI: {n}");
150            }
151        }
152        Err(std::env::VarError::NotPresent) => ABI::new_current(),
153        Err(e) => panic!("Failed to read LANDLOCK_CRATE_TEST_ABI: {e}"),
154    };
155}
156
157#[cfg(test)]
158pub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support: Option<ABI>) -> bool {
159    mock < partial_support
160        || mock <= *TEST_ABI
161        || if let Some(full) = full_support {
162            full <= *TEST_ABI
163        } else {
164            partial_support <= *TEST_ABI
165        }
166}
167
168#[cfg(test)]
169pub(crate) fn get_errno_from_landlock_status() -> Option<i32> {
170    use std::io::Error;
171
172    match ABI::new_current() {
173        ABI::Unsupported => match Error::last_os_error().raw_os_error() {
174            // Returns ENOSYS when the kernel is not built with Landlock support,
175            // or EOPNOTSUPP when Landlock is supported but disabled at boot time.
176            ret @ Some(libc::ENOSYS | libc::EOPNOTSUPP) => ret,
177            // Other values can only come from bogus seccomp filters or debug tampering.
178            _ => unreachable!(),
179        },
180        _ => None,
181    }
182}
183
184#[test]
185fn current_kernel_abi() {
186    // Ensures that the tested Landlock ABI is the latest known version supported by the running
187    // kernel.  If this test failed, you need set the LANDLOCK_CRATE_TEST_ABI environment variable
188    // to the Landlock ABI version supported by your kernel.  With a missing variable, the latest
189    // Landlock ABI version known by this crate is automatically set.
190    // From Linux 5.13 to 5.18, you need to run: LANDLOCK_CRATE_TEST_ABI=1 cargo test
191    assert_eq!(*TEST_ABI, ABI::new_current());
192}
193
194// CompatState is not public outside this crate.
195/// Returned by ruleset builder.
196#[cfg_attr(test, derive(Debug))]
197#[derive(Copy, Clone, PartialEq, Eq)]
198pub enum CompatState {
199    /// Initial undefined state.
200    Init,
201    /// All requested restrictions are enforced.
202    Full,
203    /// Some requested restrictions are enforced, following a best-effort approach.
204    Partial,
205    /// The running system doesn't support Landlock.
206    No,
207    /// Final unsupported state.
208    Dummy,
209}
210
211impl CompatState {
212    fn update(&mut self, other: Self) {
213        *self = match (*self, other) {
214            (CompatState::Init, other) => other,
215            (CompatState::Dummy, _) => CompatState::Dummy,
216            (_, CompatState::Dummy) => CompatState::Dummy,
217            (CompatState::No, CompatState::No) => CompatState::No,
218            (CompatState::Full, CompatState::Full) => CompatState::Full,
219            (_, _) => CompatState::Partial,
220        }
221    }
222}
223
224#[test]
225fn compat_state_update_1() {
226    let mut state = CompatState::Full;
227
228    state.update(CompatState::Full);
229    assert_eq!(state, CompatState::Full);
230
231    state.update(CompatState::No);
232    assert_eq!(state, CompatState::Partial);
233
234    state.update(CompatState::Full);
235    assert_eq!(state, CompatState::Partial);
236
237    state.update(CompatState::Full);
238    assert_eq!(state, CompatState::Partial);
239
240    state.update(CompatState::No);
241    assert_eq!(state, CompatState::Partial);
242
243    state.update(CompatState::Dummy);
244    assert_eq!(state, CompatState::Dummy);
245
246    state.update(CompatState::Full);
247    assert_eq!(state, CompatState::Dummy);
248}
249
250#[test]
251fn compat_state_update_2() {
252    let mut state = CompatState::Full;
253
254    state.update(CompatState::Full);
255    assert_eq!(state, CompatState::Full);
256
257    state.update(CompatState::No);
258    assert_eq!(state, CompatState::Partial);
259
260    state.update(CompatState::Full);
261    assert_eq!(state, CompatState::Partial);
262}
263
264#[cfg_attr(test, derive(Debug, PartialEq))]
265#[derive(Copy, Clone)]
266pub(crate) struct Compatibility {
267    abi: ABI,
268    pub(crate) level: Option<CompatLevel>,
269    pub(crate) state: CompatState,
270}
271
272impl From<ABI> for Compatibility {
273    fn from(abi: ABI) -> Self {
274        Compatibility {
275            abi,
276            level: Default::default(),
277            state: CompatState::Init,
278        }
279    }
280}
281
282impl Compatibility {
283    // Compatibility is a semi-opaque struct.
284    #[allow(clippy::new_without_default)]
285    pub(crate) fn new() -> Self {
286        ABI::new_current().into()
287    }
288
289    pub(crate) fn update(&mut self, state: CompatState) {
290        self.state.update(state);
291    }
292
293    pub(crate) fn abi(&self) -> ABI {
294        self.abi
295    }
296}
297
298pub(crate) mod private {
299    use crate::CompatLevel;
300
301    pub trait OptionCompatLevelMut {
302        fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel>;
303    }
304}
305
306/// Properly handles runtime unsupported features.
307///
308/// This guarantees consistent behaviors across crate users
309/// and runtime kernels even if this crate get new features.
310/// It eases backward compatibility and enables future-proofness.
311///
312/// Landlock is a security feature designed to help improve security of a running system
313/// thanks to application developers.
314/// To protect users as much as possible,
315/// compatibility with the running system should then be handled in a best-effort way,
316/// contrary to common system features.
317/// In some circumstances
318/// (e.g. applications carefully designed to only be run with a specific set of kernel features),
319/// it may be required to error out if some of these features are not available
320/// and will then not be enforced.
321pub trait Compatible: Sized + private::OptionCompatLevelMut {
322    /// To enable a best-effort security approach,
323    /// Landlock features that are not supported by the running system
324    /// are silently ignored by default,
325    /// which is a sane choice for most use cases.
326    /// However, on some rare circumstances,
327    /// developers may want to have some guarantees that their applications
328    /// will not run if a certain level of sandboxing is not possible.
329    /// If we really want to error out when not all our requested requirements are met,
330    /// then we can configure it with `set_compatibility()`.
331    ///
332    /// The `Compatible` trait is implemented for all object builders
333    /// (e.g. [`Ruleset`](crate::Ruleset)).
334    /// Such builders have a set of methods to incrementally build an object.
335    /// These build methods rely on kernel features that may not be available at runtime.
336    /// The `set_compatibility()` method enables to control the effect of
337    /// the following build method calls starting after the `set_compatibility()` call.
338    /// Such effect can be:
339    /// * to silently ignore unsupported features
340    ///   and continue building ([`CompatLevel::BestEffort`]);
341    /// * to silently ignore unsupported features
342    ///   and ignore the whole build ([`CompatLevel::SoftRequirement`]);
343    /// * to return an error for any unsupported feature ([`CompatLevel::HardRequirement`]).
344    ///
345    /// Taking [`Ruleset`](crate::Ruleset) as an example,
346    /// the [`handle_access()`](crate::RulesetAttr::handle_access()) build method
347    /// returns a [`Result`] that can be [`Err(RulesetError)`](crate::RulesetError)
348    /// with a nested [`CompatError`].
349    /// Such error can only occur with a running Linux kernel not supporting the requested
350    /// Landlock accesses *and* if the current compatibility level is
351    /// [`CompatLevel::HardRequirement`].
352    /// However, such error is not possible with [`CompatLevel::BestEffort`]
353    /// nor [`CompatLevel::SoftRequirement`].
354    ///
355    /// The order of this call is important because
356    /// it defines the behavior of the following build method calls that return a [`Result`].
357    /// If `set_compatibility(CompatLevel::HardRequirement)` is called on an object,
358    /// then a [`CompatError`] may be returned for the next method calls,
359    /// until the next call to `set_compatibility()`.
360    /// This enables to change the behavior of a set of build method calls,
361    /// for instance to be sure that the sandbox will at least restrict some access rights.
362    ///
363    /// New objects inherit the compatibility configuration of their parents, if any.
364    /// For instance, [`Ruleset::create()`](crate::Ruleset::create()) returns
365    /// a [`RulesetCreated`](crate::RulesetCreated) object that inherits the
366    /// `Ruleset`'s compatibility configuration.
367    ///
368    /// # Example with `SoftRequirement`
369    ///
370    /// Let's say an application legitimately needs to rename files between directories.
371    /// Because of [previous Landlock limitations](https://docs.kernel.org/userspace-api/landlock.html#file-renaming-and-linking-abi-2),
372    /// this was forbidden with the [first version of Landlock](ABI::V1),
373    /// but it is now handled starting with the [second version](ABI::V2).
374    /// For this use case, we only want the application to be sandboxed
375    /// if we have the guarantee that it will not break a legitimate usage (i.e. rename files).
376    /// We then create a ruleset which will either support file renaming
377    /// (thanks to [`AccessFs::Refer`](crate::AccessFs::Refer)) or silently do nothing.
378    ///
379    /// ```
380    /// use landlock::*;
381    ///
382    /// fn ruleset_handling_renames() -> Result<RulesetCreated, RulesetError> {
383    ///     Ok(Ruleset::default()
384    ///         // This ruleset must either handle the AccessFs::Refer right,
385    ///         // or it must silently ignore the whole sandboxing.
386    ///         .set_compatibility(CompatLevel::SoftRequirement)
387    ///         .handle_access(AccessFs::Refer)?
388    ///         // However, this ruleset may also handle other (future) access rights
389    ///         // if they are supported by the running kernel.
390    ///         .set_compatibility(CompatLevel::BestEffort)
391    ///         .handle_access(AccessFs::from_all(ABI::V5))?
392    ///         .create()?)
393    /// }
394    /// ```
395    ///
396    /// # Example with `HardRequirement`
397    ///
398    /// Security-dedicated applications may want to ensure that
399    /// an untrusted software component is subject to a minimum of restrictions before launching it.
400    /// In this case, we want to create a ruleset which will at least support
401    /// all restrictions provided by the [first version of Landlock](ABI::V1),
402    /// and opportunistically handle restrictions supported by newer kernels.
403    ///
404    /// ```
405    /// use landlock::*;
406    ///
407    /// fn ruleset_fragile() -> Result<RulesetCreated, RulesetError> {
408    ///     Ok(Ruleset::default()
409    ///         // This ruleset must either handle at least all accesses defined by
410    ///         // the first Landlock version (e.g. AccessFs::WriteFile),
411    ///         // or the following handle_access() call must return a wrapped
412    ///         // AccessError<AccessFs>::Incompatible error.
413    ///         .set_compatibility(CompatLevel::HardRequirement)
414    ///         .handle_access(AccessFs::from_all(ABI::V1))?
415    ///         // However, this ruleset may also handle new access rights
416    ///         // (e.g. AccessFs::Refer defined by the second version of Landlock)
417    ///         // if they are supported by the running kernel,
418    ///         // but without returning any error otherwise.
419    ///         .set_compatibility(CompatLevel::BestEffort)
420    ///         .handle_access(AccessFs::from_all(ABI::V5))?
421    ///         .create()?)
422    /// }
423    /// ```
424    fn set_compatibility(mut self, level: CompatLevel) -> Self {
425        *self.as_option_compat_level_mut() = Some(level);
426        self
427    }
428
429    /// Cf. [`set_compatibility()`](Compatible::set_compatibility()):
430    ///
431    /// - `set_best_effort(true)` translates to `set_compatibility(CompatLevel::BestEffort)`.
432    ///
433    /// - `set_best_effort(false)` translates to `set_compatibility(CompatLevel::HardRequirement)`.
434    #[deprecated(note = "Use set_compatibility() instead")]
435    fn set_best_effort(self, best_effort: bool) -> Self
436    where
437        Self: Sized,
438    {
439        self.set_compatibility(match best_effort {
440            true => CompatLevel::BestEffort,
441            false => CompatLevel::HardRequirement,
442        })
443    }
444}
445
446#[test]
447#[allow(deprecated)]
448fn deprecated_set_best_effort() {
449    use crate::{CompatLevel, Compatible, Ruleset};
450
451    assert_eq!(
452        Ruleset::default().set_best_effort(true).compat,
453        Ruleset::default()
454            .set_compatibility(CompatLevel::BestEffort)
455            .compat
456    );
457    assert_eq!(
458        Ruleset::default().set_best_effort(false).compat,
459        Ruleset::default()
460            .set_compatibility(CompatLevel::HardRequirement)
461            .compat
462    );
463}
464
465/// See the [`Compatible`] documentation.
466#[cfg_attr(test, derive(EnumIter))]
467#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
468pub enum CompatLevel {
469    /// Takes into account the build requests if they are supported by the running system,
470    /// or silently ignores them otherwise.
471    /// Never returns a compatibility error.
472    #[default]
473    BestEffort,
474    /// Takes into account the build requests if they are supported by the running system,
475    /// or silently ignores the whole build object otherwise.
476    /// Never returns a compatibility error.
477    /// If not supported,
478    /// the call to [`RulesetCreated::restrict_self()`](crate::RulesetCreated::restrict_self())
479    /// will return a
480    /// [`RestrictionStatus { ruleset: RulesetStatus::NotEnforced, no_new_privs: false, }`](crate::RestrictionStatus).
481    SoftRequirement,
482    /// Takes into account the build requests if they are supported by the running system,
483    /// or returns a compatibility error otherwise ([`CompatError`]).
484    HardRequirement,
485}
486
487impl From<Option<CompatLevel>> for CompatLevel {
488    fn from(opt: Option<CompatLevel>) -> Self {
489        match opt {
490            None => CompatLevel::default(),
491            Some(ref level) => *level,
492        }
493    }
494}
495
496// TailoredCompatLevel could be replaced with AsMut<Option<CompatLevel>>, but only traits defined
497// in the current crate can be implemented for types defined outside of the crate.  Furthermore it
498// provides a default implementation which is handy for types such as BitFlags.
499pub trait TailoredCompatLevel {
500    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
501    where
502        L: Into<CompatLevel>,
503    {
504        parent_level.into()
505    }
506}
507
508impl<T> TailoredCompatLevel for T
509where
510    Self: Compatible,
511{
512    // Every Compatible trait implementation returns its own compatibility level, if set.
513    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
514    where
515        L: Into<CompatLevel>,
516    {
517        // Using a mutable reference is not required but it makes the code simpler (no double AsRef
518        // implementations for each Compatible types), and more importantly it guarantees
519        // consistency with Compatible::set_compatibility().
520        match self.as_option_compat_level_mut() {
521            None => parent_level.into(),
522            // Returns the most constrained compatibility level.
523            Some(ref level) => parent_level.into().max(*level),
524        }
525    }
526}
527
528#[test]
529fn tailored_compat_level() {
530    use crate::{AccessFs, PathBeneath, PathFd};
531
532    fn new_path(level: CompatLevel) -> PathBeneath<PathFd> {
533        PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Execute).set_compatibility(level)
534    }
535
536    for parent_level in CompatLevel::iter() {
537        assert_eq!(
538            new_path(CompatLevel::BestEffort).tailored_compat_level(parent_level),
539            parent_level
540        );
541        assert_eq!(
542            new_path(CompatLevel::HardRequirement).tailored_compat_level(parent_level),
543            CompatLevel::HardRequirement
544        );
545    }
546
547    assert_eq!(
548        new_path(CompatLevel::SoftRequirement).tailored_compat_level(CompatLevel::SoftRequirement),
549        CompatLevel::SoftRequirement
550    );
551
552    for child_level in CompatLevel::iter() {
553        assert_eq!(
554            new_path(child_level).tailored_compat_level(CompatLevel::BestEffort),
555            child_level
556        );
557        assert_eq!(
558            new_path(child_level).tailored_compat_level(CompatLevel::HardRequirement),
559            CompatLevel::HardRequirement
560        );
561    }
562}
563
564// CompatResult is not public outside this crate.
565pub enum CompatResult<A>
566where
567    A: Access,
568{
569    // Fully matches the request.
570    Full,
571    // Partially matches the request.
572    Partial(CompatError<A>),
573    // Doesn't matches the request.
574    No(CompatError<A>),
575}
576
577// TryCompat is not public outside this crate.
578pub trait TryCompat<A>
579where
580    Self: Sized + TailoredCompatLevel,
581    A: Access,
582{
583    fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>>;
584
585    // Default implementation for objects without children.
586    //
587    // If returning something other than Ok(Some(self)), the implementation must use its own
588    // compatibility level, if any, with self.tailored_compat_level(default_compat_level), and pass
589    // it with the abi and compat_state to each child.try_compat().  See PathBeneath implementation
590    // and the self.allowed_access.try_compat() call.
591    //
592    // # Warning
593    //
594    // Errors must be prioritized over incompatibility (i.e. return Err(e) over Ok(None)) for all
595    // children.
596    fn try_compat_children<L>(
597        self,
598        _abi: ABI,
599        _parent_level: L,
600        _compat_state: &mut CompatState,
601    ) -> Result<Option<Self>, CompatError<A>>
602    where
603        L: Into<CompatLevel>,
604    {
605        Ok(Some(self))
606    }
607
608    // Update compat_state and return an error according to try_compat_*() error, or to the
609    // compatibility level, i.e. either route compatible object or error.
610    fn try_compat<L>(
611        mut self,
612        abi: ABI,
613        parent_level: L,
614        compat_state: &mut CompatState,
615    ) -> Result<Option<Self>, CompatError<A>>
616    where
617        L: Into<CompatLevel>,
618    {
619        let compat_level = self.tailored_compat_level(parent_level);
620        let some_inner = match self.try_compat_inner(abi) {
621            Ok(CompatResult::Full) => {
622                compat_state.update(CompatState::Full);
623                true
624            }
625            Ok(CompatResult::Partial(error)) => match compat_level {
626                CompatLevel::BestEffort => {
627                    compat_state.update(CompatState::Partial);
628                    true
629                }
630                CompatLevel::SoftRequirement => {
631                    compat_state.update(CompatState::Dummy);
632                    false
633                }
634                CompatLevel::HardRequirement => {
635                    compat_state.update(CompatState::Dummy);
636                    return Err(error);
637                }
638            },
639            Ok(CompatResult::No(error)) => match compat_level {
640                CompatLevel::BestEffort => {
641                    compat_state.update(CompatState::No);
642                    false
643                }
644                CompatLevel::SoftRequirement => {
645                    compat_state.update(CompatState::Dummy);
646                    false
647                }
648                CompatLevel::HardRequirement => {
649                    compat_state.update(CompatState::Dummy);
650                    return Err(error);
651                }
652            },
653            Err(error) => {
654                // Safeguard to help for test consistency.
655                compat_state.update(CompatState::Dummy);
656                return Err(error);
657            }
658        };
659
660        // At this point, any inner error have been returned, so we can proceed with
661        // try_compat_children()?.
662        match self.try_compat_children(abi, compat_level, compat_state)? {
663            Some(n) if some_inner => Ok(Some(n)),
664            _ => Ok(None),
665        }
666    }
667}