Coverage Report

Created: 2025-03-07 06:49

/rust/registry/src/index.crates.io-6f17d22bba15001f/landlock-0.4.1/src/ruleset.rs
Line
Count
Source (jump to first uncovered line)
1
use crate::compat::private::OptionCompatLevelMut;
2
use crate::{
3
    uapi, Access, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel,
4
    CompatState, Compatibility, Compatible, CreateRulesetError, RestrictSelfError, RulesetError,
5
    TryCompat,
6
};
7
use libc::close;
8
use std::io::Error;
9
use std::mem::size_of_val;
10
use std::os::unix::io::RawFd;
11
12
#[cfg(test)]
13
use crate::*;
14
15
// Public interface without methods and which is impossible to implement outside this crate.
16
pub trait Rule<T>: PrivateRule<T>
17
where
18
    T: Access,
19
{
20
}
21
22
// PrivateRule is not public outside this crate.
23
pub trait PrivateRule<T>
24
where
25
    Self: TryCompat<T> + Compatible,
26
    T: Access,
27
{
28
    const TYPE_ID: uapi::landlock_rule_type;
29
30
    /// Returns a raw pointer to the rule's inner attribute.
31
    ///
32
    /// The caller must ensure that the rule outlives the pointer this function returns, or else it
33
    /// will end up pointing to garbage.
34
    fn as_ptr(&mut self) -> *const libc::c_void;
35
36
    fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError>;
37
}
38
39
/// Enforcement status of a ruleset.
40
#[derive(Debug, PartialEq, Eq)]
41
pub enum RulesetStatus {
42
    /// All requested restrictions are enforced.
43
    FullyEnforced,
44
    /// Some requested restrictions are enforced,
45
    /// following a best-effort approach.
46
    PartiallyEnforced,
47
    /// The running system doesn't support Landlock
48
    /// or a subset of the requested Landlock features.
49
    NotEnforced,
50
}
51
52
impl From<CompatState> for RulesetStatus {
53
0
    fn from(state: CompatState) -> Self {
54
0
        match state {
55
0
            CompatState::Init | CompatState::No | CompatState::Dummy => RulesetStatus::NotEnforced,
56
0
            CompatState::Full => RulesetStatus::FullyEnforced,
57
0
            CompatState::Partial => RulesetStatus::PartiallyEnforced,
58
        }
59
0
    }
60
}
61
62
// The Debug, PartialEq and Eq implementations are useful for crate users to debug and check the
63
// result of a Landlock ruleset enforcement.
64
/// Status of a [`RulesetCreated`]
65
/// after calling [`restrict_self()`](RulesetCreated::restrict_self).
66
#[derive(Debug, PartialEq, Eq)]
67
#[non_exhaustive]
68
pub struct RestrictionStatus {
69
    /// Status of the Landlock ruleset enforcement.
70
    pub ruleset: RulesetStatus,
71
    /// Status of `prctl(2)`'s `PR_SET_NO_NEW_PRIVS` enforcement.
72
    pub no_new_privs: bool,
73
}
74
75
0
fn prctl_set_no_new_privs() -> Result<(), Error> {
76
0
    match unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } {
77
0
        0 => Ok(()),
78
0
        _ => Err(Error::last_os_error()),
79
    }
