Coverage Report

Created: 2026-06-30 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/landlock-0.4.5/src/compat.rs
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3
use crate::{uapi, Access, CompatError};
4
use std::fmt::{self, Display, Formatter};
5
use std::io::Error;
6
7
#[cfg(test)]
8
use std::convert::TryInto;
9
#[cfg(test)]
10
use strum::{EnumCount, IntoEnumIterator};
11
#[cfg(test)]
12
use 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]
51
pub 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.
81
impl 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.
91
impl From<i32> for ABI {
92
0
    fn from(value: i32) -> ABI {
93
0
        match value {
94
0
            n if n <= 0 => ABI::Unsupported,
95
0
            1 => ABI::V1,
96
0
            2 => ABI::V2,
97
0
            3 => ABI::V3,
98
0
            4 => ABI::V4,
99
0
            5 => ABI::V5,
100
0
            6 => ABI::V6,
101
            // Returns the greatest known ABI.
102
0
            _ => ABI::V7,
103
        }
104
0
    }
105
}
106
107
#[test]
108
fn 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]
127
fn 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
140
impl Display for ABI {
141
0
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
142
0
        match self {
143
0
            ABI::Unsupported => write!(f, "unsupported"),
144
0
            v => (*v as u32).fmt(f),
145
        }
146
0
    }
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)]
160
pub 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
185
impl 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
0
    fn current() -> Self {
192
        // Landlock ABI version starts at 1 but errno is only set for negative values.
193
0
        let v = unsafe {
194
0
            uapi::landlock_create_ruleset(
195
0
                std::ptr::null(),
196
                0,
197
                uapi::LANDLOCK_CREATE_RULESET_VERSION,
198
            )
199
        };
200
0
        if v < 0 {
201
            // The only possible error values should be EOPNOTSUPP and ENOSYS.
202
0
            match Error::last_os_error().raw_os_error() {
203
0
                Some(libc::EOPNOTSUPP) => Self::NotEnabled,
204
0
                _ => Self::NotImplemented,
205
            }
206
        } else {
207
0
            let abi = ABI::from(v);
208
0
            Self::Available {
209
0
                effective_abi: abi,
210
0
                kernel_abi: (v != abi as i32).then_some(v),
211
0
            }
212
        }
213
0
    }
