Skip to main content

landlock/
lib.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3//! Landlock is a security feature available since Linux 5.13.
4//! The goal is to enable to restrict ambient rights
5//! (e.g., global filesystem access)
6//! for a set of processes by creating safe security sandboxes as new security layers
7//! in addition to the existing system-wide access-controls.
8//! This kind of sandbox is expected to help mitigate the security impact of bugs,
9//! unexpected or malicious behaviors in applications.
10//! Landlock empowers any process, including unprivileged ones, to securely restrict themselves.
11//! More information about Landlock can be found in the [official website](https://landlock.io).
12//!
13//! This crate provides a safe abstraction for the Landlock system calls, along with some helpers.
14//!
15//! Minimum Supported Rust Version (MSRV): 1.71
16//!
17//! # Use cases
18//!
19//! This crate is especially useful to protect users' data by sandboxing:
20//! * trusted applications dealing with potentially malicious data
21//!   (e.g., complex file format, network request) that could exploit security vulnerabilities;
22//! * sandbox managers, container runtimes or shells launching untrusted applications.
23//!
24//! # Examples
25//!
26//! A simple example can be found with the [`path_beneath_rules()`] helper.
27//! More complex examples can be found with the [`Ruleset` documentation](Ruleset)
28//! and the [sandboxer example](https://github.com/landlock-lsm/rust-landlock/blob/master/examples/sandboxer.rs).
29//!
30//! # Current limitations
31//!
32//! This crate exposes the Landlock features available as of Linux 7.1
33//! (Landlock [ABI v9](ABI::V9))
34//! and then inherits some [kernel limitations](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#current-limitations)
35//! that will be addressed with future kernel releases
36//! (e.g., arbitrary mounts are always denied).
37//!
38//! # Compatibility
39//!
40//! Types defined in this crate are designed to enable the strictest Landlock configuration
41//! for the given kernel on which the program runs.
42//! In the default [best-effort](CompatLevel::BestEffort) mode,
43//! [`Ruleset`] will determine compatibility
44//! with the intersection of the currently running kernel's features
45//! and those required by the caller.
46//! This way, callers can distinguish between
47//! Landlock compatibility issues inherent to the current system
48//! (e.g., file names that don't exist)
49//! and misconfiguration that should be fixed in the program
50//! (e.g., empty or inconsistent access rights).
51//! [`RulesetError`] identifies such kind of errors.
52//!
53//! With [`set_compatibility(CompatLevel::BestEffort)`](Compatible::set_compatibility),
54//! users of the crate may mark Landlock features that are deemed required
55//! and other features that may be downgraded to use lower security on systems
56//! where they can't be enforced.
57//! It is discouraged to compare the system's provided [Landlock ABI](ABI) version directly,
58//! as it is difficult to track detailed ABI differences
59//! which are handled thanks to the [`Compatible`] trait.
60//!
61//! To make it easier to migrate to a new version of this library,
62//! we use the builder pattern
63//! and designed objects to require the minimal set of method arguments.
64//! Most `enum` are marked as `non_exhaustive` to enable backward-compatible evolutions.
65//!
66//! ## Test strategy
67//!
68//! Developers should test their sandboxed applications
69//! with a kernel that supports all requested Landlock features
70//! and check that [`RulesetCreated::restrict_self()`] returns a status matching
71//! [`Ok(RestrictionStatus { ruleset: RulesetStatus::FullyEnforced, no_new_privs: true, })`](RestrictionStatus)
72//! to make sure everything works as expected in an enforced sandbox.
73//! Alternatively, using [`set_compatibility(CompatLevel::HardRequirement)`](Compatible::set_compatibility)
74//! will immediately inform about unsupported Landlock features.
75//! These configurations should only depend on the test environment
76//! (e.g. [by checking an environment variable](https://github.com/landlock-lsm/rust-landlock/search?q=LANDLOCK_CRATE_TEST_ABI)).
77//! However, applications should only check that no error is returned (i.e. `Ok(_)`)
78//! and optionally log and inform users that the application is not fully sandboxed
79//! because of missing features from the running kernel.
80//!
81//! ## Audit logging
82//!
83//! Landlock ABI v7 adds control over audit logging through boolean setters, especially
84//! [`log_new_exec()`](RulesetCreatedAttr::log_new_exec)) which is useful (but noisy) for sandboxer
85//! tools.
86//!
87//! ## Multithreaded processes
88//!
89//! By default `landlock_restrict_self()` only restricts the calling thread.
90//! Landlock ABI v8 adds [`all_threads()`](RestrictSelfAttr::all_threads) to
91//! atomically enforce the configuration on every thread of the process.  On
92//! an older kernel this is silently dropped in the default best-effort mode,
93//! leaving sibling and parent threads unrestricted, so multithreaded programs
94//! that need this guarantee should require it (see
95//! [`all_threads()`](RestrictSelfAttr::all_threads)).
96
97#[cfg(test)]
98#[macro_use]
99extern crate lazy_static;
100
101pub use access::{Access, HandledAccess};
102pub use compat::{CompatLevel, Compatible, LandlockStatus, ABI};
103pub use enumflags2::{make_bitflags, BitFlags};
104pub use errata::Erratum;
105pub use errors::{
106    AccessError, AddRuleError, AddRulesError, CompatError, CreateRulesetError, Errno,
107    HandleAccessError, HandleAccessesError, PathBeneathError, PathFdError, RestrictSelfError,
108    RulesetError, ScopeError, SyscallFlagError,
109};
110pub use flags::{RestrictSelfFlag, SyscallFlag};
111pub use fs::{path_beneath_rules, AccessFs, PathBeneath, PathFd};
112pub use net::{AccessNet, NetPort};
113pub use restrict_self::{RestrictSelf, RestrictSelfAttr, RestrictSelfStatus};
114pub use ruleset::{
115    RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr,
116    RulesetStatus,
117};
118pub use scope::Scope;
119
120use access::PrivateHandledAccess;
121use compat::{CompatResult, CompatState, Compatibility, TailoredCompatLevel, TryCompat};
122use ruleset::PrivateRule;
123
124#[cfg(test)]
125use compat::{can_emulate, get_errno_from_landlock_status};
126#[cfg(test)]
127use errors::TestRulesetError;
128#[cfg(test)]
129use strum::IntoEnumIterator;
130
131mod access;
132mod compat;
133mod errata;
134mod errors;
135mod flags;
136mod fs;
137mod net;
138mod prctl;
139mod restrict_self;
140mod ruleset;
141mod scope;
142mod uapi;
143
144// Makes sure private traits cannot be implemented outside of this crate.
145mod private {
146    pub trait Sealed {}
147
148    impl Sealed for crate::AccessFs {}
149    impl Sealed for crate::AccessNet {}
150    impl Sealed for crate::Scope {}
151    impl Sealed for crate::RestrictSelfFlag {}
152}
153
154#[cfg(test)]
155mod tests {
156    use crate::*;
157
158    // These integration tests exercise the full builder-to-syscall path via
159    // check_ruleset_support().  Other tests in compat.rs and errata.rs make
160    // read-only kernel queries (LandlockStatus::current(), Erratum::current())
161    // but do not create rulesets or restrict threads.  All remaining tests use
162    // Ruleset::from(ABI) to exercise the builder logic and compatibility
163    // engine without kernel interaction.
164
165    // Emulate old kernel supports.  Iterates each ABI variant, mocks the
166    // builder via B::from(abi), runs the closure on a dedicated thread, and
167    // dispatches to the caller's assertion closures based on whether the
168    // mocked ABI is emulatable on the running kernel.
169    fn check_support<B, S, F, OkFn, ErrFn>(
170        partial: ABI,
171        full: Option<ABI>,
172        check: F,
173        assert_ok: OkFn,
174        assert_err: ErrFn,
175    ) where
176        B: From<ABI> + Send + 'static,
177        F: Fn(B) -> Result<S, TestRulesetError> + Send + Copy + 'static,
178        S: std::fmt::Debug + Send + 'static,
179        OkFn: Fn(ABI, Result<S, TestRulesetError>),
180        ErrFn: Fn(Result<S, TestRulesetError>),
181    {
182        // If there is no partial support, it means that `full == partial`.
183        assert!(partial <= full.unwrap_or(partial));
184        for abi in ABI::iter() {
185            // Ensures restrict_self() is called on a dedicated thread to avoid inconsistent tests.
186            let ret = std::thread::spawn(move || check(B::from(abi)))
187                .join()
188                .unwrap();
189
190            // Useful for failed tests and with cargo test -- --show-output
191            println!("Checking ABI {abi:?}: received {ret:#?}");
192            if can_emulate(abi, partial, full) {
193                assert_ok(abi, ret);
194            } else {
195                assert_err(ret);
196            }
197        }
198    }
199
200    fn check_ruleset_support<F>(
201        partial: ABI,
202        full: Option<ABI>,
203        check: F,
204        error_if_abi_lt_partial: bool,
205    ) where
206        F: Fn(Ruleset) -> Result<RestrictionStatus, TestRulesetError> + Send + Copy + 'static,
207    {
208        check_support(
209            partial,
210            full,
211            check,
212            |abi, ret| {
213                if abi < partial && error_if_abi_lt_partial {
214                    // TODO: Check exact error type; this may require better error types.
215                    assert!(matches!(ret, Err(TestRulesetError::Ruleset(_))));
216                } else {
217                    let full_support = if let Some(full_inner) = full {
218                        abi >= full_inner
219                    } else {
220                        false
221                    };
222                    let ruleset_status = if full_support {
223                        RulesetStatus::FullyEnforced
224                    } else if abi >= partial {
225                        RulesetStatus::PartiallyEnforced
226                    } else {
227                        RulesetStatus::NotEnforced
228                    };
229                    let landlock_status = abi.into();
230                    println!("Expecting ruleset status {ruleset_status:?}");
231                    println!("Expecting Landlock status {landlock_status:?}");
232                    assert!(matches!(
233                        ret,
234                        Ok(RestrictionStatus {
235                            ruleset,
236                            landlock,
237                            no_new_privs: true,
238                            ..
239                        }) if ruleset == ruleset_status && landlock == landlock_status
240                    ))
241                }
242            },
243            |ret| {
244                // The errno value should be ENOSYS, EOPNOTSUPP, EINVAL (e.g. when an unknown
245                // access right is provided), or E2BIG (e.g. when there is an unknown field in a
246                // Landlock syscall attribute).
247                let errno = get_errno_from_landlock_status();
248                println!("Expecting error {errno:?}");
249                match ret {
250                    Err(
251                        ref error @ TestRulesetError::Ruleset(RulesetError::CreateRuleset(
252                            CreateRulesetError::CreateRulesetCall { ref source },
253                        )),
254                    ) => {
255                        assert_eq!(source.raw_os_error(), Some(*Errno::from(error)));
256                        match (source.raw_os_error(), errno) {
257                            (Some(e1), Some(e2)) => assert_eq!(e1, e2),
258                            (Some(e1), None) => assert!(matches!(e1, libc::EINVAL | libc::E2BIG)),
259                            _ => unreachable!(),
260                        }
261                    }
262                    // restrict_self flags may be rejected by the kernel with EINVAL
263                    // when the mock ABI is higher than the running kernel's ABI.
264                    Err(TestRulesetError::Ruleset(RulesetError::RestrictSelf(
265                        RestrictSelfError::RestrictSelfCall { ref source },
266                    ))) => {
267                        assert_eq!(source.raw_os_error(), Some(libc::EINVAL));
268                    }
269                    _ => unreachable!(),
270                }
271            },
272        );
273    }
274
275    // Emulate old kernel supports for the domain-less RestrictSelf builder.
276    //
277    // Unlike check_ruleset_support, RestrictSelf does not create a Landlock domain, so there is no
278    // ruleset enforcement status to assert.  We verify the kernel probe (status.landlock) and
279    // propagate any syscall error.  RestrictSelf::apply() enforces PR_SET_NO_NEW_PRIVS by default.
280    fn check_restrict_self_support<F>(partial: ABI, full: Option<ABI>, check: F)
281    where
282        F: Fn(RestrictSelf) -> Result<RestrictSelfStatus, TestRulesetError> + Send + Copy + 'static,
283    {
284        check_support(
285            partial,
286            full,
287            check,
288            |abi, ret| {
289                let landlock_status: LandlockStatus = abi.into();
290                println!("Expecting Landlock status {landlock_status:?}");
291                assert!(matches!(
292                    ret,
293                    Ok(RestrictSelfStatus { landlock, .. }) if landlock == landlock_status
294                ));
295            },
296            |ret| {
297                // The errno value should be ENOSYS, EOPNOTSUPP, or EINVAL (e.g. when actual_flags
298                // carries bits unknown to the running kernel).  Unlike check_ruleset_support,
299                // landlock_restrict_self() is the first syscall here, so ENOSYS is possible when
300                // Landlock is unavailable.
301                let errno = get_errno_from_landlock_status();
302                println!("Expecting error {errno:?}");
303                match ret {
304                    Err(
305                        ref error @ TestRulesetError::Ruleset(RulesetError::RestrictSelf(
306                            RestrictSelfError::RestrictSelfCall { ref source },
307                        )),
308                    ) => {
309                        assert_eq!(source.raw_os_error(), Some(*Errno::from(error)));
310                        match (source.raw_os_error(), errno) {
311                            (Some(e1), Some(e2)) => assert_eq!(e1, e2),
312                            (Some(e1), None) => assert!(matches!(e1, libc::EINVAL | libc::E2BIG)),
313                            _ => unreachable!(),
314                        }
315                    }
316                    _ => unreachable!(),
317                }
318            },
319        );
320    }
321
322    #[test]
323    fn allow_root_compat() {
324        let abi = ABI::V1;
325
326        check_ruleset_support(
327            abi,
328            Some(abi),
329            move |ruleset: Ruleset| -> _ {
330                Ok(ruleset
331                    .handle_access(AccessFs::from_all(abi))?
332                    .create()?
333                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::from_all(abi)))?
334                    .restrict_self()?)
335            },
336            false,
337        );
338    }
339
340    #[test]
341    fn too_much_access_rights_for_a_file() {
342        let abi = ABI::V1;
343
344        check_ruleset_support(
345            abi,
346            Some(abi),
347            move |ruleset: Ruleset| -> _ {
348                Ok(ruleset
349                    .handle_access(AccessFs::from_all(abi))?
350                    .create()?
351                    // Same code as allow_root_compat() but with /etc/passwd instead of /
352                    .add_rule(PathBeneath::new(
353                        PathFd::new("/etc/passwd")?,
354                        // Only allow legitimate access rights on a file.
355                        AccessFs::from_file(abi),
356                    ))?
357                    .restrict_self()?)
358            },
359            false,
360        );
361
362        check_ruleset_support(
363            abi,
364            None,
365            move |ruleset: Ruleset| -> _ {
366                Ok(ruleset
367                    .handle_access(AccessFs::from_all(abi))?
368                    .create()?
369                    // Same code as allow_root_compat() but with /etc/passwd instead of /
370                    .add_rule(PathBeneath::new(
371                        PathFd::new("/etc/passwd")?,
372                        // Tries to allow all access rights on a file.
373                        AccessFs::from_all(abi),
374                    ))?
375                    .restrict_self()?)
376            },
377            false,
378        );
379    }
380
381    #[test]
382    fn path_beneath_rules_with_too_much_access_rights_for_a_file() {
383        let abi = ABI::V1;
384
385        check_ruleset_support(
386            abi,
387            Some(abi),
388            move |ruleset: Ruleset| -> _ {
389                Ok(ruleset
390                    .handle_access(AccessFs::from_all(ABI::V1))?
391                    .create()?
392                    // Same code as too_much_access_rights_for_a_file() but using path_beneath_rules()
393                    .add_rules(path_beneath_rules(["/etc/passwd"], AccessFs::from_all(abi)))?
394                    .restrict_self()?)
395            },
396            false,
397        );
398    }
399
400    #[test]
401    fn allow_root_fragile() {
402        let abi = ABI::V1;
403
404        check_ruleset_support(
405            abi,
406            Some(abi),
407            move |ruleset: Ruleset| -> _ {
408                // Sets default support requirement: abort the whole sandboxing for any Landlock error.
409                Ok(ruleset
410                    // Must have at least the execute check…
411                    .set_compatibility(CompatLevel::HardRequirement)
412                    .handle_access(AccessFs::Execute)?
413                    // …and possibly others.
414                    .set_compatibility(CompatLevel::BestEffort)
415                    .handle_access(AccessFs::from_all(abi))?
416                    .create()?
417                    .no_new_privs(true)
418                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::from_all(abi)))?
419                    .restrict_self()?)
420            },
421            true,
422        );
423    }
424
425    #[test]
426    fn ruleset_enforced() {
427        let abi = ABI::V1;
428
429        check_ruleset_support(
430            abi,
431            Some(abi),
432            move |ruleset: Ruleset| -> _ {
433                Ok(ruleset
434                    // Restricting without rule exceptions is legitimate to forbid a set of actions.
435                    .handle_access(AccessFs::Execute)?
436                    .create()?
437                    .restrict_self()?)
438            },
439            false,
440        );
441    }
442
443    #[test]
444    fn abi_v2_exec_refer() {
445        check_ruleset_support(
446            ABI::V1,
447            Some(ABI::V2),
448            move |ruleset: Ruleset| -> _ {
449                Ok(ruleset
450                    .handle_access(AccessFs::Execute)?
451                    // AccessFs::Refer is not supported by ABI::V1 (best-effort).
452                    .handle_access(AccessFs::Refer)?
453                    .create()?
454                    .restrict_self()?)
455            },
456            false,
457        );
458    }
459
460    #[test]
461    fn abi_v2_refer_only() {
462        // When no access is handled, do not try to create a ruleset without access.
463        check_ruleset_support(
464            ABI::V2,
465            Some(ABI::V2),
466            move |ruleset: Ruleset| -> _ {
467                Ok(ruleset
468                    .handle_access(AccessFs::Refer)?
469                    .create()?
470                    .restrict_self()?)
471            },
472            false,
473        );
474    }
475
476    #[test]
477    fn abi_v3_truncate() {
478        check_ruleset_support(
479            ABI::V2,
480            Some(ABI::V3),
481            move |ruleset: Ruleset| -> _ {
482                Ok(ruleset
483                    .handle_access(AccessFs::Refer)?
484                    .handle_access(AccessFs::Truncate)?
485                    .create()?
486                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::Refer))?
487                    .restrict_self()?)
488            },
489            false,
490        );
491    }
492
493    #[test]
494    fn ruleset_created_try_clone() {
495        check_ruleset_support(
496            ABI::V1,
497            Some(ABI::V1),
498            move |ruleset: Ruleset| -> _ {
499                Ok(ruleset
500                    .handle_access(AccessFs::Execute)?
501                    .create()?
502                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::Execute))?
503                    .try_clone()?
504                    .restrict_self()?)
505            },
506            false,
507        );
508    }
509
510    #[test]
511    fn abi_v4_tcp() {
512        check_ruleset_support(
513            ABI::V3,
514            Some(ABI::V4),
515            move |ruleset: Ruleset| -> _ {
516                Ok(ruleset
517                    .handle_access(AccessFs::Truncate)?
518                    .handle_access(AccessNet::BindTcp | AccessNet::ConnectTcp)?
519                    .create()?
520                    .add_rule(NetPort::new(1, AccessNet::ConnectTcp))?
521                    .restrict_self()?)
522            },
523            false,
524        );
525    }
526
527    #[test]
528    fn abi_v5_ioctl_dev() {
529        check_ruleset_support(
530            ABI::V4,
531            Some(ABI::V5),
532            move |ruleset: Ruleset| -> _ {
533                Ok(ruleset
534                    .handle_access(AccessNet::BindTcp)?
535                    .handle_access(AccessFs::IoctlDev)?
536                    .create()?
537                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::IoctlDev))?
538                    .restrict_self()?)
539            },
540            false,
541        );
542    }
543
544    #[test]
545    fn abi_v6_scope_mix() {
546        check_ruleset_support(
547            ABI::V5,
548            Some(ABI::V6),
549            move |ruleset: Ruleset| -> _ {
550                Ok(ruleset
551                    .handle_access(AccessFs::IoctlDev)?
552                    .scope(Scope::AbstractUnixSocket | Scope::Signal)?
553                    .create()?
554                    .restrict_self()?)
555            },
556            false,
557        );
558    }
559
560    #[test]
561    fn abi_v6_scope_only() {
562        check_ruleset_support(
563            ABI::V6,
564            Some(ABI::V6),
565            move |ruleset: Ruleset| -> _ {
566                Ok(ruleset
567                    .scope(Scope::AbstractUnixSocket | Scope::Signal)?
568                    .create()?
569                    .restrict_self()?)
570            },
571            false,
572        );
573    }
574
575    #[test]
576    fn abi_v7_log_flags() {
577        // Uses Scope::Signal to get partial enforcement at V6 (scopes supported
578        // but log flags not).
579        check_ruleset_support(
580            ABI::V6,
581            Some(ABI::V7),
582            move |ruleset: Ruleset| -> _ {
583                let status = ruleset
584                    .scope(Scope::Signal)?
585                    .create()?
586                    .log_same_exec(false)?
587                    .log_new_exec(true)?
588                    .log_subdomains(false)?
589                    .restrict_self()?;
590
591                if status.ruleset == RulesetStatus::FullyEnforced {
592                    assert!(!status.log_same_exec);
593                    assert!(status.log_new_exec);
594                    assert!(!status.log_subdomains);
595                } else {
596                    assert!(status.log_same_exec);
597                    assert!(!status.log_new_exec);
598                    assert!(status.log_subdomains);
599                }
600
601                Ok(status)
602            },
603            false,
604        );
605    }
606
607    #[test]
608    fn ruleset_created_try_clone_ownedfd() {
609        use std::os::unix::io::{AsRawFd, OwnedFd};
610
611        let abi = ABI::V1;
612        check_ruleset_support(
613            abi,
614            Some(abi),
615            move |ruleset: Ruleset| -> _ {
616                let ruleset1 = ruleset.handle_access(AccessFs::from_all(abi))?.create()?;
617                let ruleset2 = ruleset1.try_clone().unwrap();
618                let ruleset3 = ruleset2.try_clone().unwrap();
619
620                let some1: Option<OwnedFd> = ruleset1.into();
621                if let Some(fd1) = some1 {
622                    assert!(fd1.as_raw_fd() >= 0);
623
624                    let some2: Option<OwnedFd> = ruleset2.into();
625                    let fd2 = some2.unwrap();
626                    assert!(fd2.as_raw_fd() >= 0);
627
628                    assert_ne!(fd1.as_raw_fd(), fd2.as_raw_fd());
629                }
630                Ok(ruleset3.restrict_self()?)
631            },
632            false,
633        );
634    }
635
636    #[test]
637    fn restrict_self_log_subdomains() {
638        check_restrict_self_support(ABI::V7, Some(ABI::V7), move |rs: RestrictSelf| -> _ {
639            Ok(rs.log_subdomains(false)?.apply()?)
640        });
641    }
642
643    // ABI v8's LANDLOCK_RESTRICT_SELF_TSYNC (exposed as all_threads()) applies
644    // the domain to every thread of the process, not just the calling one.
645    // Enforcing that in the cargo-test runner would restrict every test thread,
646    // so each all_threads() setting runs in its own forked child (restrict_self
647    // is irreversible and, with TSYNC, process-wide) that hosts exactly two
648    // threads: its main thread and one sibling.  Both threads list "/" (which
649    // needs AccessFs::ReadDir, handled by from_all(ABI::V1)) before and after
650    // enforcement.
651    //
652    // Expected outcomes are pinned from TEST_ABI, the running kernel's Landlock
653    // ABI: set per kernel by CI, auto-detected locally, and asserted to match
654    // the kernel by current_kernel_abi().  It is an oracle independent of the
655    // restrict_self() status under test: the calling thread must be denied iff
656    // Landlock is enforced at all, and the sibling must be denied iff TSYNC is
657    // both requested and supported (ABI v8+).  Both the kernel's real behavior
658    // and the crate's reported status are checked against this truth, so a
659    // silently dropped or otherwise ineffective TSYNC is caught on every kernel
660    // in the CI matrix, from unsupported up to v8.
661    #[test]
662    fn abi_v8_all_threads() {
663        use crate::compat::TEST_ABI;
664        use std::sync::{Arc, Barrier};
665        use std::thread;
666
667        // Child exit code: OK, the OR of the mismatched checks, or PANICKED.
668        const OK: i32 = 0;
669        const MAIN_BEFORE_DENIED: i32 = 1 << 0;
670        const SIBLING_BEFORE_DENIED: i32 = 1 << 1;
671        const MAIN_AFTER_MISMATCH: i32 = 1 << 2;
672        const SIBLING_AFTER_MISMATCH: i32 = 1 << 3;
673        const STATUS_RULESET_MISMATCH: i32 = 1 << 4;
674        const STATUS_TSYNC_MISMATCH: i32 = 1 << 5;
675        const PANICKED: i32 = 1 << 6;
676
677        // Listing "/" needs AccessFs::ReadDir: allowed before enforcement,
678        // denied afterwards for every thread the domain covers.
679        fn can_list_root() -> bool {
680            std::fs::read_dir("/").is_ok()
681        }
682
683        // Ground truth for the running kernel, independent of the status object.
684        let landlock_supported = *TEST_ABI != ABI::Unsupported;
685        let tsync_supported = *TEST_ABI >= ABI::V8;
686
687        for all_threads in [false, true] {
688            // The sibling is covered only when TSYNC is requested and supported.
689            let expect_sibling_denied = all_threads && tsync_supported;
690
691            match unsafe { libc::fork() } {
692                -1 => panic!("fork() failed: {}", std::io::Error::last_os_error()),
693                0 => {
694                    // Any panic (builder error, spawn/join failure) becomes a
695                    // non-zero code rather than being swallowed as a pass.
696                    let code = std::panic::catch_unwind(|| {
697                        let barrier = Arc::new(Barrier::new(2));
698                        let sibling = {
699                            let barrier = Arc::clone(&barrier);
700                            thread::spawn(move || {
701                                let before = can_list_root();
702                                // Both threads probed before enforcing.
703                                barrier.wait();
704                                // Main has called restrict_self().
705                                barrier.wait();
706                                (before, can_list_root())
707                            })
708                        };
709
710                        let main_before = can_list_root();
711                        barrier.wait();
712
713                        let status = Ruleset::default()
714                            .handle_access(AccessFs::from_all(ABI::V1))
715                            .unwrap()
716                            .create()
717                            .unwrap()
718                            .all_threads(all_threads)
719                            .unwrap()
720                            .restrict_self()
721                            .unwrap();
722                        barrier.wait();
723
724                        let main_denied = !can_list_root();
725                        let (sibling_before, sibling_after) = sibling.join().unwrap();
726                        let sibling_denied = !sibling_after;
727                        let ruleset_enforced = status.ruleset != RulesetStatus::NotEnforced;
728
729                        let mut code = OK;
730                        // Both threads can list "/" before enforcement.
731                        if !main_before {
732                            code |= MAIN_BEFORE_DENIED;
733                        }
734                        if !sibling_before {
735                            code |= SIBLING_BEFORE_DENIED;
736                        }
737                        // Kernel reality matches the independent expectation.
738                        if main_denied != landlock_supported {
739                            code |= MAIN_AFTER_MISMATCH;
740                        }
741                        if sibling_denied != expect_sibling_denied {
742                            code |= SIBLING_AFTER_MISMATCH;
743                        }
744                        // The crate's reported status matches it too.
745                        if ruleset_enforced != landlock_supported {
746                            code |= STATUS_RULESET_MISMATCH;
747                        }
748                        if status.all_threads != expect_sibling_denied {
749                            code |= STATUS_TSYNC_MISMATCH;
750                        }
751                        code
752                    })
753                    .unwrap_or(PANICKED);
754                    // _exit avoids atexit handlers and destructors inherited from
755                    // the test harness.
756                    unsafe { libc::_exit(code) };
757                }
758                pid => {
759                    let mut wstatus: libc::c_int = 0;
760                    let ret = unsafe { libc::waitpid(pid, &mut wstatus, 0) };
761                    assert_eq!(ret, pid);
762                    assert!(libc::WIFEXITED(wstatus));
763                    assert_eq!(libc::WEXITSTATUS(wstatus), OK, "all_threads({all_threads})");
764                }
765            }
766        }
767    }
768
769    #[test]
770    fn abi_v9_resolve_unix() {
771        // ResolveUnix is the only access right added in ABI v9, and ABI v8 added
772        // no filesystem access right, so there is no access from a lower ABI to
773        // combine it with while keeping `partial` and `full` adjacent.  It is
774        // therefore tested on its own with partial == full == V9, like
775        // abi_v2_refer_only.  Pairing it with a non-adjacent lower right (e.g.
776        // IoctlDev from V5) would leave mocked ABIs V6..=V8 where only that lower
777        // right is enforced (PartiallyEnforced); can_emulate() expects an error
778        // whenever the runner kernel's ABI is below the mocked one, so such a
779        // test would pass or fail depending on the runner kernel.
780        //
781        // For a mocked ABI below V9, ResolveUnix is dropped by best-effort
782        // compatibility: no access is handled, create() builds no kernel ruleset
783        // and add_rule() is a no-op, so restrict_self() reports NotEnforced (not
784        // an error).  At mocked ABI V9 the ruleset is fully enforced on a v9
785        // kernel, or the create()/add_rule() syscall is rejected on an older one
786        // (asserted through can_emulate()'s error path).
787        check_ruleset_support(
788            ABI::V9,
789            Some(ABI::V9),
790            move |ruleset: Ruleset| -> _ {
791                Ok(ruleset
792                    .handle_access(AccessFs::ResolveUnix)?
793                    .create()?
794                    .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::ResolveUnix))?
795                    .restrict_self()?)
796            },
797            false,
798        );
799    }
800}