80
0
}
81
82
0
fn support_no_new_privs() -> bool {
83
    // Only Linux < 3.5 or kernel with seccomp filters should return an error.
84
0
    matches!(
85
0
        unsafe { libc::prctl(libc::PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) },
86
        0 | 1
87
    )
88
0
}
89
90
/// Landlock ruleset builder.
91
///
92
/// `Ruleset` enables to create a Landlock ruleset in a flexible way
93
/// following the builder pattern.
94
/// Most build steps return a [`Result`] with [`RulesetError`].
95
///
96
/// You should probably not create more than one ruleset per application.
97
/// Creating multiple rulesets is only useful when gradually restricting an application
98
/// (e.g., a first set of generic restrictions before reading any file,
99
/// then a second set of tailored restrictions after reading the configuration).
100
///
101
/// # Simple example
102
///
103
/// Simple helper handling only Landlock-related errors.
104
///
105
/// ```
106
/// use landlock::{
107
///     Access, AccessFs, PathBeneath, PathFd, RestrictionStatus, Ruleset, RulesetAttr,
108
///     RulesetCreatedAttr, RulesetError, ABI,
109
/// };
110
/// use std::os::unix::io::AsFd;
111
///
112
/// fn restrict_fd<T>(hierarchy: T) -> Result<RestrictionStatus, RulesetError>
113
/// where
114
///     T: AsFd,
115
/// {
116
///     // The Landlock ABI should be incremented (and tested) regularly.
117
///     let abi = ABI::V1;
118
///     let access_all = AccessFs::from_all(abi);
119
///     let access_read = AccessFs::from_read(abi);
120
///     Ok(Ruleset::default()
121
///         .handle_access(access_all)?
122
///         .create()?
123
///         .add_rule(PathBeneath::new(hierarchy, access_read))?
124
///         .restrict_self()?)
125
/// }
126
///
127
/// let fd = PathFd::new("/home").expect("failed to open /home");
128
/// let status = restrict_fd(fd).expect("failed to build the ruleset");
129
/// ```
130
///
131
/// # Generic example
132
///
133
/// More generic helper handling a set of file hierarchies
134
/// and multiple types of error (i.e. [`RulesetError`](crate::RulesetError)
135
/// and [`PathFdError`](crate::PathFdError).
136
///
137
/// ```
138
/// use landlock::{
139
///     Access, AccessFs, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
140
///     RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
141
/// };
142
/// use thiserror::Error;
143
///
144
/// #[derive(Debug, Error)]
145
/// enum MyRestrictError {
146
///     #[error(transparent)]
147
///     Ruleset(#[from] RulesetError),
148
///     #[error(transparent)]
149
///     AddRule(#[from] PathFdError),
150
/// }
151
///
152
/// fn restrict_paths(hierarchies: &[&str]) -> Result<RestrictionStatus, MyRestrictError> {
153
///     // The Landlock ABI should be incremented (and tested) regularly.
154
///     let abi = ABI::V1;
155
///     let access_all = AccessFs::from_all(abi);
156
///     let access_read = AccessFs::from_read(abi);
157
///     Ok(Ruleset::default()
158
///         .handle_access(access_all)?
159
///         .create()?
160
///         .add_rules(
161
///             hierarchies
162
///                 .iter()
163
///                 .map::<Result<_, MyRestrictError>, _>(|p| {
164
///                     Ok(PathBeneath::new(PathFd::new(p)?, access_read))
165
///                 }),
166
///         )?
167
///         .restrict_self()?)
168
/// }
169
///
170
/// let status = restrict_paths(&["/usr", "/home"]).expect("failed to build the ruleset");
171
/// ```
172
#[cfg_attr(test, derive(Debug))]
173
pub struct Ruleset {
174
    pub(crate) requested_handled_fs: BitFlags<AccessFs>,
175
    pub(crate) requested_handled_net: BitFlags<AccessNet>,
176
    pub(crate) actual_handled_fs: BitFlags<AccessFs>,
177
    pub(crate) actual_handled_net: BitFlags<AccessNet>,
178
    pub(crate) compat: Compatibility,
179
}
180
181
impl From<Compatibility> for Ruleset {
182
0
    fn from(compat: Compatibility) -> Self {
183
0
        Ruleset {
184
0
            // Non-working default handled FS accesses to force users to set them explicitely.
185
0
            requested_handled_fs: Default::default(),
186
0
            requested_handled_net: Default::default(),
187
0
            actual_handled_fs: Default::default(),
188
0
            actual_handled_net: Default::default(),
189
0
            compat,
190
0
        }
191
0
    }
192
}
193
194
#[cfg(test)]
195
impl From<ABI> for Ruleset {
196
    fn from(abi: ABI) -> Self {
197
        Ruleset::from(Compatibility::from(abi))
198
    }
199
}
200
201
#[test]
202
fn ruleset_add_rule_iter() {
203
    assert!(matches!(
204
        Ruleset::from(ABI::Unsupported)
205
            .handle_access(AccessFs::Execute)
206
            .unwrap()
207
            .create()
208
            .unwrap()
209
            .add_rule(PathBeneath::new(
210
                PathFd::new("/").unwrap(),
211
                AccessFs::ReadFile
212
            ))
213
            .unwrap_err(),
214
        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
215
    ));
216
}
217
218
impl Default for Ruleset {
219
    /// Returns a new `Ruleset`.
220
    /// This call automatically probes the running kernel to know if it supports Landlock.
221
    ///
222
    /// To be able to successfully call [`create()`](Ruleset::create),
223
    /// it is required to set the handled accesses with
224
    /// [`handle_access()`](Ruleset::handle_access).
225
0
    fn default() -> Self {
226
0
        // The API should be future-proof: one Rust program or library should have the same
227
0
        // behavior if built with an old or a newer crate (e.g. with an extended ruleset_attr
228
0
        // enum).  It should then not be possible to give an "all-possible-handled-accesses" to the
229
0
        // Ruleset builder because this value would be relative to the running kernel.
230
0
        Compatibility::new().into()
231
0
    }
232
}
233
234
impl Ruleset {
235
    #[allow(clippy::new_without_default)]
236
    #[deprecated(note = "Use Ruleset::default() instead")]
237
0
    pub fn new() -> Self {
238
0
        Ruleset::default()
239
0
    }
240
241
    /// Attempts to create a real Landlock ruleset (if supported by the running kernel).
242
    /// The returned [`RulesetCreated`] is also a builder.
243
    ///
244
    /// On error, returns a wrapped [`CreateRulesetError`].
245
0
    pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
246
0
        let body = || -> Result<RulesetCreated, CreateRulesetError> {
247
0
            match self.compat.state {
248
                CompatState::Init => {
249
                    // Checks that there is at least one requested access (e.g.
250
                    // requested_handled_fs): one call to handle_access().
251
0
                    Err(CreateRulesetError::MissingHandledAccess)
252
                }
253
                CompatState::No | CompatState::Dummy => {
254
                    // There is at least one requested access.
255
                    #[cfg(test)]
256
                    assert!(
257
                        !self.requested_handled_fs.is_empty()
258
                            || !self.requested_handled_net.is_empty()
259
                    );
260
261
                    // CompatState::No should be handled as CompatState::Dummy because it is not
262
                    // possible to create an actual ruleset.
263
0
                    self.compat.update(CompatState::Dummy);
264
0
                    match self.compat.level.into() {
265
                        CompatLevel::HardRequirement => {
266
0
                            Err(CreateRulesetError::MissingHandledAccess)
267
                        }
268
0
                        _ => Ok(RulesetCreated::new(self, -1)),
269
                    }
270
                }
271
                CompatState::Full | CompatState::Partial => {
272
                    // There is at least one actual handled access.
273
                    #[cfg(test)]
274
                    assert!(
275
                        !self.actual_handled_fs.is_empty() || !self.actual_handled_net.is_empty()
276
                    );
277
278
0
                    let attr = uapi::landlock_ruleset_attr {
279
0
                        handled_access_fs: self.actual_handled_fs.bits(),
280
0
                        handled_access_net: self.actual_handled_net.bits(),
281
0
                    };
282
0
                    match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {
283
0
                        fd if fd >= 0 => Ok(RulesetCreated::new(self, fd)),
284
0
                        _ => Err(CreateRulesetError::CreateRulesetCall {
285
0
                            source: Error::last_os_error(),
286
0
                        }),
287
                    }
288
                }
289
            }
290
0
        };
291
0
        Ok(body()?)
292
0
    }