214
}
215
216
// Test against the running kernel.
217
#[test]
218
fn 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
239
impl From<LandlockStatus> for ABI {
240
0
    fn from(status: LandlockStatus) -> Self {
241
0
        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
0
            LandlockStatus::NotEnabled | LandlockStatus::NotImplemented => ABI::Unsupported,
245
0
            LandlockStatus::Available { effective_abi, .. } => effective_abi,
246
        }
247
0
    }
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)]
253
impl 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)]
267
pub(crate) static TEST_ABI_ENV_NAME: &str = "LANDLOCK_CRATE_TEST_ABI";
268
269
#[cfg(test)]
270
lazy_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)]
286
pub(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)]
297
pub(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]
317
fn 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)]
338
pub 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
351
impl CompatState {
352
0
    fn update(&mut self, other: Self) {
353
0
        *self = match (*self, other) {
354
0
            (CompatState::Init, other) => other,
355
0
            (CompatState::Dummy, _) => CompatState::Dummy,
356
0
            (_, CompatState::Dummy) => CompatState::Dummy,
357
0
            (CompatState::No, CompatState::No) => CompatState::No,
358
0
            (CompatState::Full, CompatState::Full) => CompatState::Full,
359
0
            (_, _) => CompatState::Partial,
360
        }
361
0
    }
362
}
363
364
#[test]
365
fn 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]
391
fn 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]
405
fn 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)]
430
pub(crate) struct Compatibility {
431
    status: LandlockStatus,
432
    pub(crate) level: Option<CompatLevel>,
433
    pub(crate) state: CompatState,
434
}
435
436
impl From<LandlockStatus> for Compatibility {
437
0
    fn from(status: LandlockStatus) -> Self {
438
0
        Compatibility {
439
0
            status,
440
0
            level: Default::default(),
441
0
            state: CompatState::Init,
442
0
        }
443
0
    }
444
}
445
446
#[cfg(test)]
447
impl From<ABI> for Compatibility {
448
    fn from(abi: ABI) -> Self {
449
        Self::from(LandlockStatus::from(abi))
450
    }
451
}
452
453
impl Compatibility {
454
    // Compatibility is a semi-opaque struct.
455
    #[allow(clippy::new_without_default)]
456
0
    pub(crate) fn new() -> Self {
457
0
        LandlockStatus::current().into()
458
0
    }
459
460
0
    pub(crate) fn update(&mut self, state: CompatState) {
461
0
        self.state.update(state);
462
0
    }
463
464
0
    pub(crate) fn abi(&self) -> ABI {
465
0
        self.status.into()
466
0
    }
467
468
0
    pub(crate) fn status(&self) -> LandlockStatus {
469
0
        self.status
470
0
    }
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
0
    pub(crate) fn try_compat_binary<E, F>(
485
0
        &mut self,
486
0
        supported: bool,
487
0
        make_error: F,
488
0
    ) -> Result<bool, E>
489
0
    where
490
0
        F: FnOnce() -> E,
491
    {
492
0
        if supported {
493
0
            self.state.update(CompatState::Full);
494
0
            Ok(true)
495
        } else {
496
0
            match self.level.into() {
497
                CompatLevel::BestEffort => {
498
0
                    self.state.update(CompatState::No);
499
0
                    Ok(false)
500
                }
501
                CompatLevel::SoftRequirement => {
502
0
                    self.state.update(CompatState::Dummy);
503
0
                    Ok(false)
504
                }
505
0
                CompatLevel::HardRequirement => Err(make_error()),
506
            }
507
        }
508
0
    }
509
}
510
511
pub(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.
534
pub 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
0
    fn set_compatibility(mut self, level: CompatLevel) -> Self {
638
0
        *self.as_option_compat_level_mut() = Some(level);
639
0
        self
640
0
    }
Unexecuted instantiation: <landlock::ruleset::Ruleset as landlock::compat::Compatible>::set_compatibility
Unexecuted instantiation: <_ as landlock::compat::Compatible>::set_compatibility
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
0
    fn set_best_effort(self, best_effort: bool) -> Self
649
0
    where
650
0
        Self: Sized,
651
    {
652
0
        self.set_compatibility(match best_effort {
653
0
            true => CompatLevel::BestEffort,
654
0
            false => CompatLevel::HardRequirement,
655
        })
656
0
    }
657
}
658
659
#[test]
660
#[allow(deprecated)]
661
fn 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)]
681
pub 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
700
impl From<Option<CompatLevel>> for CompatLevel {
701
0
    fn from(opt: Option<CompatLevel>) -> Self {
702
0
        match opt {
703
0
            None => CompatLevel::default(),
704
0
            Some(ref level) => *level,
705
        }
706
0
    }
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.
712
pub trait TailoredCompatLevel {
713
0
    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
714
0
    where
715
0
        L: Into<CompatLevel>,
716
    {
717
0
        parent_level.into()
718
0
    }
Unexecuted instantiation: <enumflags2::BitFlags<landlock::fs::AccessFs, u64> as landlock::compat::TailoredCompatLevel>::tailored_compat_level::<landlock::compat::CompatLevel>
Unexecuted instantiation: <enumflags2::BitFlags<landlock::fs::AccessFs, u64> as landlock::compat::TailoredCompatLevel>::tailored_compat_level::<core::option::Option<landlock::compat::CompatLevel>>
Unexecuted instantiation: <enumflags2::BitFlags<landlock::net::AccessNet, u64> as landlock::compat::TailoredCompatLevel>::tailored_compat_level::<core::option::Option<landlock::compat::CompatLevel>>
719
}
720
721
impl<T> TailoredCompatLevel for T
722
where
723
    Self: Compatible,
724
{
725
    // Every Compatible trait implementation returns its own compatibility level, if set.
726
0
    fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
727
0
    where
728
0
        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
0
        match self.as_option_compat_level_mut() {
734
0
            None => parent_level.into(),
735
            // Returns the most constrained compatibility level.
736
0
            Some(ref level) => parent_level.into().max(*level),
737
        }
738
0
    }
Unexecuted instantiation: <landlock::fs::PathBeneath<landlock::fs::PathFd> as landlock::compat::TailoredCompatLevel>::tailored_compat_level::<core::option::Option<landlock::compat::CompatLevel>>
Unexecuted instantiation: <landlock::fs::PathBeneath<landlock::fs::PathFd> as landlock::compat::TailoredCompatLevel>::tailored_compat_level::<landlock::compat::CompatLevel>
Unexecuted instantiation: <_ as landlock::compat::TailoredCompatLevel>::tailored_compat_level::<_>
739
}
740
741
#[test]
742
fn 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.
778
pub enum CompatResult<A>
779
where
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.
791
pub trait TryCompat<A>
792
where
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
0
    fn try_compat_children<L>(
810
0
        self,
811
0
        _abi: ABI,
812
0
        _parent_level: L,
813
0
        _compat_state: &mut CompatState,
814
0
    ) -> Result<Option<Self>, CompatError<A>>
