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