293
}
294
295
impl OptionCompatLevelMut for Ruleset {
296
0
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
297
0
        &mut self.compat.level
298
0
    }
299
}
300
301
impl OptionCompatLevelMut for &mut Ruleset {
302
0
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
303
0
        &mut self.compat.level
304
0
    }
305
}
306
307
impl Compatible for Ruleset {}
308
309
impl Compatible for &mut Ruleset {}
310
311
impl AsMut<Ruleset> for Ruleset {
312
0
    fn as_mut(&mut self) -> &mut Ruleset {
313
0
        self
314
0
    }
315
}
316
317
// Tests unambiguous type.
318
#[test]
319
fn ruleset_as_mut() {
320
    let mut ruleset = Ruleset::from(ABI::Unsupported);
321
    let _ = ruleset.as_mut();
322
323
    let mut ruleset_created = Ruleset::from(ABI::Unsupported)
324
        .handle_access(AccessFs::Execute)
325
        .unwrap()
326
        .create()
327
        .unwrap();
328
    let _ = ruleset_created.as_mut();
329
}
330
331
pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
332
    /// Attempts to add a set of access rights that will be supported by this ruleset.
333
    /// By default, all actions requiring these access rights will be denied.
334
    /// Consecutive calls to `handle_access()` will be interpreted as logical ORs
335
    /// with the previous handled accesses.
336
    ///
337
    /// On error, returns a wrapped [`HandleAccessesError`](crate::HandleAccessesError).
338
    /// E.g., `RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError<AccessFs>))`
339
0
    fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
340
0
    where
341
0
        T: Into<BitFlags<U>>,
342
0
        U: Access,
343
0
    {
344
0
        U::ruleset_handle_access(self.as_mut(), access.into())?;
345
0
        Ok(self)
346
0
    }
Unexecuted instantiation: <landlock::ruleset::Ruleset as landlock::ruleset::RulesetAttr>::handle_access::<enumflags2::BitFlags<landlock::fs::AccessFs, u64>, landlock::fs::AccessFs>
Unexecuted instantiation: <_ as landlock::ruleset::RulesetAttr>::handle_access::<_, _>
347
}
348
349
impl RulesetAttr for Ruleset {}
350
351
impl RulesetAttr for &mut Ruleset {}
352
353
#[test]
354
fn ruleset_attr() {
355
    let mut ruleset = Ruleset::from(ABI::Unsupported);
356
    let ruleset_ref = &mut ruleset;
357
358
    // Can pass this reference to prepare the ruleset...
359
    ruleset_ref
360
        .set_compatibility(CompatLevel::BestEffort)
361
        .handle_access(AccessFs::Execute)
362
        .unwrap()
363
        .handle_access(AccessFs::ReadFile)
364
        .unwrap();
365
366
    // ...and finally create the ruleset (thanks to non-lexical lifetimes).
367
    ruleset
368
        .set_compatibility(CompatLevel::BestEffort)
369
        .handle_access(AccessFs::Execute)
370
        .unwrap()
371
        .handle_access(AccessFs::WriteFile)
372
        .unwrap()
373
        .create()
374
        .unwrap();
375
}
376
377
#[test]
378
fn ruleset_created_handle_access_fs() {
379
    // Tests AccessFs::ruleset_handle_access()
380
    let ruleset = Ruleset::from(ABI::V1)
381
        .handle_access(AccessFs::Execute)
382
        .unwrap()
383
        .handle_access(AccessFs::ReadDir)
384
        .unwrap();
385
    let access = make_bitflags!(AccessFs::{Execute | ReadDir});
386
    assert_eq!(ruleset.requested_handled_fs, access);
387
    assert_eq!(ruleset.actual_handled_fs, access);
388
389
    // Tests that only the required handled accesses are reported as incompatible:
390
    // access should not contains AccessFs::Execute.
391
    assert!(matches!(Ruleset::from(ABI::Unsupported)
392
        .handle_access(AccessFs::Execute)
393
        .unwrap()
394
        .set_compatibility(CompatLevel::HardRequirement)
395
        .handle_access(AccessFs::ReadDir)
396
        .unwrap_err(),
397
        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
398
            CompatError::Access(AccessError::Incompatible { access })
399
        ))) if access == AccessFs::ReadDir