815
0
    where
816
0
        L: Into<CompatLevel>,
817
    {
818
0
        Ok(Some(self))
819
0
    }
Unexecuted instantiation: <enumflags2::BitFlags<landlock::fs::AccessFs, u64> as landlock::compat::TryCompat<landlock::fs::AccessFs>>::try_compat_children::<landlock::compat::CompatLevel>
Unexecuted instantiation: <enumflags2::BitFlags<landlock::net::AccessNet, u64> as landlock::compat::TryCompat<landlock::net::AccessNet>>::try_compat_children::<landlock::compat::CompatLevel>
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
0
    fn try_compat<L>(
824
0
        mut self,
825
0
        abi: ABI,
826
0
        parent_level: L,
827
0
        compat_state: &mut CompatState,
828
0
    ) -> Result<Option<Self>, CompatError<A>>
829
0
    where
830
0
        L: Into<CompatLevel>,
831
    {
832
0
        let compat_level = self.tailored_compat_level(parent_level);
833
0
        let some_inner = match self.try_compat_inner(abi) {
834
            Ok(CompatResult::Full) => {
835
0
                compat_state.update(CompatState::Full);
836
0
                true
837
            }
838
0
            Ok(CompatResult::Partial(error)) => match compat_level {
839
                CompatLevel::BestEffort => {
840
0
                    compat_state.update(CompatState::Partial);
841
0
                    true
842
                }
843
                CompatLevel::SoftRequirement => {
844
0
                    compat_state.update(CompatState::Dummy);
845
0
                    false
846
                }
847
                CompatLevel::HardRequirement => {
848
0
                    compat_state.update(CompatState::Dummy);
849
0
                    return Err(error);
850
                }
851
            },
852
0
            Ok(CompatResult::No(error)) => match compat_level {
853
                CompatLevel::BestEffort => {
854
0
                    compat_state.update(CompatState::No);
855
0
                    false
856
                }
857
                CompatLevel::SoftRequirement => {
858
0
                    compat_state.update(CompatState::Dummy);
859
0
                    false
860
                }
861
                CompatLevel::HardRequirement => {
862
0
                    compat_state.update(CompatState::Dummy);
863
0
                    return Err(error);
864
                }
865
            },
866
0
            Err(error) => {
867
                // Safeguard to help for test consistency.
868
0
                compat_state.update(CompatState::Dummy);
869
0
                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
0
        match self.try_compat_children(abi, compat_level, compat_state)? {
876
0
            Some(n) if some_inner => Ok(Some(n)),
877
0
            _ => Ok(None),
878
        }
879
0
    }
Unexecuted instantiation: <landlock::fs::PathBeneath<landlock::fs::PathFd> as landlock::compat::TryCompat<landlock::fs::AccessFs>>::try_compat::<core::option::Option<landlock::compat::CompatLevel>>
Unexecuted instantiation: <enumflags2::BitFlags<landlock::fs::AccessFs, u64> as landlock::compat::TryCompat<landlock::fs::AccessFs>>::try_compat::<landlock::compat::CompatLevel>
Unexecuted instantiation: <enumflags2::BitFlags<landlock::fs::AccessFs, u64> as landlock::compat::TryCompat<landlock::fs::AccessFs>>::try_compat::<core::option::Option<landlock::compat::CompatLevel>>
Unexecuted instantiation: <enumflags2::BitFlags<landlock::net::AccessNet, u64> as landlock::compat::TryCompat<landlock::net::AccessNet>>::try_compat::<core::option::Option<landlock::compat::CompatLevel>>
880
}