Rust Landlock

repository·main·Indexed 42 days ago

https://github.com/landlock-lsm/rust-landlock

A Rust wrapper providing safe abstractions for the Linux Landlock security feature (available since kernel 5.13). It enables processes to sandbox themselves and restrict filesystem access by defining rulesets and access rights, helping mitigate the impact of bugs or malicious behavior in applications.

Tokens
10K
Snippets
28
Records
40
Agent score
55%

What's inside Rust Landlock

  1. What is Rust Landlock?

    main
    Rust Landlock is a safe abstraction for the Linux Landlock security feature (available since Linux kernel 5.13). It allows processes—including unprivileged ones—to restrict their own ambient rights, such as global filesystem access. By creating a security sandbox, it helps mitigate the impact of bugs or malicious behavior in applications by adding a new layer of security on top of existing system-wide access controls.
  2. Use cases for Rust Landlock

    main

    This crate is designed for scenarios where you need to protect user data by sandboxing specific components, such as:

    • Trusted applications handling untrusted data: Protecting against exploits in complex file formats or network requests.
    • Sandbox managers and container runtimes: Launching untrusted applications within a restricted environment.
    • Shells: Restricting the scope of commands executed within a shell environment.
  3. Explore Rust Landlock examples

    main

    To learn how to implement sandboxing, you can refer to the following resources:

    • Simple sandboxing: Use the path_beneath_rules() helper for basic path-based restrictions.
    • Advanced configuration: Consult the Ruleset documentation for complex rule definitions.
    • Full implementation: Review the examples/sandboxer.rs file in the repository for a complete sandboxer implementation.
  4. Understand AccessError and compatibility errors

    main

    When configuring access-rights, the library may return AccessError or CompatError. These errors help you understand if your requested sandbox configuration is compatible with the current running kernel.

    Key AccessError variants:

    • Empty: The access-rights set is empty (rejected by the kernel).
    • Unknown: The bitflags contain bits that are not recognized by the library.
    • Incompatible: The requested access-rights are fully incompatible with the kernel (when best-effort mode is disabled).
    • PartiallyCompatible: The requested access-rights are only partially supported by the kernel.

    PathBeneathError can also occur when adding rules, specifically if a directory-only access-right is requested for a file descriptor that does not point to a directory.

  5. Configure `landlock_restrict_self()` flags via `RestrictSelfFlag`

    main

    The RestrictSelfFlag enum identifies configuration flags used specifically with the landlock_restrict_self() syscall. Unlike access rights or scopes, these flags control audit logging behavior and thread synchronization rather than access restrictions.

    These flags are not passed to landlock_create_ruleset(). Instead, they are set using dedicated boolean setter methods on either RulesetCreatedAttr or RestrictSelfAttr. The library handles the mapping between your boolean input and the underlying UAPI bitmask (including polarity, such as mapping false to an _OFF constant) automatically.

    Available flags:

    • LogSameExec: Controls same-exec logging (set via RulesetCreatedAttr::log_same_exec()).
    • LogNewExec: Controls new-exec logging (set via RulesetCreatedAttr::log_new_exec()).
    • LogSubdomains: Controls subdomain logging (set via RestrictSelfAttr::log_subdomains()).
    • AllThreads: Controls whether the restriction applies to all threads of the process (set via RestrictSelfAttr::all_threads()).
    // Note: Flags are typically set via builder methods on attributes, e.g.:
    // ruleset_created_attr.log_same_exec(true);
    // restrict_self_attr.all_threads(true);
  6. How compatibility levels affect ruleset enforcement

    main

    The CompatLevel determines how the library behaves when requested features (like specific access rights or scopes) are not supported by the current kernel ABI.

    • HardRequirement: The library will return an error (e.g., RulesetError::HandleAccesses or RulesetError::RestrictSelfFlags) if any requested feature is unsupported.
    • BestEffort: Unsupported features are silently ignored or dropped. For example, if an access right is unsupported, the ruleset might still be created but with a RulesetStatus::NotEnforced or LandlockStatus::NotImplemented status.
    • SoftRequirement: Provides a middle ground where some unsupported features might be silently dropped (e.g., setting a scope), but the library attempts to maintain certain protections like no_new_privs where possible.
  7. Configure Landlock compatibility levels

    main

    You can control how the library handles kernel feature mismatches using the set_compatibility method on a Ruleset builder. This is useful for deciding whether to allow a degraded sandbox on older kernels or to fail entirely if strict security cannot be guaranteed.

    • Use CompatLevel::BestEffort to allow the library to automatically downgrade unsupported features.
    • Use CompatLevel::HardRequirement to ensure the application only runs if the kernel supports all requested features.
    // Example of setting a hard requirement for features
    let status = ruleset
        .set_compatibility(CompatLevel::HardRequirement)
        .handle_access(AccessFs::Execute)?
        .create()?
        .restrict_self()?;
  8. How Ruleset and RulesetCreated work together

    main

    The Landlock enforcement process follows a specific builder lifecycle:

    1. Ruleset: The initial configuration stage. Use handle_access() to define which types of access (FS, Net, etc.) the ruleset will govern. Use scope() to define scopes. Call .create() to transition to the next stage.
    2. RulesetCreated: The rule definition stage. This object holds the file descriptor for the ruleset. Use add_rule() or add_rules() to attach specific restrictions (e.g., restricting access to a specific directory). Use restrict_self() to finalize and apply the sandbox to the current thread.
    3. RestrictionStatus: The result of enforcement. It tells you if the ruleset was FullyEnforced, PartiallyEnforced, or NotEnforced based on kernel support and your compatibility settings.
  9. Understand `RestrictionStatus` output

    main

    The restrict_self() method returns a RestrictionStatus struct which describes the actual state of the process restrictions. Key fields include:

    • ruleset: The enforcement status of the ruleset (e.g., RulesetStatus::NotEnforced).
    • landlock: The status of the Landlock subsystem (e.g., LandlockStatus::NotImplemented or LandlockStatus::Available).
    • no_new_privs: A boolean indicating if the no_new_privs bit is enforced.
    • log_same_exec, log_new_exec, log_subdomains, all_threads: Boolean flags reflecting the active logging and thread configuration.
  10. How Landlock sandboxing works

    main

    Landlock is a Linux security feature (available since kernel 5.13) that allows processes to restrict their own ambient rights, such as filesystem access, by creating security sandboxes. This helps mitigate the impact of bugs or malicious behavior in applications.

    In this crate, you define a Ruleset containing specific Rules (like PathBeneath) and associated Access rights (like AccessFs). Once the ruleset is created, you call restrict_self() to enforce the sandbox on the current process.

    Key Concepts:

    • Ruleset: A collection of rules and access rights that define the sandbox boundaries.
    • Access Rights: Specific permissions being handled (e.g., AccessFs::Execute, AccessNet::ConnectTcp).
    • Compatibility Modes: The crate uses a CompatLevel to handle different kernel versions:
      • BestEffort (default): The Ruleset will determine compatibility based on the intersection of the running kernel's features and the requested features. If a feature is unsupported, it is gracefully downgraded or ignored.
      • HardRequirement: The application will immediately return an error if any requested Landlock feature is unsupported by the running kernel.
    • Enforcement Status: After calling restrict_self(), you can check the RestrictionStatus to see if the ruleset was FullyEnforced, PartiallyEnforced, or NotEnforced.
  11. Create and configure a Landlock Ruleset

    main

    Use the Ruleset builder to define which filesystem, network, or scope accesses your application should handle. You must call handle_access() to specify the access rights that the ruleset will manage before calling create().

    Once created, a RulesetCreated object allows you to add specific rules (like PathBeneath) and finally apply the restrictions to the current process using restrict_self().

    use landlock::{
        AccessFs, PathBeneath, PathFd, RestrictionStatus, Ruleset, RulesetAttr, 
        RulesetCreatedAttr, RulesetError, ABI
    };
    use std::os::unix::io::AsFd;
    
    fn restrict_fd<T>(hierarchy: T) -> Result<RestrictionStatus, RulesetError>
    where
        T: AsFd,
    {
        let abi = ABI::V1;
        let access_all = AccessFs::from_all(abi);
        let access_read = AccessFs::from_read(abi);
        Ok(Ruleset::default()
            .handle_access(access_all)?
            .create()?
            .add_rule(PathBeneath::new(hierarchy, access_read))?
            .restrict_self()?)
    }
    
    let fd = PathFd::new("/home").expect("failed to open /home");
    let status = restrict_fd(fd).expect("failed to build the ruleset");
  12. Configure ruleset compatibility and add rules

    main

    To restrict a process, you typically follow this pattern: create a Ruleset, define the types of access you intend to handle, create the ruleset instance, add specific path or resource rules, and finally call restrict_self().

    Note that handle_access must be called before create() to define the scope of what the ruleset is allowed to manage. If you add a rule with access rights that were not declared in handle_access, the library will return a RulesetError::AddRules error.

    // Example: Restricting access to /usr and /etc
    let status = Ruleset::from(ABI::V1)
        .handle_access(AccessFs::Execute)
        .unwrap()
        .create()
        .unwrap()
        .set_compatibility(CompatLevel::BestEffort)
        .add_rule(PathBeneath::new(
            PathFd::new("/usr").unwrap(),
            AccessFs::Execute,
        ))
        .unwrap()
        .add_rule(PathBeneath::new(
            PathFd::new("/etc").unwrap(),
            AccessFs::Execute,
        ))
        .unwrap()
        .restrict_self()
        .unwrap();