400
    ));
401
}
402
403
#[test]
404
fn ruleset_created_handle_access_net_tcp() {
405
    let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
406
407
    // Tests AccessNet::ruleset_handle_access() with ABI that doesn't support TCP rights.
408
    let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();
409
    assert_eq!(ruleset.requested_handled_net, access);
410
    assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);
411
412
    // Tests AccessNet::ruleset_handle_access() with ABI that supports TCP rights.
413
    let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
414
    assert_eq!(ruleset.requested_handled_net, access);
415
    assert_eq!(ruleset.actual_handled_net, access);
416
417
    // Tests that only the required handled accesses are reported as incompatible:
418
    // access should not contains AccessNet::BindTcp.
419
    assert!(matches!(Ruleset::from(ABI::Unsupported)
420
        .handle_access(AccessNet::BindTcp)
421
        .unwrap()
422
        .set_compatibility(CompatLevel::HardRequirement)
423
        .handle_access(AccessNet::ConnectTcp)
424
        .unwrap_err(),
425
        RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
426
            CompatError::Access(AccessError::Incompatible { access })
427
        ))) if access == AccessNet::ConnectTcp
428
    ));
429
}
430
431
impl OptionCompatLevelMut for RulesetCreated {
432
0
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
433
0
        &mut self.compat.level
434
0
    }
435
}
436
437
impl OptionCompatLevelMut for &mut RulesetCreated {
438
0
    fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
439
0
        &mut self.compat.level
440
0
    }
