Rust Landlock
repository·main·Indexed 42 days ago
https://github.com/landlock-lsm/rust-landlockA 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.
What's inside Rust Landlock
- 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.
Use cases for Rust Landlock
mainThis 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.
Explore Rust Landlock examples
mainTo 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
Rulesetdocumentation for complex rule definitions. - Full implementation: Review the
examples/sandboxer.rsfile in the repository for a complete sandboxer implementation.
- Simple sandboxing: Use the
Understand AccessError and compatibility errors
mainWhen configuring access-rights, the library may return
AccessErrororCompatError. These errors help you understand if your requested sandbox configuration is compatible with the current running kernel.Key
AccessErrorvariants: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.
PathBeneathErrorcan 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.Configure `landlock_restrict_self()` flags via `RestrictSelfFlag`
mainThe
RestrictSelfFlagenum identifies configuration flags used specifically with thelandlock_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 eitherRulesetCreatedAttrorRestrictSelfAttr. The library handles the mapping between your boolean input and the underlying UAPI bitmask (including polarity, such as mappingfalseto an_OFFconstant) automatically.Available flags:
LogSameExec: Controls same-exec logging (set viaRulesetCreatedAttr::log_same_exec()).LogNewExec: Controls new-exec logging (set viaRulesetCreatedAttr::log_new_exec()).LogSubdomains: Controls subdomain logging (set viaRestrictSelfAttr::log_subdomains()).AllThreads: Controls whether the restriction applies to all threads of the process (set viaRestrictSelfAttr::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);How compatibility levels affect ruleset enforcement
mainThe
CompatLeveldetermines 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::HandleAccessesorRulesetError::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 aRulesetStatus::NotEnforcedorLandlockStatus::NotImplementedstatus.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 likeno_new_privswhere possible.
Configure Landlock compatibility levels
mainYou can control how the library handles kernel feature mismatches using the
set_compatibilitymethod on aRulesetbuilder. 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::BestEffortto allow the library to automatically downgrade unsupported features. - Use
CompatLevel::HardRequirementto 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()?;- Use
How Ruleset and RulesetCreated work together
mainThe Landlock enforcement process follows a specific builder lifecycle:
Ruleset: The initial configuration stage. Usehandle_access()to define which types of access (FS, Net, etc.) the ruleset will govern. Usescope()to define scopes. Call.create()to transition to the next stage.RulesetCreated: The rule definition stage. This object holds the file descriptor for the ruleset. Useadd_rule()oradd_rules()to attach specific restrictions (e.g., restricting access to a specific directory). Userestrict_self()to finalize and apply the sandbox to the current thread.RestrictionStatus: The result of enforcement. It tells you if the ruleset wasFullyEnforced,PartiallyEnforced, orNotEnforcedbased on kernel support and your compatibility settings.
Understand `RestrictionStatus` output
mainThe
restrict_self()method returns aRestrictionStatusstruct 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::NotImplementedorLandlockStatus::Available).no_new_privs: A boolean indicating if theno_new_privsbit is enforced.log_same_exec,log_new_exec,log_subdomains,all_threads: Boolean flags reflecting the active logging and thread configuration.
How Landlock sandboxing works
mainLandlock 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
Rulesetcontaining specificRules (likePathBeneath) and associatedAccessrights (likeAccessFs). Once the ruleset is created, you callrestrict_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
CompatLevelto handle different kernel versions:BestEffort(default): TheRulesetwill 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 theRestrictionStatusto see if the ruleset wasFullyEnforced,PartiallyEnforced, orNotEnforced.
Create and configure a Landlock Ruleset
mainUse the
Rulesetbuilder to define which filesystem, network, or scope accesses your application should handle. You must callhandle_access()to specify the access rights that the ruleset will manage before callingcreate().Once created, a
RulesetCreatedobject allows you to add specific rules (likePathBeneath) and finally apply the restrictions to the current process usingrestrict_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");Configure ruleset compatibility and add rules
mainTo 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 callrestrict_self().Note that
handle_accessmust be called beforecreate()to define the scope of what the ruleset is allowed to manage. If you add a rule with access rights that were not declared inhandle_access, the library will return aRulesetError::AddRuleserror.// 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();