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