441
}
442
443
impl Compatible for RulesetCreated {}
444
445
impl Compatible for &mut RulesetCreated {}
446
447
pub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {
448
    /// Attempts to add a new rule to the ruleset.
449
    ///
450
    /// On error, returns a wrapped [`AddRulesError`].
451
0
    fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
452
0
    where
453
0
        T: Rule<U>,
454
0
        U: Access,
455
0
    {
456
0
        let body = || -> Result<Self, AddRulesError> {
457
0
            let self_ref = self.as_mut();
458
0
            rule.check_consistency(self_ref)?;
459
0
            let mut compat_rule = match rule
460
0
                .try_compat(
461
0
                    self_ref.compat.abi(),
462
0
                    self_ref.compat.level,
463
0
                    &mut self_ref.compat.state,
464
0
                )
465
0
                .map_err(AddRuleError::Compat)?
466
            {
467
0
                Some(r) => r,
468
0
                None => return Ok(self),
469
            };
470
0
            match self_ref.compat.state {
471
0
                CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
472
0
                CompatState::Full | CompatState::Partial => match unsafe {
473
0
                    uapi::landlock_add_rule(self_ref.fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
474
0
                } {
475
0
                    0 => Ok(self),
476
0
                    _ => Err(AddRuleError::<U>::AddRuleCall {
477
0
                        source: Error::last_os_error(),
478
0
                    }
479
0
                    .into()),
480
                },
481
            }
482
0
        };
Unexecuted instantiation: <&mut landlock::ruleset::RulesetCreated as landlock::ruleset::RulesetCreatedAttr>::add_rule::<landlock::fs::PathBeneath<landlock::fs::PathFd>, landlock::fs::AccessFs>::{closure#0}
Unexecuted instantiation: <_ as landlock::ruleset::RulesetCreatedAttr>::add_rule::<_, _>::{closure#0}
483
0
        Ok(body()?)
484
0
    }
Unexecuted instantiation: <&mut landlock::ruleset::RulesetCreated as landlock::ruleset::RulesetCreatedAttr>::add_rule::<landlock::fs::PathBeneath<landlock::fs::PathFd>, landlock::fs::AccessFs>
Unexecuted instantiation: <_ as landlock::ruleset::RulesetCreatedAttr>::add_rule::<_, _>
485
486
    /// Attempts to add a set of new rules to the ruleset.
487
    ///
488
    /// On error, returns a (double) wrapped [`AddRulesError`].
489
    ///
490
    /// # Example
491
    ///
492
    /// Create a custom iterator to read paths from environment variable.
493
    ///
494
    /// ```
495
    /// use landlock::{
496
    ///     Access, AccessFs, BitFlags, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
497
    ///     RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
498
    /// };
499
    /// use std::env;
500
    /// use std::ffi::OsStr;
501
    /// use std::os::unix::ffi::{OsStrExt, OsStringExt};
502
    /// use thiserror::Error;
503
    ///
504
    /// #[derive(Debug, Error)]
505
    /// enum PathEnvError<'a> {
506
    ///     #[error(transparent)]
507
    ///     Ruleset(#[from] RulesetError),
508
    ///     #[error(transparent)]
509
    ///     AddRuleIter(#[from] PathFdError),
510
    ///     #[error("missing environment variable {0}")]
511
    ///     MissingVar(&'a str),
512
    /// }
513
    ///
514
    /// struct PathEnv {
515
    ///     paths: Vec<u8>,
516
    ///     access: BitFlags<AccessFs>,
517
    /// }
518
    ///
519
    /// impl PathEnv {
520
    ///     // env_var is the name of an environment variable
521
    ///     // containing paths requested to be allowed.
522
    ///     // Paths are separated with ":", e.g. "/bin:/lib:/usr:/proc".
523
    ///     // In case an empty string is provided,
524
    ///     // no restrictions are applied.
525
    ///     // `access` is the set of access rights allowed for each of the parsed paths.
526
    ///     fn new<'a>(
527
    ///         env_var: &'a str, access: BitFlags<AccessFs>
528
    ///     ) -> Result<Self, PathEnvError<'a>> {
529
    ///         Ok(Self {
530
    ///             paths: env::var_os(env_var)
531
    ///                 .ok_or(PathEnvError::MissingVar(env_var))?
532
    ///                 .into_vec(),
533
    ///             access,
534
    ///         })
535
    ///     }
536
    ///
537
    ///     fn iter(
538
    ///         &self,
539
    ///     ) -> impl Iterator<Item = Result<PathBeneath<PathFd>, PathEnvError<'static>>> + '_ {
540
    ///         let is_empty = self.paths.is_empty();
541
    ///         self.paths
542
    ///             .split(|b| *b == b':')
543
    ///             // Skips the first empty element from of an empty string.
544
    ///             .skip_while(move |_| is_empty)
545
    ///             .map(OsStr::from_bytes)
546
    ///             .map(move |path|
547
    ///                 Ok(PathBeneath::new(PathFd::new(path)?, self.access)))
548
    ///     }
549
    /// }
550
    ///
551
    /// fn restrict_env() -> Result<RestrictionStatus, PathEnvError<'static>> {
552
    ///     Ok(Ruleset::default()
553
    ///         .handle_access(AccessFs::from_all(ABI::V1))?
554
    ///         .create()?
555
    ///         // In the shell: export EXECUTABLE_PATH="/usr:/bin:/sbin"
556
    ///         .add_rules(PathEnv::new("EXECUTABLE_PATH", AccessFs::Execute.into())?.iter())?
557
    ///         .restrict_self()?)
558
    /// }
559
    /// ```
560
0
    fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
561
0
    where
562
0
        I: IntoIterator<Item = Result<T, E>>,
563
0
        T: Rule<U>,
564
0
        U: Access,
565
0
        E: From<RulesetError>,
566
0
    {
567
0
        for rule in rules {
568
0
            self = self.add_rule(rule?)?;
569
        }
570
0
        Ok(self)
571
0
    }
Unexecuted instantiation: <&mut landlock::ruleset::RulesetCreated as landlock::ruleset::RulesetCreatedAttr>::add_rules::<core::iter::adapters::filter_map::FilterMap<alloc::vec::into_iter::IntoIter<std::path::PathBuf>, landlock::fs::path_beneath_rules<alloc::vec::Vec<std::path::PathBuf>, std::path::PathBuf, enumflags2::BitFlags<landlock::fs::AccessFs, u64>>::{closure#0}>, landlock::fs::PathBeneath<landlock::fs::PathFd>, landlock::fs::AccessFs, landlock::errors::RulesetError>
Unexecuted instantiation: <_ as landlock::ruleset::RulesetCreatedAttr>::add_rules::<_, _, _, _>
572
573
    /// Configures the ruleset to call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS` command
574
    /// in [`restrict_self()`](RulesetCreated::restrict_self).
575
    ///
576
    /// This `prctl(2)` call is never ignored, even if an error was encountered on a [`Ruleset`] or
577
    /// [`RulesetCreated`] method call while [`CompatLevel::SoftRequirement`] was set.
578
0
    fn set_no_new_privs(mut self, no_new_privs: bool) -> Self {
579
0
        <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = no_new_privs;
580
0
        self
581
0
    }
582
}
583
584
/// Ruleset created with [`Ruleset::create()`].
585
#[cfg_attr(test, derive(Debug))]
586
pub struct RulesetCreated {
587
    fd: RawFd,
588
    no_new_privs: bool,
589
    pub(crate) requested_handled_fs: BitFlags<AccessFs>,
590
    pub(crate) requested_handled_net: BitFlags<AccessNet>,
591
    compat: Compatibility,
592
}
593
594
impl RulesetCreated {
595
0
    pub(crate) fn new(ruleset: Ruleset, fd: RawFd) -> Self {
596
0
        // The compatibility state is initialized by Ruleset::create().
597
0
        #[cfg(test)]
598
0
        assert!(!matches!(ruleset.compat.state, CompatState::Init));
599
0
600
0
        RulesetCreated {
601
0
            fd,
602
0
            no_new_privs: true,
603
0
            requested_handled_fs: ruleset.requested_handled_fs,
604
0
            requested_handled_net: ruleset.requested_handled_net,
605
0
            compat: ruleset.compat,
606
0
        }
607
0
    }
608
609
    /// Attempts to restrict the calling thread with the ruleset
610
    /// according to the best-effort configuration
611
    /// (see [`RulesetCreated::set_compatibility()`] and [`CompatLevel::BestEffort`]).
612
    /// Call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS`
613
    /// according to the ruleset configuration.
614
    ///
615
    /// On error, returns a wrapped [`RestrictSelfError`].
616
0
    pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
617
0
        let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
618
            // Enforce no_new_privs even if something failed with SoftRequirement. The rationale is
619
            // that no_new_privs should not be an issue on its own if it is not explicitly
620
            // deactivated.
621
0
            let enforced_nnp = if self.no_new_privs {
622
0
                if let Err(e) = prctl_set_no_new_privs() {
623
0
                    match self.compat.level.into() {
624
0
                        CompatLevel::BestEffort => {}
625
0
                        CompatLevel::SoftRequirement => {
626
0
                            self.compat.update(CompatState::Dummy);
627
0
                        }
628
                        CompatLevel::HardRequirement => {
629
0
                            return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
630
                        }
631
                    }
632
                    // To get a consistent behavior, calls this prctl whether or not
633
                    // Landlock is supported by the running kernel.
634
0
                    let support_nnp = support_no_new_privs();
635
0
                    match self.compat.state {
636
                        // It should not be an error for kernel (older than 3.5) not supporting
637
                        // no_new_privs.
638
                        CompatState::Init | CompatState::No | CompatState::Dummy => {
639
0
                            if support_nnp {
640
                                // The kernel seems to be between 3.5 (included) and 5.13 (excluded),
641
                                // or Landlock is not enabled; no_new_privs should be supported anyway.
642
0
                                return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
643
0
                            }
644
                        }
645
                        // A kernel supporting Landlock should also support no_new_privs (unless
646
                        // filtered by seccomp).
647
                        CompatState::Full | CompatState::Partial => {
648
0
                            return Err(RestrictSelfError::SetNoNewPrivsCall { source: e })
649
                        }
650
                    }
651
0
                    false
652
                } else {
653
0
                    true
654
                }
655
            } else {
656
0
                false
657
            };
658
659
0
            match self.compat.state {
660
0
                CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
661
0
                    ruleset: self.compat.state.into(),
662
0
                    no_new_privs: enforced_nnp,
663
0
                }),
664
                CompatState::Full | CompatState::Partial => {
665
0
                    match unsafe { uapi::landlock_restrict_self(self.fd, 0) } {
666
                        0 => {
667
0
                            self.compat.update(CompatState::Full);
668
0
                            Ok(RestrictionStatus {
669
0
                                ruleset: self.compat.state.into(),
670
0
                                no_new_privs: enforced_nnp,
671
0
                            })
672
                        }
673
                        // TODO: match specific Landlock restrict self errors
674
0
                        _ => Err(RestrictSelfError::RestrictSelfCall {
675
0
                            source: Error::last_os_error(),
676
0
                        }),
677
                    }
678
                }
679
            }
680
0
        };
681
0
        Ok(body()?)
682
0
    }
683
684
    /// Creates a new `RulesetCreated` instance by duplicating the underlying file descriptor.
685
    /// Rule modification will affect both `RulesetCreated` instances simultaneously.
686
    ///
687
    /// On error, returns [`std::io::Error`].
688
0
    pub fn try_clone(&self) -> std::io::Result<Self> {
689
0
        Ok(RulesetCreated {
690
0
            fd: match self.fd {
691
0
                -1 => -1,
692
0
                self_fd => match unsafe { libc::fcntl(self_fd, libc::F_DUPFD_CLOEXEC, 0) } {
693
0
                    dup_fd if dup_fd >= 0 => dup_fd,
694
0
                    _ => return Err(Error::last_os_error()),
695
                },
696
            },
697
0
            no_new_privs: self.no_new_privs,
698
0
            requested_handled_fs: self.requested_handled_fs,
699
0
            requested_handled_net: self.requested_handled_net,
700
0
            compat: self.compat,
701
        })
702
0
    }
703
}
704
705
impl Drop for RulesetCreated {
706
0
    fn drop(&mut self) {
707
0
        if self.fd >= 0 {
708
0
            unsafe { close(self.fd) };
709
0
        }
710
0
    }
711
}
712
713
impl AsMut<RulesetCreated> for RulesetCreated {
714
0
    fn as_mut(&mut self) -> &mut RulesetCreated {
715
0
        self
716
0
    }
717
}
718
719
impl RulesetCreatedAttr for RulesetCreated {}
720
721
impl RulesetCreatedAttr for &mut RulesetCreated {}
722
723
#[test]
724
fn ruleset_created_attr() {
725
    let mut ruleset_created = Ruleset::from(ABI::Unsupported)
726
        .handle_access(AccessFs::Execute)
727
        .unwrap()
728
        .create()
729
        .unwrap();
730
    let ruleset_created_ref = &mut ruleset_created;
731
732
    // Can pass this reference to populate the ruleset...
733
    ruleset_created_ref
734
        .set_compatibility(CompatLevel::BestEffort)
735
        .add_rule(PathBeneath::new(
736
            PathFd::new("/usr").unwrap(),
737
            AccessFs::Execute,
738
        ))
739
        .unwrap()
740
        .add_rule(PathBeneath::new(
741
            PathFd::new("/etc").unwrap(),
742
            AccessFs::Execute,
743
        ))
744
        .unwrap();
745
746
    // ...and finally restrict with the last rules (thanks to non-lexical lifetimes).
747
    assert_eq!(
748
        ruleset_created
749
            .set_compatibility(CompatLevel::BestEffort)
750
            .add_rule(PathBeneath::new(
751
                PathFd::new("/tmp").unwrap(),
752
                AccessFs::Execute,
753
            ))
754
            .unwrap()
755
            .add_rule(PathBeneath::new(
756
                PathFd::new("/var").unwrap(),
757
                AccessFs::Execute,
758
            ))
759
            .unwrap()
760
            .restrict_self()
761
            .unwrap(),
762
        RestrictionStatus {
763
            ruleset: RulesetStatus::NotEnforced,
764
            no_new_privs: true,
765
        }
766
    );
767
}
768
769
#[test]
770
fn ruleset_compat_dummy() {
771
    for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
772
        println!("level: {:?}", level);
773
774
        // ABI:Unsupported does not support AccessFs::Execute.
775
        let ruleset = Ruleset::from(ABI::Unsupported);
776
        assert_eq!(ruleset.compat.state, CompatState::Init);
777
778
        let ruleset = ruleset.set_compatibility(level);
779
        assert_eq!(ruleset.compat.state, CompatState::Init);
780
781
        let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
782
        assert_eq!(
783
            ruleset.compat.state,
784
            match level {
785
                CompatLevel::BestEffort => CompatState::No,
786
                CompatLevel::SoftRequirement => CompatState::Dummy,
787
                _ => unreachable!(),
788
            }
789
        );
790
791
        let ruleset_created = ruleset.create().unwrap();
792
        // Because the compatibility state was either No or Dummy, calling create() updates it to
793
        // Dummy.
794
        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
795
796
        let ruleset_created = ruleset_created
797
            .add_rule(PathBeneath::new(
798
                PathFd::new("/usr").unwrap(),
799
                AccessFs::Execute,
800
            ))
801
            .unwrap();
802
        assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
803
    }
804
}
805
806
#[test]
807
fn ruleset_compat_partial() {
808
    // CompatLevel::BestEffort
809
    let ruleset = Ruleset::from(ABI::V1);
810
    assert_eq!(ruleset.compat.state, CompatState::Init);
811
812
    // ABI::V1 does not support AccessFs::Refer.
813
    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
814
    assert_eq!(ruleset.compat.state, CompatState::No);
815
816
    let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
817
    assert_eq!(ruleset.compat.state, CompatState::Partial);
818
819
    // Requesting to handle another unsupported handled access does not change anything.
820
    let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
821
    assert_eq!(ruleset.compat.state, CompatState::Partial);
822
}
823
824
#[test]
825
fn ruleset_unsupported() {
826
    assert_eq!(
827
        Ruleset::from(ABI::Unsupported)
828
            // BestEffort for Ruleset.
829
            .handle_access(AccessFs::Execute)
830
            .unwrap()
831
            .create()
832
            .unwrap()
833
            .restrict_self()
834
            .unwrap(),
835
        RestrictionStatus {
836
            ruleset: RulesetStatus::NotEnforced,
837
            // With BestEffort, no_new_privs is still enabled.
838
            no_new_privs: true,
839
        }
840
    );
841
842
    assert_eq!(
843
        Ruleset::from(ABI::Unsupported)
844
            // SoftRequirement for Ruleset.
845
            .set_compatibility(CompatLevel::SoftRequirement)
846
            .handle_access(AccessFs::Execute)
847
            .unwrap()
848
            .create()
849
            .unwrap()
850
            .restrict_self()
851
            .unwrap(),
852
        RestrictionStatus {
853
            ruleset: RulesetStatus::NotEnforced,
854
            // With SoftRequirement, no_new_privs is still enabled.
855
            no_new_privs: true,
856
        }
857
    );
858
859
    matches!(
860
        Ruleset::from(ABI::Unsupported)
861
            // HardRequirement for Ruleset.
862
            .set_compatibility(CompatLevel::HardRequirement)
863
            .handle_access(AccessFs::Execute)
864
            .unwrap_err(),
865
        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
866
    );
867
868
    assert_eq!(
869
        Ruleset::from(ABI::Unsupported)
870
            .handle_access(AccessFs::Execute)
871
            .unwrap()
872
            .create()
873
            .unwrap()
874
            // SoftRequirement for RulesetCreated without any rule.
875
            .set_compatibility(CompatLevel::SoftRequirement)
876
            .restrict_self()
877
            .unwrap(),
878
        RestrictionStatus {
879
            ruleset: RulesetStatus::NotEnforced,
880
            // With SoftRequirement, no_new_privs is untouched if there is no error (e.g. no rule).
881
            no_new_privs: true,
882
        }
883
    );
884
885
    // Don't explicitly call create() on a CI that doesn't support Landlock.
886
    if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
887
        assert_eq!(
888
            Ruleset::from(ABI::V1)
889
                .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
890
                .unwrap()
891
                .create()
892
                .unwrap()
893
                // SoftRequirement for RulesetCreated with a rule.
894
                .set_compatibility(CompatLevel::SoftRequirement)
895
                .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
896
                .unwrap()
897
                .restrict_self()
898
                .unwrap(),
899
            RestrictionStatus {
900
                ruleset: RulesetStatus::NotEnforced,
901
                // With SoftRequirement, no_new_privs is still enabled, even if there is an error
902
                // (e.g. unsupported access right).
903
                no_new_privs: true,
904
            }
905
        );
906
    }
907
908
    assert_eq!(
909
        Ruleset::from(ABI::Unsupported)
910
            .handle_access(AccessFs::Execute)
911
            .unwrap()
912
            .create()
913
            .unwrap()
914
            .set_no_new_privs(false)
915
            .restrict_self()
916
            .unwrap(),
917
        RestrictionStatus {
918
            ruleset: RulesetStatus::NotEnforced,
919
            no_new_privs: false,
920
        }
921
    );
922
923
    assert!(matches!(
924
        Ruleset::from(ABI::Unsupported)
925
            // Empty access-rights
926
            .handle_access(AccessFs::from_all(ABI::Unsupported))
927
            .unwrap_err(),
928
        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
929
            CompatError::Access(AccessError::Empty)
930
        )))
931
    ));
932
933
    assert!(matches!(
934
        Ruleset::from(ABI::Unsupported)
935
            // No handle_access() call.
936
            .create()
937
            .unwrap_err(),
938
        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
939
    ));
940
941
    assert!(matches!(
942
        Ruleset::from(ABI::V1)
943
            // Empty access-rights
944
            .handle_access(AccessFs::from_all(ABI::Unsupported))
945
            .unwrap_err(),
946
        RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
947
            CompatError::Access(AccessError::Empty)
948
        )))
949
    ));
950
951
    // Tests inconsistency between the ruleset handled access-rights and the rule access-rights.
952
    for handled_access in &[
953
        make_bitflags!(AccessFs::{Execute | WriteFile}),
954
        AccessFs::Execute.into(),
955
    ] {
956
        let ruleset = Ruleset::from(ABI::V1)
957
            .handle_access(*handled_access)
958
            .unwrap();
959
        // Fakes a call to create() to test without involving the kernel (i.e. no
960
        // landlock_ruleset_create() call).
961
        let ruleset_created = RulesetCreated::new(ruleset, -1);
962
        assert!(matches!(
963
            ruleset_created
964
                .add_rule(PathBeneath::new(
965
                    PathFd::new("/").unwrap(),
966
                    AccessFs::ReadFile
967
                ))
968
                .unwrap_err(),
969
            RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
970
        ));
971
    }
972
}
973
974
#[test]
975
fn ignore_abi_v2_with_abi_v1() {
976
    // We don't need kernel/CI support for Landlock because no related syscalls should actually be
977
    // performed.
978
    assert_eq!(
979
        Ruleset::from(ABI::V1)
980
            .set_compatibility(CompatLevel::HardRequirement)
981
            .handle_access(AccessFs::from_all(ABI::V1))
982
            .unwrap()
983
            .set_compatibility(CompatLevel::SoftRequirement)
984
            // Because Ruleset only supports V1, Refer will be ignored.
985
            .handle_access(AccessFs::Refer)
986
            .unwrap()
987
            .create()
988
            .unwrap()
989
            .add_rule(PathBeneath::new(
990
                PathFd::new("/tmp").unwrap(),
991
                AccessFs::from_all(ABI::V2)
992
            ))
993
            .unwrap()
994
            .add_rule(PathBeneath::new(
995
                PathFd::new("/usr").unwrap(),
996
                make_bitflags!(AccessFs::{ReadFile | ReadDir})
997
            ))
998
            .unwrap()
999
            .restrict_self()
1000
            .unwrap(),
1001
        RestrictionStatus {
1002
            ruleset: RulesetStatus::NotEnforced,
1003
            no_new_privs: true,
1004
        }
1005
    );
1006
}
1007
1008
#[test]
1009
fn unsupported_handled_access() {
1010
    matches!(
1011
        Ruleset::from(ABI::V3)
1012
            .handle_access(AccessNet::from_all(ABI::V3))
1013
            .unwrap_err(),
1014
        RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
1015
            CompatError::Access(AccessError::Empty)
1016
        )))
1017
    );
1018
}