diff --git a/.lock b/.lock new file mode 100644 index 0000000..e69de29 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/crates.js b/crates.js new file mode 100644 index 0000000..0a1a379 --- /dev/null +++ b/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["landlock"]; \ No newline at end of file diff --git a/help.html b/help.html new file mode 100644 index 0000000..d2ce699 --- /dev/null +++ b/help.html @@ -0,0 +1 @@ +
Redirecting to ../../landlock/trait.Access.html...
+ + + \ No newline at end of file diff --git a/landlock/all.html b/landlock/all.html new file mode 100644 index 0000000..409e1d4 --- /dev/null +++ b/landlock/all.html @@ -0,0 +1 @@ +Redirecting to ../../landlock/enum.ABI.html...
+ + + \ No newline at end of file diff --git a/landlock/compat/enum.CompatLevel.html b/landlock/compat/enum.CompatLevel.html new file mode 100644 index 0000000..1f1c92c --- /dev/null +++ b/landlock/compat/enum.CompatLevel.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.CompatLevel.html...
+ + + \ No newline at end of file diff --git a/landlock/compat/trait.Compatible.html b/landlock/compat/trait.Compatible.html new file mode 100644 index 0000000..8d1820c --- /dev/null +++ b/landlock/compat/trait.Compatible.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/trait.Compatible.html...
+ + + \ No newline at end of file diff --git a/landlock/enum.ABI.html b/landlock/enum.ABI.html new file mode 100644 index 0000000..50fe1e2 --- /dev/null +++ b/landlock/enum.ABI.html @@ -0,0 +1,60 @@ +#[non_exhaustive]pub enum ABI {
+ Unsupported = 0,
+ V1 = 1,
+ V2 = 2,
+ V3 = 3,
+ V4 = 4,
+ V5 = 5,
+}
Version of the Landlock ABI.
+ABI
enables getting the features supported by a specific Landlock ABI
+(without relying on the kernel version which may not be accessible or patched).
+For example, AccessFs::from_all(ABI::V1)
+gets all the file system access rights defined by the first version.
Without ABI
, it would be hazardous to rely on the the full set of access flags
+(e.g., BitFlags::<AccessFs>::all()
or BitFlags::ALL
),
+a moving target that would change the semantics of your Landlock rule
+when migrating to a newer version of this crate.
+Indeed, a simple cargo update
or cargo install
run by any developer
+can result in a new version of this crate (fixing bugs or bringing non-breaking changes).
+This crate cannot give any guarantee concerning the new restrictions resulting from
+these unknown bits (i.e. access rights) that would not be controlled by your application but by
+a future version of this crate instead.
+Because we cannot know what the effect on your application of an unknown restriction would be
+when handling an untested Landlock access right (i.e. denied-by-default access),
+it could trigger bugs in your application.
This crate provides a set of tools to sandbox as much as possible
+while guaranteeing a consistent behavior thanks to the Compatible
methods.
+You should also test with different relevant kernel versions,
+see landlock-test-tools and
+CI integration.
This way, we can have the guarantee that the use of a set of tested Landlock ABI works as +expected because features brought by newer Landlock ABI will never be enabled by default +(cf. Linux kernel compatibility contract).
+In a nutshell, test the access rights you request on a kernel that support them and +on a kernel that doesn’t support them.
+Kernel not supporting Landlock, either because it is not built with Landlock +or Landlock is not enabled at boot.
+First Landlock ABI, introduced with +Linux 5.13.
+Second Landlock ABI, introduced with +Linux 5.19.
+Third Landlock ABI, introduced with +Linux 6.2.
+Fourth Landlock ABI, introduced with +Linux 6.7.
+Fifth Landlock ABI, introduced with +Linux 6.10.
+clone_to_uninit
)clone_to_uninit
)pub enum AccessError<T>where
+ T: Access,{
+ Empty,
+ Unknown {
+ access: BitFlags<T>,
+ unknown: BitFlags<T>,
+ },
+ Incompatible {
+ access: BitFlags<T>,
+ },
+ PartiallyCompatible {
+ access: BitFlags<T>,
+ incompatible: BitFlags<T>,
+ },
+}
The access-rights set is empty, which doesn’t make sense and would be rejected by the +kernel.
+The access-rights set was forged with the unsafe BitFlags::from_bits_unchecked()
and it
+contains unknown bits.
The best-effort approach was (deliberately) disabled and the requested access-rights are +fully incompatible with the running kernel.
+The best-effort approach was (deliberately) disabled and the requested access-rights are +partially incompatible with the running kernel.
+#[non_exhaustive]#[repr(u64)]pub enum AccessFs {
+Show 16 variants
Execute = 1,
+ WriteFile = 2,
+ ReadFile = 4,
+ ReadDir = 8,
+ RemoveDir = 16,
+ RemoveFile = 32,
+ MakeChar = 64,
+ MakeDir = 128,
+ MakeReg = 256,
+ MakeSock = 512,
+ MakeFifo = 1_024,
+ MakeBlock = 2_048,
+ MakeSym = 4_096,
+ Refer = 8_192,
+ Truncate = 16_384,
+ IoctlDev = 32_768,
+}
File system access right.
+Each variant of AccessFs
is an access right
+for the file system.
+A set of access rights can be created with BitFlags<AccessFs>
.
use landlock::{ABI, Access, AccessFs, BitFlags, make_bitflags};
+
+let exec = AccessFs::Execute;
+
+let exec_set: BitFlags<AccessFs> = exec.into();
+
+let file_content = make_bitflags!(AccessFs::{Execute | WriteFile | ReadFile});
+
+let fs_v1 = AccessFs::from_all(ABI::V1);
+
+let without_exec = fs_v1 & !AccessFs::Execute;
+
+assert_eq!(fs_v1 | AccessFs::Refer, AccessFs::from_all(ABI::V2));
To avoid unknown restrictions don’t use BitFlags::<AccessFs>::all()
nor BitFlags::ALL
,
+but use a version you tested and vetted instead,
+for instance AccessFs::from_all(ABI::V1)
.
+Direct use of the BitFlags
API is deprecated.
+See ABI
for the rationale and help to test it.
Execute a file.
+Open a file with write access.
+Open a file with read access.
+Open a directory or list its content.
+Remove an empty directory or rename one.
+Unlink (or rename) a file.
+Create (or rename or link) a character device.
+Create (or rename) a directory.
+Create (or rename or link) a regular file.
+Create (or rename or link) a UNIX domain socket.
+Create (or rename or link) a named pipe.
+Create (or rename or link) a block device.
+Create (or rename or link) a symbolic link.
+Link or rename a file from or to a different directory.
+Truncate a file with truncate(2)
, ftruncate(2)
, creat(2)
, or open(2)
with O_TRUNC
.
Send IOCL commands to a device file.
+Gets the access rights identified as read-only according to a specific ABI.
+Exclusive with from_write()
.
Gets the access rights identified as write-only according to a specific ABI.
+Exclusive with from_read()
.
Union of from_read()
and from_write()
.
BitFlags
with no flags set (in other words, with a value of 0). Read moreBitFlags
if the raw value provided does not contain
+any illegal flags. Read moreBitFlags
from an underlying bitwise value. If any
+invalid bits are set, ignore them. Read moreBitFlags
unsafely, without checking if the bits form
+a valid bit pattern for the type. Read moreclone_to_uninit
)clone_to_uninit
)#[non_exhaustive]#[repr(u64)]pub enum AccessNet {
+ BindTcp = 1,
+ ConnectTcp = 2,
+}
Network access right.
+Each variant of AccessNet
is an access right
+for the network.
+A set of access rights can be created with BitFlags<AccessNet>
.
use landlock::{ABI, Access, AccessNet, BitFlags, make_bitflags};
+
+let bind = AccessNet::BindTcp;
+
+let bind_set: BitFlags<AccessNet> = bind.into();
+
+let bind_connect = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
+
+let net_v4 = AccessNet::from_all(ABI::V4);
+
+assert_eq!(bind_connect, net_v4);
To avoid unknown restrictions don’t use BitFlags::<AccessNet>::all()
nor BitFlags::ALL
,
+but use a version you tested and vetted instead,
+for instance AccessNet::from_all(ABI::V4)
.
+Direct use of the BitFlags
API is deprecated.
+See ABI
for the rationale and help to test it.
If ABI <= ABI::V3
, AccessNet::from_all()
returns an empty BitFlags<AccessNet>
, which
+makes Ruleset::handle_access(AccessNet::from_all(ABI::V3))
return an error.
BitFlags
with no flags set (in other words, with a value of 0). Read moreBitFlags
if the raw value provided does not contain
+any illegal flags. Read moreBitFlags
from an underlying bitwise value. If any
+invalid bits are set, ignore them. Read moreBitFlags
unsafely, without checking if the bits form
+a valid bit pattern for the type. Read moreclone_to_uninit
)clone_to_uninit
)#[non_exhaustive]pub enum AddRuleError<T>where
+ T: Access,{
+ AddRuleCall {
+ source: Error,
+ },
+ UnhandledAccess {
+ access: BitFlags<T>,
+ incompatible: BitFlags<T>,
+ },
+ Compat(CompatError<T>),
+}
Identifies errors when adding a rule to a ruleset.
+The landlock_add_rule()
system call failed.
The rule’s access-rights are not all handled by the (requested) ruleset access-rights.
+#[non_exhaustive]pub enum AddRulesError {
+ Fs(AddRuleError<AccessFs>),
+ Net(AddRuleError<AccessNet>),
+}
Identifies errors when adding rules to a ruleset thanks to an iterator returning +Result<Rule, E> items.
+#[non_exhaustive]pub enum CompatError<T>where
+ T: Access,{
+ PathBeneath(PathBeneathError),
+ Access(AccessError<T>),
+}
pub enum CompatLevel {
+ BestEffort,
+ SoftRequirement,
+ HardRequirement,
+}
See the Compatible
documentation.
Takes into account the build requests if they are supported by the running system, +or silently ignores them otherwise. +Never returns a compatibility error.
+Takes into account the build requests if they are supported by the running system,
+or silently ignores the whole build object otherwise.
+Never returns a compatibility error.
+If not supported,
+the call to RulesetCreated::restrict_self()
+will return a
+RestrictionStatus { ruleset: RulesetStatus::NotEnforced, no_new_privs: false, }
.
Takes into account the build requests if they are supported by the running system,
+or returns a compatibility error otherwise (CompatError
).
source
. Read moreself
and other
values to be equal, and is used
+by ==
.self
and other
) and is used by the <=
+operator. Read moreclone_to_uninit
)clone_to_uninit
)#[non_exhaustive]pub enum CreateRulesetError {
+ CreateRulesetCall {
+ source: Error,
+ },
+ MissingHandledAccess,
+}
Identifies errors when creating a ruleset.
+The landlock_create_ruleset()
system call failed.
Missing call to RulesetAttr::handle_access()
.
#[non_exhaustive]pub enum HandleAccessError<T>where
+ T: Access,{
+ Compat(CompatError<T>),
+}
Identifies errors when updating the ruleset’s handled access-rights.
+#[non_exhaustive]pub enum HandleAccessesError {
+ Fs(HandleAccessError<AccessFs>),
+ Net(HandleAccessError<AccessNet>),
+}
#[non_exhaustive]pub enum PathBeneathError {
+ StatCall {
+ source: Error,
+ },
+ DirectoryAccess {
+ access: BitFlags<AccessFs>,
+ incompatible: BitFlags<AccessFs>,
+ },
+}
To check that access-rights are consistent with a file descriptor, a call to
+RulesetCreatedAttr::add_rule()
+looks at the file type with an fstat()
system call.
This error is returned by
+RulesetCreatedAttr::add_rule()
+if the related PathBeneath object is not set to best-effort,
+and if its allowed access-rights contain directory-only ones
+whereas the file descriptor doesn’t point to a directory.
#[non_exhaustive]pub enum PathFdError {
+ OpenCall {
+ source: Error,
+ path: PathBuf,
+ },
+}
The open()
system call failed.
#[non_exhaustive]pub enum RestrictSelfError {
+ SetNoNewPrivsCall {
+ source: Error,
+ },
+ RestrictSelfCall {
+ source: Error,
+ },
+}
The prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
system call failed.
The landlock_restrict_self()
system call failed.
#[non_exhaustive]pub enum RulesetError {
+ HandleAccesses(HandleAccessesError),
+ CreateRuleset(CreateRulesetError),
+ AddRules(AddRulesError),
+ RestrictSelf(RestrictSelfError),
+}
Maps to all errors that can be returned by a ruleset action.
+pub enum RulesetStatus {
+ FullyEnforced,
+ PartiallyEnforced,
+ NotEnforced,
+}
Enforcement status of a ruleset.
+All requested restrictions are enforced.
+Some requested restrictions are enforced, +following a best-effort approach.
+The running system doesn’t support Landlock +or a subset of the requested Landlock features.
+self
and other
values to be equal, and is used
+by ==
.Redirecting to ../../landlock/enum.AccessError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.AddRuleError.html b/landlock/errors/enum.AddRuleError.html new file mode 100644 index 0000000..14c0c6f --- /dev/null +++ b/landlock/errors/enum.AddRuleError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.AddRuleError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.AddRulesError.html b/landlock/errors/enum.AddRulesError.html new file mode 100644 index 0000000..3b9bd3c --- /dev/null +++ b/landlock/errors/enum.AddRulesError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.AddRulesError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.CompatError.html b/landlock/errors/enum.CompatError.html new file mode 100644 index 0000000..ce6d875 --- /dev/null +++ b/landlock/errors/enum.CompatError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.CompatError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.CreateRulesetError.html b/landlock/errors/enum.CreateRulesetError.html new file mode 100644 index 0000000..bea5d20 --- /dev/null +++ b/landlock/errors/enum.CreateRulesetError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.CreateRulesetError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.HandleAccessError.html b/landlock/errors/enum.HandleAccessError.html new file mode 100644 index 0000000..950acf3 --- /dev/null +++ b/landlock/errors/enum.HandleAccessError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.HandleAccessError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.HandleAccessesError.html b/landlock/errors/enum.HandleAccessesError.html new file mode 100644 index 0000000..86b8f0a --- /dev/null +++ b/landlock/errors/enum.HandleAccessesError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.HandleAccessesError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.PathBeneathError.html b/landlock/errors/enum.PathBeneathError.html new file mode 100644 index 0000000..d66ac0c --- /dev/null +++ b/landlock/errors/enum.PathBeneathError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.PathBeneathError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.PathFdError.html b/landlock/errors/enum.PathFdError.html new file mode 100644 index 0000000..82c43dc --- /dev/null +++ b/landlock/errors/enum.PathFdError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.PathFdError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.RestrictSelfError.html b/landlock/errors/enum.RestrictSelfError.html new file mode 100644 index 0000000..9c8f1e0 --- /dev/null +++ b/landlock/errors/enum.RestrictSelfError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.RestrictSelfError.html...
+ + + \ No newline at end of file diff --git a/landlock/errors/enum.RulesetError.html b/landlock/errors/enum.RulesetError.html new file mode 100644 index 0000000..869ce9e --- /dev/null +++ b/landlock/errors/enum.RulesetError.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.RulesetError.html...
+ + + \ No newline at end of file diff --git a/landlock/fn.path_beneath_rules.html b/landlock/fn.path_beneath_rules.html new file mode 100644 index 0000000..9d941c5 --- /dev/null +++ b/landlock/fn.path_beneath_rules.html @@ -0,0 +1,35 @@ +pub fn path_beneath_rules<I, P, A>(
+ paths: I,
+ access: A,
+) -> impl Iterator<Item = Result<PathBeneath<PathFd>, RulesetError>>
Helper to quickly create an iterator of PathBeneath rules.
+Silently ignores paths that cannot be opened, and automatically adjust access rights according +to file types when possible.
+use landlock::{
+ ABI, Access, AccessFs, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus, RulesetError,
+ path_beneath_rules,
+};
+
+fn restrict_thread() -> Result<(), RulesetError> {
+ let abi = ABI::V1;
+ let status = Ruleset::default()
+ .handle_access(AccessFs::from_all(abi))?
+ .create()?
+ // Read-only access to /usr, /etc and /dev.
+ .add_rules(path_beneath_rules(&["/usr", "/etc", "/dev"], AccessFs::from_read(abi)))?
+ // Read-write access to /home and /tmp.
+ .add_rules(path_beneath_rules(&["/home", "/tmp"], AccessFs::from_all(abi)))?
+ .restrict_self()?;
+ match status.ruleset {
+ // The FullyEnforced case must be tested by the developer.
+ RulesetStatus::FullyEnforced => println!("Fully sandboxed."),
+ RulesetStatus::PartiallyEnforced => println!("Partially sandboxed."),
+ // Users should be warned that they are not protected.
+ RulesetStatus::NotEnforced => println!("Not sandboxed! Please update your kernel."),
+ }
+ Ok(())
+}
Redirecting to ../../landlock/enum.AccessFs.html...
+ + + \ No newline at end of file diff --git a/landlock/fs/fn.path_beneath_rules.html b/landlock/fs/fn.path_beneath_rules.html new file mode 100644 index 0000000..db8131e --- /dev/null +++ b/landlock/fs/fn.path_beneath_rules.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/fn.path_beneath_rules.html...
+ + + \ No newline at end of file diff --git a/landlock/fs/struct.PathBeneath.html b/landlock/fs/struct.PathBeneath.html new file mode 100644 index 0000000..79705cb --- /dev/null +++ b/landlock/fs/struct.PathBeneath.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/struct.PathBeneath.html...
+ + + \ No newline at end of file diff --git a/landlock/fs/struct.PathFd.html b/landlock/fs/struct.PathFd.html new file mode 100644 index 0000000..31db2d0 --- /dev/null +++ b/landlock/fs/struct.PathFd.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/struct.PathFd.html...
+ + + \ No newline at end of file diff --git a/landlock/index.html b/landlock/index.html new file mode 100644 index 0000000..55144fe --- /dev/null +++ b/landlock/index.html @@ -0,0 +1,70 @@ +Landlock is a security feature available since Linux 5.13. +The goal is to enable to restrict ambient rights +(e.g., global filesystem access) +for a set of processes by creating safe security sandboxes as new security layers +in addition to the existing system-wide access-controls. +This kind of sandbox is expected to help mitigate the security impact of bugs, +unexpected or malicious behaviors in applications. +Landlock empowers any process, including unprivileged ones, to securely restrict themselves. +More information about Landlock can be found in the official website.
+This crate provides a safe abstraction for the Landlock system calls, along with some helpers.
+Minimum Supported Rust Version (MSRV): 1.63
+This crate is especially useful to protect users’ data by sandboxing:
+A simple example can be found with the path_beneath_rules()
helper.
+More complex examples can be found with the Ruleset
documentation
+and the sandboxer example.
This crate exposes the Landlock features available as of Linux 5.19 +and then inherits some kernel limitations +that will be addressed with future kernel releases +(e.g., arbitrary mounts are always denied).
+Types defined in this crate are designed to enable the strictest Landlock configuration
+for the given kernel on which the program runs.
+In the default best-effort mode,
+Ruleset
will determine compatibility
+with the intersection of the currently running kernel’s features
+and those required by the caller.
+This way, callers can distinguish between
+Landlock compatibility issues inherent to the current system
+(e.g., file names that don’t exist)
+and misconfiguration that should be fixed in the program
+(e.g., empty or inconsistent access rights).
+RulesetError
identifies such kind of errors.
With set_compatibility(CompatLevel::BestEffort)
,
+users of the crate may mark Landlock features that are deemed required
+and other features that may be downgraded to use lower security on systems
+where they can’t be enforced.
+It is discouraged to compare the system’s provided Landlock ABI version directly,
+as it is difficult to track detailed ABI differences
+which are handled thanks to the Compatible
trait.
To make it easier to migrate to a new version of this library,
+we use the builder pattern
+and designed objects to require the minimal set of method arguments.
+Most enum
are marked as non_exhaustive
to enable backward-compatible evolutions.
Developers should test their sandboxed applications
+with a kernel that supports all requested Landlock features
+and check that RulesetCreated::restrict_self()
returns a status matching
+Ok(RestrictionStatus { ruleset: RulesetStatus::FullyEnforced, no_new_privs: true, })
+to make sure everything works as expected in an enforced sandbox.
+Alternatively, using set_compatibility(CompatLevel::HardRequirement)
+will immediately inform about unsupported Landlock features.
+These configurations should only depend on the test environment
+(e.g. by checking an environment variable).
+However, applications should only check that no error is returned (i.e. Ok(_)
)
+and optionally log and inform users that the application is not fully sandboxed
+because of missing features from the running kernel.
make_bitflags!
provides a succint syntax for creating instances of
+BitFlags<T>
. Instead of repeating the name of your type for each flag
+you want to add, try make_bitflags!(Flags::{Foo | Bar})
.T
.
+T
must have the #[bitflags]
attribute applied.O_PATH
flag.RulesetCreated
+after calling restrict_self()
.Ruleset::create()
.Compatible
documentation.Redirecting to macro.make_bitflags.html...
+ + + \ No newline at end of file diff --git a/landlock/macro.make_bitflags.html b/landlock/macro.make_bitflags.html new file mode 100644 index 0000000..ea525f5 --- /dev/null +++ b/landlock/macro.make_bitflags.html @@ -0,0 +1,18 @@ +macro_rules! make_bitflags { + ( $enum:ident ::{ $($variant:ident)|* } ) => { ... }; +}
make_bitflags!
provides a succint syntax for creating instances of
+BitFlags<T>
. Instead of repeating the name of your type for each flag
+you want to add, try make_bitflags!(Flags::{Foo | Bar})
.
use enumflags2::{bitflags, make_bitflags};
+#[bitflags]
+#[repr(u8)]
+#[derive(Clone, Copy, Debug)]
+enum Test {
+ A = 1 << 0,
+ B = 1 << 1,
+ C = 1 << 2,
+}
+let x = make_bitflags!(Test::{A | C});
+assert_eq!(x, Test::A | Test::C);
Redirecting to ../../landlock/enum.AccessNet.html...
+ + + \ No newline at end of file diff --git a/landlock/net/struct.NetPort.html b/landlock/net/struct.NetPort.html new file mode 100644 index 0000000..898e381 --- /dev/null +++ b/landlock/net/struct.NetPort.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/struct.NetPort.html...
+ + + \ No newline at end of file diff --git a/landlock/ruleset/enum.RulesetStatus.html b/landlock/ruleset/enum.RulesetStatus.html new file mode 100644 index 0000000..c92e750 --- /dev/null +++ b/landlock/ruleset/enum.RulesetStatus.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/enum.RulesetStatus.html...
+ + + \ No newline at end of file diff --git a/landlock/ruleset/struct.RestrictionStatus.html b/landlock/ruleset/struct.RestrictionStatus.html new file mode 100644 index 0000000..63e2f53 --- /dev/null +++ b/landlock/ruleset/struct.RestrictionStatus.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/struct.RestrictionStatus.html...
+ + + \ No newline at end of file diff --git a/landlock/ruleset/struct.Ruleset.html b/landlock/ruleset/struct.Ruleset.html new file mode 100644 index 0000000..2906cf1 --- /dev/null +++ b/landlock/ruleset/struct.Ruleset.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/struct.Ruleset.html...
+ + + \ No newline at end of file diff --git a/landlock/ruleset/struct.RulesetCreated.html b/landlock/ruleset/struct.RulesetCreated.html new file mode 100644 index 0000000..79f9ca0 --- /dev/null +++ b/landlock/ruleset/struct.RulesetCreated.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/struct.RulesetCreated.html...
+ + + \ No newline at end of file diff --git a/landlock/ruleset/trait.Rule.html b/landlock/ruleset/trait.Rule.html new file mode 100644 index 0000000..1cb6b90 --- /dev/null +++ b/landlock/ruleset/trait.Rule.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/trait.Rule.html...
+ + + \ No newline at end of file diff --git a/landlock/ruleset/trait.RulesetAttr.html b/landlock/ruleset/trait.RulesetAttr.html new file mode 100644 index 0000000..f3e9c57 --- /dev/null +++ b/landlock/ruleset/trait.RulesetAttr.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/trait.RulesetAttr.html...
+ + + \ No newline at end of file diff --git a/landlock/ruleset/trait.RulesetCreatedAttr.html b/landlock/ruleset/trait.RulesetCreatedAttr.html new file mode 100644 index 0000000..35cff5e --- /dev/null +++ b/landlock/ruleset/trait.RulesetCreatedAttr.html @@ -0,0 +1,11 @@ + + + + +Redirecting to ../../landlock/trait.RulesetCreatedAttr.html...
+ + + \ No newline at end of file diff --git a/landlock/sidebar-items.js b/landlock/sidebar-items.js new file mode 100644 index 0000000..3121fb2 --- /dev/null +++ b/landlock/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ABI","AccessError","AccessFs","AccessNet","AddRuleError","AddRulesError","CompatError","CompatLevel","CreateRulesetError","HandleAccessError","HandleAccessesError","PathBeneathError","PathFdError","RestrictSelfError","RulesetError","RulesetStatus"],"fn":["path_beneath_rules"],"macro":["make_bitflags"],"struct":["BitFlags","NetPort","PathBeneath","PathFd","RestrictionStatus","Ruleset","RulesetCreated"],"trait":["Access","Compatible","Rule","RulesetAttr","RulesetCreatedAttr"]}; \ No newline at end of file diff --git a/landlock/struct.BitFlags.html b/landlock/struct.BitFlags.html new file mode 100644 index 0000000..c8edaa3 --- /dev/null +++ b/landlock/struct.BitFlags.html @@ -0,0 +1,399 @@ +pub struct BitFlags<T, N = <T as RawBitFlags>::Numeric> { /* private fields */ }
Represents a set of flags of some type T
.
+T
must have the #[bitflags]
attribute applied.
A BitFlags<T>
is as large as the T
itself,
+and stores one flag per bit.
PartialOrd
and Ord
To make it possible to use BitFlags
as the key of a
+[BTreeMap
][std::collections::BTreeMap], BitFlags
implements
+Ord
. There is no meaningful total order for bitflags,
+so the implementation simply compares the integer values of the bits.
Unfortunately, this means that comparing BitFlags
with an operator
+like <=
will compile, and return values that are probably useless
+and not what you expect. In particular, <=
does not check whether
+one value is a subset of the other. Use BitFlags::contains
for that.
Default
By default, creating an instance of BitFlags<T>
with Default
will result
+in an empty set. If that’s undesirable, you may customize this:
#[bitflags(default = B | C)]
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+enum MyFlag {
+ A = 0b0001,
+ B = 0b0010,
+ C = 0b0100,
+ D = 0b1000,
+}
+
+assert_eq!(BitFlags::default(), MyFlag::B | MyFlag::C);
BitFlags<T>
is marked with the #[repr(transparent)]
trait, meaning
+it can be safely transmuted into the corresponding numeric type.
Usually, the same can be achieved by using BitFlags::bits
in one
+direction, and BitFlags::from_bits
, BitFlags::from_bits_truncate
,
+or BitFlags::from_bits_unchecked
in the other direction. However,
+transmuting might still be useful if, for example, you’re dealing with
+an entire array of BitFlags
.
When transmuting into a BitFlags
, make sure that each set bit
+corresponds to an existing flag
+(cf. from_bits_unchecked
).
For example:
+ +#[bitflags]
+#[repr(u8)] // <-- the repr determines the numeric type
+#[derive(Copy, Clone)]
+enum TransmuteMe {
+ One = 1 << 0,
+ Two = 1 << 1,
+}
+
+// NOTE: we use a small, self-contained function to handle the slice
+// conversion to make sure the lifetimes are right.
+fn transmute_slice<'a>(input: &'a [BitFlags<TransmuteMe>]) -> &'a [u8] {
+ unsafe {
+ slice::from_raw_parts(input.as_ptr() as *const u8, input.len())
+ }
+}
+
+let many_flags = &[
+ TransmuteMe::One.into(),
+ TransmuteMe::One | TransmuteMe::Two,
+];
+
+let as_nums = transmute_slice(many_flags);
+assert_eq!(as_nums, &[0b01, 0b11]);
You might expect this struct to be defined as
+ +struct BitFlags<T: BitFlag> {
+ value: T::Numeric
+}
Ideally, that would be the case. However, because const fn
s cannot
+have trait bounds in current Rust, this would prevent us from providing
+most const fn
APIs. As a workaround, we define BitFlags
with two
+type parameters, with a default for the second one:
struct BitFlags<T, N = <T as BitFlag>::Numeric> {
+ value: N,
+ marker: PhantomData<T>,
+}
Manually providing a type for the N
type parameter shouldn’t ever
+be necessary.
The types substituted for T
and N
must always match, creating a
+BitFlags
value where that isn’t the case is only possible with
+incorrect unsafe code.
Iterate over the BitFlags
.
let flags = make_bitflags!(MyFlag::{A | C});
+
+flags.iter()
+ .for_each(|flag| println!("{:?}", flag));
An empty BitFlags
. Equivalent to empty()
,
+but works in a const context.
A BitFlags
with all flags set. Equivalent to all()
,
+but works in a const context.
A [ConstToken
] for this type of flag.
Create a new BitFlags unsafely, without checking if the bits form +a valid bit pattern for the type.
+Const variant of
+from_bits_unchecked
.
Consider using
+from_bits_truncate_c
instead.
All bits set in val
must correspond to a value of the enum.
Create a BitFlags<T>
from an underlying bitwise value. If any
+invalid bits are set, ignore them.
#[bitflags]
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum MyFlag {
+ One = 1 << 0,
+ Two = 1 << 1,
+ Three = 1 << 2,
+}
+
+const FLAGS: BitFlags<MyFlag> =
+ BitFlags::<MyFlag>::from_bits_truncate_c(0b10101010, BitFlags::CONST_TOKEN);
+assert_eq!(FLAGS, MyFlag::Two);
Bitwise OR — return value contains flag if either argument does.
+Also available as a | b
, but operator overloads are not usable
+in const fn
s at the moment.
Bitwise AND — return value contains flag if both arguments do.
+Also available as a & b
, but operator overloads are not usable
+in const fn
s at the moment.
Bitwise NOT — return value contains flag if argument doesn’t.
+Also available as !a
, but operator overloads are not usable
+in const fn
s at the moment.
Moreover, due to const fn
limitations, not_c
needs a
+[ConstToken
] as an argument.
#[bitflags]
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum MyFlag {
+ One = 1 << 0,
+ Two = 1 << 1,
+ Three = 1 << 2,
+}
+
+const FLAGS: BitFlags<MyFlag> = make_bitflags!(MyFlag::{One | Two});
+const NEGATED: BitFlags<MyFlag> = FLAGS.not_c(BitFlags::CONST_TOKEN);
+assert_eq!(NEGATED, MyFlag::Three);
Create a BitFlags
if the raw value provided does not contain
+any illegal flags.
See also: [a convenience re-export in the BitFlag
trait][BitFlag::from_bits],
+which can help avoid the need for type hints.
#[bitflags]
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+enum MyFlag {
+ One = 1 << 0,
+ Two = 1 << 1,
+ Three = 1 << 2,
+}
+
+let flags: BitFlags<MyFlag> = BitFlags::from_bits(0b11).unwrap();
+assert_eq!(flags.contains(MyFlag::One), true);
+assert_eq!(flags.contains(MyFlag::Two), true);
+assert_eq!(flags.contains(MyFlag::Three), false);
+let invalid = BitFlags::<MyFlag>::from_bits(1 << 3);
+assert!(invalid.is_err());
Create a BitFlags
from an underlying bitwise value. If any
+invalid bits are set, ignore them.
See also: [a convenience re-export in the BitFlag
trait][BitFlag::from_bits_truncate],
+which can help avoid the need for type hints.
#[bitflags]
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum MyFlag {
+ One = 1 << 0,
+ Two = 1 << 1,
+ Three = 1 << 2,
+}
+
+let flags: BitFlags<MyFlag> = BitFlags::from_bits_truncate(0b1_1011);
+assert_eq!(flags.contains(MyFlag::One), true);
+assert_eq!(flags.contains(MyFlag::Two), true);
+assert_eq!(flags.contains(MyFlag::Three), false);
Create a new BitFlags unsafely, without checking if the bits form +a valid bit pattern for the type.
+Consider using from_bits
+or from_bits_truncate
instead.
All bits set in val
must correspond to a value of the enum.
#[bitflags]
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum MyFlag {
+ One = 1 << 0,
+ Two = 1 << 1,
+ Three = 1 << 2,
+}
+
+let flags: BitFlags<MyFlag> = unsafe {
+ BitFlags::from_bits_unchecked(0b011)
+};
+
+assert_eq!(flags.contains(MyFlag::One), true);
+assert_eq!(flags.contains(MyFlag::Two), true);
+assert_eq!(flags.contains(MyFlag::Three), false);
Turn a T
into a BitFlags<T>
. Also available as flag.into()
.
Create a BitFlags
with no flags set (in other words, with a value of 0
).
See also: [BitFlag::empty
], a convenience reexport;
+BitFlags::EMPTY
, the same functionality available
+as a constant for const fn
code.
#[bitflags]
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum MyFlag {
+ One = 1 << 0,
+ Two = 1 << 1,
+ Three = 1 << 2,
+}
+
+let empty: BitFlags<MyFlag> = BitFlags::empty();
+assert!(empty.is_empty());
+assert_eq!(empty.contains(MyFlag::One), false);
+assert_eq!(empty.contains(MyFlag::Two), false);
+assert_eq!(empty.contains(MyFlag::Three), false);
Create a BitFlags
with all flags set.
See also: [BitFlag::all
], a convenience reexport;
+BitFlags::ALL
, the same functionality available
+as a constant for const fn
code.
#[bitflags]
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum MyFlag {
+ One = 1 << 0,
+ Two = 1 << 1,
+ Three = 1 << 2,
+}
+
+let empty: BitFlags<MyFlag> = BitFlags::all();
+assert!(empty.is_all());
+assert_eq!(empty.contains(MyFlag::One), true);
+assert_eq!(empty.contains(MyFlag::Two), true);
+assert_eq!(empty.contains(MyFlag::Three), true);
If exactly one flag is set, the flag is returned. Otherwise, returns None
.
See also Itertools::exactly_one
.
Returns the underlying bitwise value.
+ +#[bitflags]
+#[repr(u8)]
+#[derive(Clone, Copy)]
+enum Flags {
+ Foo = 1 << 0,
+ Bar = 1 << 1,
+}
+
+let both_flags = Flags::Foo | Flags::Bar;
+assert_eq!(both_flags.bits(), 0b11);
Returns true if at least one flag is shared.
+Inserts the flags into the BitFlag
+Inserts if cond
holds, else removes
#[bitflags]
+#[derive(Clone, Copy, PartialEq, Debug)]
+#[repr(u8)]
+enum MyFlag {
+ A = 1 << 0,
+ B = 1 << 1,
+ C = 1 << 2,
+}
+
+let mut state = MyFlag::A | MyFlag::C;
+state.set(MyFlag::A | MyFlag::B, false);
+
+// Because the condition was false, both
+// `A` and `B` are removed from the set
+assert_eq!(state, MyFlag::C);
&=
operation. Read more|=
operation. Read more^=
operation. Read moreThe default value returned is one with all flags unset, i. e. empty
,
+unless customized.
extend_one
)extend_one
)self
and other
) and is used by the <=
+operator. Read moreclone_to_uninit
)clone_to_uninit
)pub struct NetPort { /* private fields */ }
Landlock rule for a network port.
+use landlock::{AccessNet, NetPort};
+
+fn bind_http() -> NetPort {
+ NetPort::new(80, AccessNet::BindTcp)
+}
set_compatibility()
. Read moreset_compatibility()
. Read morepub struct PathBeneath<F> { /* private fields */ }
Landlock rule for a file hierarchy.
+use landlock::{AccessFs, PathBeneath, PathFd, PathFdError};
+
+fn home_dir() -> Result<PathBeneath<PathFd>, PathFdError> {
+ Ok(PathBeneath::new(PathFd::new("/home")?, AccessFs::ReadDir))
+}
set_compatibility()
. Read moreset_compatibility()
. Read morepub struct PathFd { /* private fields */ }
Simple helper to open a file or a directory with the O_PATH
flag.
This is the recommended way to identify a path
+and manage the lifetime of the underlying opened file descriptor.
+Indeed, using other AsFd
implementations such as File
brings more complexity
+and may lead to unexpected errors (e.g., denied access).
use landlock::{AccessFs, PathBeneath, PathFd, PathFdError};
+
+fn allowed_root_dir(access: AccessFs) -> Result<PathBeneath<PathFd>, PathFdError> {
+ let fd = PathFd::new("/")?;
+ Ok(PathBeneath::new(fd, access))
+}
#[non_exhaustive]pub struct RestrictionStatus {
+ pub ruleset: RulesetStatus,
+ pub no_new_privs: bool,
+}
Status of a RulesetCreated
+after calling restrict_self()
.
Struct { .. }
syntax; cannot be matched against without a wildcard ..
; and struct update syntax will not work.ruleset: RulesetStatus
Status of the Landlock ruleset enforcement.
+no_new_privs: bool
Status of prctl(2)
’s PR_SET_NO_NEW_PRIVS
enforcement.
self
and other
values to be equal, and is used
+by ==
.pub struct Ruleset { /* private fields */ }
Landlock ruleset builder.
+Ruleset
enables to create a Landlock ruleset in a flexible way
+following the builder pattern.
+Most build steps return a Result
with RulesetError
.
You should probably not create more than one ruleset per application. +Creating multiple rulesets is only useful when gradually restricting an application +(e.g., a first set of generic restrictions before reading any file, +then a second set of tailored restrictions after reading the configuration).
+Simple helper handling only Landlock-related errors.
+ +use landlock::{
+ Access, 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,
+{
+ // The Landlock ABI should be incremented (and tested) regularly.
+ 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");
More generic helper handling a set of file hierarchies
+and multiple types of error (i.e. RulesetError
+and PathFdError
.
use landlock::{
+ Access, AccessFs, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
+ RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
+};
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+enum MyRestrictError {
+ #[error(transparent)]
+ Ruleset(#[from] RulesetError),
+ #[error(transparent)]
+ AddRule(#[from] PathFdError),
+}
+
+fn restrict_paths(hierarchies: &[&str]) -> Result<RestrictionStatus, MyRestrictError> {
+ // The Landlock ABI should be incremented (and tested) regularly.
+ 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_rules(
+ hierarchies
+ .iter()
+ .map::<Result<_, MyRestrictError>, _>(|p| {
+ Ok(PathBeneath::new(PathFd::new(p)?, access_read))
+ }),
+ )?
+ .restrict_self()?)
+}
+
+let status = restrict_paths(&["/usr", "/home"]).expect("failed to build the ruleset");
Attempts to create a real Landlock ruleset (if supported by the running kernel).
+The returned RulesetCreated
is also a builder.
On error, returns a wrapped CreateRulesetError
.
set_compatibility()
. Read moreset_compatibility()
. Read moreReturns a new Ruleset
.
+This call automatically probes the running kernel to know if it supports Landlock.
To be able to successfully call create()
,
+it is required to set the handled accesses with
+handle_access()
.
handle_access()
will be interpreted as logical ORs
+with the previous handled accesses. Read morehandle_access()
will be interpreted as logical ORs
+with the previous handled accesses. Read morepub struct RulesetCreated { /* private fields */ }
Ruleset created with Ruleset::create()
.
Attempts to restrict the calling thread with the ruleset
+according to the best-effort configuration
+(see RulesetCreated::set_compatibility()
and CompatLevel::BestEffort
).
+Call prctl(2)
with the PR_SET_NO_NEW_PRIVS
+according to the ruleset configuration.
On error, returns a wrapped RestrictSelfError
.
Creates a new RulesetCreated
instance by duplicating the underlying file descriptor.
+Rule modification will affect both RulesetCreated
instances simultaneously.
On error, returns std::io::Error
.
set_compatibility()
. Read moreset_compatibility()
. Read moreprctl(2)
with the PR_SET_NO_NEW_PRIVS
command
+in restrict_self()
. Read moreprctl(2)
with the PR_SET_NO_NEW_PRIVS
command
+in restrict_self()
. Read morepub trait Compatible: Sized + OptionCompatLevelMut {
+ // Provided methods
+ fn set_compatibility(self, level: CompatLevel) -> Self { ... }
+ fn set_best_effort(self, best_effort: bool) -> Self
+ where Self: Sized { ... }
+}
Properly handles runtime unsupported features.
+This guarantees consistent behaviors across crate users +and runtime kernels even if this crate get new features. +It eases backward compatibility and enables future-proofness.
+Landlock is a security feature designed to help improve security of a running system +thanks to application developers. +To protect users as much as possible, +compatibility with the running system should then be handled in a best-effort way, +contrary to common system features. +In some circumstances +(e.g. applications carefully designed to only be run with a specific set of kernel features), +it may be required to error out if some of these features are not available +and will then not be enforced.
+To enable a best-effort security approach,
+Landlock features that are not supported by the running system
+are silently ignored by default,
+which is a sane choice for most use cases.
+However, on some rare circumstances,
+developers may want to have some guarantees that their applications
+will not run if a certain level of sandboxing is not possible.
+If we really want to error out when not all our requested requirements are met,
+then we can configure it with set_compatibility()
.
The Compatible
trait is implemented for all object builders
+(e.g. Ruleset
).
+Such builders have a set of methods to incrementally build an object.
+These build methods rely on kernel features that may not be available at runtime.
+The set_compatibility()
method enables to control the effect of
+the following build method calls starting after the set_compatibility()
call.
+Such effect can be:
CompatLevel::BestEffort
);CompatLevel::SoftRequirement
);CompatLevel::HardRequirement
).Taking Ruleset
as an example,
+the handle_access()
build method
+returns a Result
that can be Err(RulesetError)
+with a nested CompatError
.
+Such error can only occur with a running Linux kernel not supporting the requested
+Landlock accesses and if the current compatibility level is
+CompatLevel::HardRequirement
.
+However, such error is not possible with CompatLevel::BestEffort
+nor CompatLevel::SoftRequirement
.
The order of this call is important because
+it defines the behavior of the following build method calls that return a Result
.
+If set_compatibility(CompatLevel::HardRequirement)
is called on an object,
+then a CompatError
may be returned for the next method calls,
+until the next call to set_compatibility()
.
+This enables to change the behavior of a set of build method calls,
+for instance to be sure that the sandbox will at least restrict some access rights.
New objects inherit the compatibility configuration of their parents, if any.
+For instance, Ruleset::create()
returns
+a RulesetCreated
object that inherits the
+Ruleset
’s compatibility configuration.
SoftRequirement
Let’s say an application legitimately needs to rename files between directories.
+Because of previous Landlock limitations,
+this was forbidden with the first version of Landlock,
+but it is now handled starting with the second version.
+For this use case, we only want the application to be sandboxed
+if we have the guarantee that it will not break a legitimate usage (i.e. rename files).
+We then create a ruleset which will either support file renaming
+(thanks to AccessFs::Refer
) or silently do nothing.
use landlock::*;
+
+fn ruleset_handling_renames() -> Result<RulesetCreated, RulesetError> {
+ Ok(Ruleset::default()
+ // This ruleset must either handle the AccessFs::Refer right,
+ // or it must silently ignore the whole sandboxing.
+ .set_compatibility(CompatLevel::SoftRequirement)
+ .handle_access(AccessFs::Refer)?
+ // However, this ruleset may also handle other (future) access rights
+ // if they are supported by the running kernel.
+ .set_compatibility(CompatLevel::BestEffort)
+ .handle_access(AccessFs::from_all(ABI::V5))?
+ .create()?)
+}
HardRequirement
Security-dedicated applications may want to ensure that +an untrusted software component is subject to a minimum of restrictions before launching it. +In this case, we want to create a ruleset which will at least support +all restrictions provided by the first version of Landlock, +and opportunistically handle restrictions supported by newer kernels.
+ +use landlock::*;
+
+fn ruleset_fragile() -> Result<RulesetCreated, RulesetError> {
+ Ok(Ruleset::default()
+ // This ruleset must either handle at least all accesses defined by
+ // the first Landlock version (e.g. AccessFs::WriteFile),
+ // or the following handle_access() call must return a wrapped
+ // AccessError<AccessFs>::Incompatible error.
+ .set_compatibility(CompatLevel::HardRequirement)
+ .handle_access(AccessFs::from_all(ABI::V1))?
+ // However, this ruleset may also handle new access rights
+ // (e.g. AccessFs::Refer defined by the second version of Landlock)
+ // if they are supported by the running kernel,
+ // but without returning any error otherwise.
+ .set_compatibility(CompatLevel::BestEffort)
+ .handle_access(AccessFs::from_all(ABI::V5))?
+ .create()?)
+}
Cf. set_compatibility()
:
set_best_effort(true)
translates to set_compatibility(CompatLevel::BestEffort)
.
set_best_effort(false)
translates to set_compatibility(CompatLevel::HardRequirement)
.
pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
+ // Provided method
+ fn handle_access<T, U>(self, access: T) -> Result<Self, RulesetError>
+ where T: Into<BitFlags<U>>,
+ U: Access { ... }
+}
Attempts to add a set of access rights that will be supported by this ruleset.
+By default, all actions requiring these access rights will be denied.
+Consecutive calls to handle_access()
will be interpreted as logical ORs
+with the previous handled accesses.
On error, returns a wrapped HandleAccessesError
.
+E.g., RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError<AccessFs>))
pub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {
+ // Provided methods
+ fn add_rule<T, U>(self, rule: T) -> Result<Self, RulesetError>
+ where T: Rule<U>,
+ U: Access { ... }
+ fn add_rules<I, T, U, E>(self, rules: I) -> Result<Self, E>
+ where I: IntoIterator<Item = Result<T, E>>,
+ T: Rule<U>,
+ U: Access,
+ E: From<RulesetError> { ... }
+ fn set_no_new_privs(self, no_new_privs: bool) -> Self { ... }
+}
Attempts to add a new rule to the ruleset.
+On error, returns a wrapped AddRulesError
.
Attempts to add a set of new rules to the ruleset.
+On error, returns a (double) wrapped AddRulesError
.
Create a custom iterator to read paths from environment variable.
+ +use landlock::{
+ Access, AccessFs, BitFlags, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
+ RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
+};
+use std::env;
+use std::ffi::OsStr;
+use std::os::unix::ffi::{OsStrExt, OsStringExt};
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+enum PathEnvError<'a> {
+ #[error(transparent)]
+ Ruleset(#[from] RulesetError),
+ #[error(transparent)]
+ AddRuleIter(#[from] PathFdError),
+ #[error("missing environment variable {0}")]
+ MissingVar(&'a str),
+}
+
+struct PathEnv {
+ paths: Vec<u8>,
+ access: BitFlags<AccessFs>,
+}
+
+impl PathEnv {
+ // env_var is the name of an environment variable
+ // containing paths requested to be allowed.
+ // Paths are separated with ":", e.g. "/bin:/lib:/usr:/proc".
+ // In case an empty string is provided,
+ // no restrictions are applied.
+ // `access` is the set of access rights allowed for each of the parsed paths.
+ fn new<'a>(
+ env_var: &'a str, access: BitFlags<AccessFs>
+ ) -> Result<Self, PathEnvError<'a>> {
+ Ok(Self {
+ paths: env::var_os(env_var)
+ .ok_or(PathEnvError::MissingVar(env_var))?
+ .into_vec(),
+ access,
+ })
+ }
+
+ fn iter(
+ &self,
+ ) -> impl Iterator<Item = Result<PathBeneath<PathFd>, PathEnvError<'static>>> + '_ {
+ let is_empty = self.paths.is_empty();
+ self.paths
+ .split(|b| *b == b':')
+ // Skips the first empty element from of an empty string.
+ .skip_while(move |_| is_empty)
+ .map(OsStr::from_bytes)
+ .map(move |path|
+ Ok(PathBeneath::new(PathFd::new(path)?, self.access)))
+ }
+}
+
+fn restrict_env() -> Result<RestrictionStatus, PathEnvError<'static>> {
+ Ok(Ruleset::default()
+ .handle_access(AccessFs::from_all(ABI::V1))?
+ .create()?
+ // In the shell: export EXECUTABLE_PATH="/usr:/bin:/sbin"
+ .add_rules(PathEnv::new("EXECUTABLE_PATH", AccessFs::Execute.into())?.iter())?
+ .restrict_self()?)
+}
Configures the ruleset to call prctl(2)
with the PR_SET_NO_NEW_PRIVS
command
+in restrict_self()
.
This prctl(2)
call is never ignored, even if an error was encountered on a Ruleset
or
+RulesetCreated
method call while CompatLevel::SoftRequirement
was set.
BitFlags
with all flags set. Equivalent to all()
, but …\nFile system access right.\nNetwork access right.\nThe landlock_add_rule()
system call failed.\nIdentifies errors when adding a rule to a ruleset.\nIdentifies errors when adding rules to a ruleset thanks to …\nTakes into account the build requests if they are …\nBind to a TCP port.\nRepresents a set of flags of some type T
. T
must have the …\nA ConstToken
for this type of flag.\nSee the Compatible
documentation.\nProperly handles runtime unsupported features.\nConnect to a TCP port.\nThe landlock_create_ruleset()
system call failed.\nIdentifies errors when creating a ruleset.\nThis error is returned by RulesetCreatedAttr::add_rule()
…\nAn empty BitFlags
. Equivalent to empty()
, but works in a …\nThe access-rights set is empty, which doesn’t make sense …\nExecute a file.\nAll requested restrictions are enforced.\nIdentifies errors when updating the ruleset’s handled …\nTakes into account the build requests if they are …\nThe best-effort approach was (deliberately) disabled and …\nSend IOCL commands to a device file.\nCreate (or rename or link) a block device.\nCreate (or rename or link) a character device.\nCreate (or rename) a directory.\nCreate (or rename or link) a named pipe.\nCreate (or rename or link) a regular file.\nCreate (or rename or link) a UNIX domain socket.\nCreate (or rename or link) a symbolic link.\nMissing call to RulesetAttr::handle_access()
.\nLandlock rule for a network port.\nThe running system doesn’t support Landlock or a subset …\nThe open()
system call failed.\nThe best-effort approach was (deliberately) disabled and …\nSome requested restrictions are enforced, following a …\nLandlock rule for a file hierarchy.\nSimple helper to open a file or a directory with the O_PATH
…\nOpen a directory or list its content.\nOpen a file with read access.\nLink or rename a file from or to a different directory.\nRemove an empty directory or rename one.\nUnlink (or rename) a file.\nThe landlock_restrict_self()
system call failed.\nStatus of a RulesetCreated
after calling restrict_self()
.\nLandlock ruleset builder.\nRuleset created with Ruleset::create()
.\nMaps to all errors that can be returned by a ruleset …\nEnforcement status of a ruleset.\nThe prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
system call …\nTakes into account the build requests if they are …\nTo check that access-rights are consistent with a file …\nTruncate a file with truncate(2)
, ftruncate(2)
, creat(2)
, …\nThe rule’s access-rights are not all handled by the …\nThe access-rights set was forged with the unsafe …\nKernel not supporting Landlock, either because it is not …\nFirst Landlock ABI, introduced with Linux 5.13.\nSecond Landlock ABI, introduced with Linux 5.19.\nThird Landlock ABI, introduced with Linux 6.2.\nFourth Landlock ABI, introduced with Linux 6.7.\nFifth Landlock ABI, introduced with Linux 6.10.\nOpen a file with write access.\nAttempts to add a new rule to the ruleset.\nAttempts to add a new rule to the ruleset.\nAttempts to add a set of new rules to the ruleset.\nAttempts to add a set of new rules to the ruleset.\nCreate a BitFlags
with all flags set.\nReturns the underlying bitwise value.\nReturns the underlying bitwise value.\nReturns true if all flags are contained.\nAttempts to create a real Landlock ruleset (if supported …\nReturns a new Ruleset
. This call automatically probes the …\nCreate a BitFlags
with no flags set (in other words, with …\nIf exactly one flag is set, the flag is returned. …\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nReturns the argument unchanged.\nGets the access rights defined by a specific ABI
.\nUnion of from_read()
and from_write()
.\nCreate a BitFlags
if the raw value provided does not …\nCreate a BitFlags
from an underlying bitwise value. If any …\nCreate a BitFlags<T>
from an underlying bitwise value. If …\nCreate a new BitFlags unsafely, without checking if the …\nCreate a new BitFlags unsafely, without checking if the …\nGets the access rights legitimate for non-directory files.\nTurn a T
into a BitFlags<T>
. Also available as flag.into()
.\nGets the access rights identified as read-only according …\nGets the access rights identified as write-only according …\nAttempts to add a set of access rights that will be …\nAttempts to add a set of access rights that will be …\nInserts the flags into the BitFlag\nBitwise AND — return value contains flag if both …\nReturns true if at least one flag is shared.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nCalls U::from(self)
.\nReturns true if all flags are set\nReturns true if no flag is set\nIterate over the BitFlags
.\nReturns the number of flags set.\nmake_bitflags!
provides a succint syntax for creating …\nCreates a new PathBeneath
rule identifying the parent
…\nCreates a new TCP port rule.\nStatus of prctl(2)
’s PR_SET_NO_NEW_PRIVS
enforcement.\nBitwise NOT — return value contains flag if argument …\nHelper to quickly create an iterator of PathBeneath rules.\nRemoves the matching flags\nAttempts to restrict the calling thread with the ruleset …\nStatus of the Landlock ruleset enforcement.\nInserts if cond
holds, else removes\nCf. set_compatibility()
:\nCf. set_compatibility()
:\nTo enable a best-effort security approach, Landlock …\nTo enable a best-effort security approach, Landlock …\nConfigures the ruleset to call prctl(2)
with the …\nConfigures the ruleset to call prctl(2)
with the …\nToggles the matching bits\nCreates a new RulesetCreated
instance by duplicating the …\nBitwise OR — return value contains flag if either …")
\ No newline at end of file
diff --git a/settings.html b/settings.html
new file mode 100644
index 0000000..ff15de5
--- /dev/null
+++ b/settings.html
@@ -0,0 +1 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +
use crate::{
+ AccessError, AddRuleError, AddRulesError, BitFlags, CompatError, CompatResult,
+ HandleAccessError, HandleAccessesError, Ruleset, TailoredCompatLevel, TryCompat, ABI,
+};
+use enumflags2::BitFlag;
+
+#[cfg(test)]
+use crate::{make_bitflags, AccessFs, CompatLevel, CompatState, Compatibility};
+
+pub trait Access: PrivateAccess {
+ /// Gets the access rights defined by a specific [`ABI`].
+ fn from_all(abi: ABI) -> BitFlags<Self>;
+}
+
+pub trait PrivateAccess: BitFlag {
+ fn ruleset_handle_access(
+ ruleset: &mut Ruleset,
+ access: BitFlags<Self>,
+ ) -> Result<(), HandleAccessesError>
+ where
+ Self: Access;
+
+ fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError
+ where
+ Self: Access;
+
+ fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError
+ where
+ Self: Access;
+}
+
+// Creates an illegal/overflowed BitFlags<T> with all its bits toggled, including undefined ones.
+fn full_negation<T>(flags: BitFlags<T>) -> BitFlags<T>
+where
+ T: Access,
+{
+ unsafe { BitFlags::<T>::from_bits_unchecked(!flags.bits()) }
+}
+
+#[test]
+fn bit_flags_full_negation() {
+ let scoped_negation = !BitFlags::<AccessFs>::all();
+ assert_eq!(scoped_negation, BitFlags::<AccessFs>::empty());
+ // !BitFlags::<AccessFs>::all() could be equal to full_negation(BitFlags::<AccessFs>::all()))
+ // if all the 64-bits would be used, which is not currently the case.
+ assert_ne!(scoped_negation, full_negation(BitFlags::<AccessFs>::all()));
+}
+
+impl<A> TailoredCompatLevel for BitFlags<A> where A: Access {}
+
+impl<A> TryCompat<A> for BitFlags<A>
+where
+ A: Access,
+{
+ fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>> {
+ if self.is_empty() {
+ // Empty access-rights would result to a runtime error.
+ Err(AccessError::Empty.into())
+ } else if !Self::all().contains(*self) {
+ // Unknown access-rights (at build time) would result to a runtime error.
+ // This can only be reached by using the unsafe BitFlags::from_bits_unchecked().
+ Err(AccessError::Unknown {
+ access: *self,
+ unknown: *self & full_negation(Self::all()),
+ }
+ .into())
+ } else {
+ let compat = *self & A::from_all(abi);
+ let ret = if compat.is_empty() {
+ Ok(CompatResult::No(
+ AccessError::Incompatible { access: *self }.into(),
+ ))
+ } else if compat != *self {
+ let error = AccessError::PartiallyCompatible {
+ access: *self,
+ incompatible: *self & full_negation(compat),
+ }
+ .into();
+ Ok(CompatResult::Partial(error))
+ } else {
+ Ok(CompatResult::Full)
+ };
+ *self = compat;
+ ret
+ }
+ }
+}
+
+#[test]
+fn compat_bit_flags() {
+ use crate::ABI;
+
+ let mut compat: Compatibility = ABI::V1.into();
+ assert!(compat.state == CompatState::Init);
+
+ let ro_access = make_bitflags!(AccessFs::{Execute | ReadFile | ReadDir});
+ assert_eq!(
+ ro_access,
+ ro_access
+ .try_compat(compat.abi(), compat.level, &mut compat.state)
+ .unwrap()
+ .unwrap()
+ );
+ assert!(compat.state == CompatState::Full);
+
+ let empty_access = BitFlags::<AccessFs>::empty();
+ assert!(matches!(
+ empty_access
+ .try_compat(compat.abi(), compat.level, &mut compat.state)
+ .unwrap_err(),
+ CompatError::Access(AccessError::Empty)
+ ));
+
+ let all_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked(1 << 63) };
+ assert!(matches!(
+ all_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
+ CompatError::Access(AccessError::Unknown { access, unknown }) if access == all_unknown_access && unknown == all_unknown_access
+ ));
+ // An error makes the state final.
+ assert!(compat.state == CompatState::Dummy);
+
+ let some_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked(1 << 63 | 1) };
+ assert!(matches!(
+ some_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
+ CompatError::Access(AccessError::Unknown { access, unknown }) if access == some_unknown_access && unknown == all_unknown_access
+ ));
+ assert!(compat.state == CompatState::Dummy);
+
+ compat = ABI::Unsupported.into();
+
+ // Tests that the ruleset is marked as unsupported.
+ assert!(compat.state == CompatState::Init);
+
+ // Access-rights are valid (but ignored) when they are not required for the current ABI.
+ assert_eq!(
+ None,
+ ro_access
+ .try_compat(compat.abi(), compat.level, &mut compat.state)
+ .unwrap()
+ );
+
+ assert!(compat.state == CompatState::No);
+
+ // Access-rights are not valid when they are required for the current ABI.
+ compat.level = Some(CompatLevel::HardRequirement);
+ assert!(matches!(
+ ro_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
+ CompatError::Access(AccessError::Incompatible { access }) if access == ro_access
+ ));
+
+ compat = ABI::V1.into();
+
+ // Tests that the ruleset is marked as the unknown compatibility state.
+ assert!(compat.state == CompatState::Init);
+
+ // Access-rights are valid (but ignored) when they are not required for the current ABI.
+ assert_eq!(
+ ro_access,
+ ro_access
+ .try_compat(compat.abi(), compat.level, &mut compat.state)
+ .unwrap()
+ .unwrap()
+ );
+
+ // Tests that the ruleset is in an unsupported state, which is important to be able to still
+ // enforce no_new_privs.
+ assert!(compat.state == CompatState::Full);
+
+ let v2_access = ro_access | AccessFs::Refer;
+
+ // Access-rights are not valid when they are required for the current ABI.
+ compat.level = Some(CompatLevel::HardRequirement);
+ assert!(matches!(
+ v2_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(),
+ CompatError::Access(AccessError::PartiallyCompatible { access, incompatible })
+ if access == v2_access && incompatible == AccessFs::Refer
+ ));
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +
use crate::{uapi, Access, CompatError};
+
+#[cfg(test)]
+use std::convert::TryInto;
+#[cfg(test)]
+use strum::{EnumCount, IntoEnumIterator};
+#[cfg(test)]
+use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
+
+/// Version of the Landlock [ABI](https://en.wikipedia.org/wiki/Application_binary_interface).
+///
+/// `ABI` enables getting the features supported by a specific Landlock ABI
+/// (without relying on the kernel version which may not be accessible or patched).
+/// For example, [`AccessFs::from_all(ABI::V1)`](Access::from_all)
+/// gets all the file system access rights defined by the first version.
+///
+/// Without `ABI`, it would be hazardous to rely on the the full set of access flags
+/// (e.g., `BitFlags::<AccessFs>::all()` or `BitFlags::ALL`),
+/// a moving target that would change the semantics of your Landlock rule
+/// when migrating to a newer version of this crate.
+/// Indeed, a simple `cargo update` or `cargo install` run by any developer
+/// can result in a new version of this crate (fixing bugs or bringing non-breaking changes).
+/// This crate cannot give any guarantee concerning the new restrictions resulting from
+/// these unknown bits (i.e. access rights) that would not be controlled by your application but by
+/// a future version of this crate instead.
+/// Because we cannot know what the effect on your application of an unknown restriction would be
+/// when handling an untested Landlock access right (i.e. denied-by-default access),
+/// it could trigger bugs in your application.
+///
+/// This crate provides a set of tools to sandbox as much as possible
+/// while guaranteeing a consistent behavior thanks to the [`Compatible`] methods.
+/// You should also test with different relevant kernel versions,
+/// see [landlock-test-tools](https://github.com/landlock-lsm/landlock-test-tools) and
+/// [CI integration](https://github.com/landlock-lsm/rust-landlock/pull/41).
+///
+/// This way, we can have the guarantee that the use of a set of tested Landlock ABI works as
+/// expected because features brought by newer Landlock ABI will never be enabled by default
+/// (cf. [Linux kernel compatibility contract](https://docs.kernel.org/userspace-api/landlock.html#compatibility)).
+///
+/// In a nutshell, test the access rights you request on a kernel that support them and
+/// on a kernel that doesn't support them.
+#[cfg_attr(
+ test,
+ derive(Debug, PartialEq, Eq, PartialOrd, EnumIter, EnumCountMacro)
+)]
+#[derive(Copy, Clone)]
+#[non_exhaustive]
+pub enum ABI {
+ /// Kernel not supporting Landlock, either because it is not built with Landlock
+ /// or Landlock is not enabled at boot.
+ Unsupported = 0,
+ /// First Landlock ABI, introduced with
+ /// [Linux 5.13](https://git.kernel.org/stable/c/17ae69aba89dbfa2139b7f8024b757ab3cc42f59).
+ V1 = 1,
+ /// Second Landlock ABI, introduced with
+ /// [Linux 5.19](https://git.kernel.org/stable/c/cb44e4f061e16be65b8a16505e121490c66d30d0).
+ V2 = 2,
+ /// Third Landlock ABI, introduced with
+ /// [Linux 6.2](https://git.kernel.org/stable/c/299e2b1967578b1442128ba8b3e86ed3427d3651).
+ V3 = 3,
+ /// Fourth Landlock ABI, introduced with
+ /// [Linux 6.7](https://git.kernel.org/stable/c/136cc1e1f5be75f57f1e0404b94ee1c8792cb07d).
+ V4 = 4,
+ /// Fifth Landlock ABI, introduced with
+ /// [Linux 6.10](https://git.kernel.org/stable/c/2fc0e7892c10734c1b7c613ef04836d57d4676d5).
+ V5 = 5,
+}
+
+impl ABI {
+ // Must remain private to avoid inconsistent behavior by passing Ok(self) to a builder method,
+ // e.g. to make it impossible to call ruleset.handle_fs(ABI::new_current()?)
+ fn new_current() -> Self {
+ ABI::from(unsafe {
+ // Landlock ABI version starts at 1 but errno is only set for negative values.
+ uapi::landlock_create_ruleset(
+ std::ptr::null(),
+ 0,
+ uapi::LANDLOCK_CREATE_RULESET_VERSION,
+ )
+ })
+ }
+
+ // There is no way to not publicly expose an implementation of an external trait such as
+ // From<i32>. See RFC https://github.com/rust-lang/rfcs/pull/2529
+ fn from(value: i32) -> ABI {
+ match value {
+ // The only possible error values should be EOPNOTSUPP and ENOSYS, but let's interpret
+ // all kind of errors as unsupported.
+ n if n <= 0 => ABI::Unsupported,
+ 1 => ABI::V1,
+ 2 => ABI::V2,
+ 3 => ABI::V3,
+ 4 => ABI::V4,
+ // Returns the greatest known ABI.
+ _ => ABI::V5,
+ }
+ }
+
+ #[cfg(test)]
+ fn is_known(value: i32) -> bool {
+ value > 0 && value < ABI::COUNT as i32
+ }
+}
+
+#[test]
+fn abi_from() {
+ // EOPNOTSUPP (-95), ENOSYS (-38)
+ for n in [-95, -38, -1, 0] {
+ assert_eq!(ABI::from(n), ABI::Unsupported);
+ }
+
+ let mut last_i = 1;
+ let mut last_abi = ABI::Unsupported;
+ for (i, abi) in ABI::iter().enumerate() {
+ last_i = i.try_into().unwrap();
+ last_abi = abi;
+ assert_eq!(ABI::from(last_i), last_abi);
+ }
+
+ assert_eq!(ABI::from(last_i + 1), last_abi);
+ assert_eq!(ABI::from(9), last_abi);
+}
+
+#[test]
+fn known_abi() {
+ assert!(!ABI::is_known(-1));
+ assert!(!ABI::is_known(0));
+ assert!(!ABI::is_known(99));
+
+ let mut last_i = -1;
+ for (i, _) in ABI::iter().enumerate().skip(1) {
+ last_i = i as i32;
+ assert!(ABI::is_known(last_i));
+ }
+ assert!(!ABI::is_known(last_i + 1));
+}
+
+#[cfg(test)]
+lazy_static! {
+ static ref TEST_ABI: ABI = match std::env::var("LANDLOCK_CRATE_TEST_ABI") {
+ Ok(s) => {
+ let n = s.parse::<i32>().unwrap();
+ if ABI::is_known(n) || n == 0 {
+ ABI::from(n)
+ } else {
+ panic!("Unknown ABI: {n}");
+ }
+ }
+ Err(std::env::VarError::NotPresent) => ABI::iter().last().unwrap(),
+ Err(e) => panic!("Failed to read LANDLOCK_CRATE_TEST_ABI: {e}"),
+ };
+}
+
+#[cfg(test)]
+pub(crate) fn can_emulate(mock: ABI, partial_support: ABI, full_support: Option<ABI>) -> bool {
+ mock < partial_support
+ || mock <= *TEST_ABI
+ || if let Some(full) = full_support {
+ full <= *TEST_ABI
+ } else {
+ partial_support <= *TEST_ABI
+ }
+}
+
+#[cfg(test)]
+pub(crate) fn get_errno_from_landlock_status() -> Option<i32> {
+ use std::io::Error;
+
+ if unsafe {
+ uapi::landlock_create_ruleset(std::ptr::null(), 0, uapi::LANDLOCK_CREATE_RULESET_VERSION)
+ } < 0
+ {
+ match Error::last_os_error().raw_os_error() {
+ // Returns ENOSYS when the kernel is not built with Landlock support,
+ // or EOPNOTSUPP when Landlock is supported but disabled at boot time.
+ ret @ Some(libc::ENOSYS | libc::EOPNOTSUPP) => ret,
+ // Other values can only come from bogus seccomp filters or debug tampering.
+ _ => unreachable!(),
+ }
+ } else {
+ None
+ }
+}
+
+#[test]
+fn current_kernel_abi() {
+ // Ensures that the tested Landlock ABI is the latest known version supported by the running
+ // kernel. If this test failed, you need set the LANDLOCK_CRATE_TEST_ABI environment variable
+ // to the Landlock ABI version supported by your kernel. With a missing variable, the latest
+ // Landlock ABI version known by this crate is automatically set.
+ // From Linux 5.13 to 5.18, you need to run: LANDLOCK_CRATE_TEST_ABI=1 cargo test
+ assert_eq!(*TEST_ABI, ABI::new_current());
+}
+
+// CompatState is not public outside this crate.
+/// Returned by ruleset builder.
+#[cfg_attr(test, derive(Debug))]
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum CompatState {
+ /// Initial undefined state.
+ Init,
+ /// All requested restrictions are enforced.
+ Full,
+ /// Some requested restrictions are enforced, following a best-effort approach.
+ Partial,
+ /// The running system doesn't support Landlock.
+ No,
+ /// Final unsupported state.
+ Dummy,
+}
+
+impl CompatState {
+ fn update(&mut self, other: Self) {
+ *self = match (*self, other) {
+ (CompatState::Init, other) => other,
+ (CompatState::Dummy, _) => CompatState::Dummy,
+ (_, CompatState::Dummy) => CompatState::Dummy,
+ (CompatState::No, CompatState::No) => CompatState::No,
+ (CompatState::Full, CompatState::Full) => CompatState::Full,
+ (_, _) => CompatState::Partial,
+ }
+ }
+}
+
+#[test]
+fn compat_state_update_1() {
+ let mut state = CompatState::Full;
+
+ state.update(CompatState::Full);
+ assert_eq!(state, CompatState::Full);
+
+ state.update(CompatState::No);
+ assert_eq!(state, CompatState::Partial);
+
+ state.update(CompatState::Full);
+ assert_eq!(state, CompatState::Partial);
+
+ state.update(CompatState::Full);
+ assert_eq!(state, CompatState::Partial);
+
+ state.update(CompatState::No);
+ assert_eq!(state, CompatState::Partial);
+
+ state.update(CompatState::Dummy);
+ assert_eq!(state, CompatState::Dummy);
+
+ state.update(CompatState::Full);
+ assert_eq!(state, CompatState::Dummy);
+}
+
+#[test]
+fn compat_state_update_2() {
+ let mut state = CompatState::Full;
+
+ state.update(CompatState::Full);
+ assert_eq!(state, CompatState::Full);
+
+ state.update(CompatState::No);
+ assert_eq!(state, CompatState::Partial);
+
+ state.update(CompatState::Full);
+ assert_eq!(state, CompatState::Partial);
+}
+
+#[cfg_attr(test, derive(Debug, PartialEq))]
+#[derive(Copy, Clone)]
+pub(crate) struct Compatibility {
+ abi: ABI,
+ pub(crate) level: Option<CompatLevel>,
+ pub(crate) state: CompatState,
+}
+
+impl From<ABI> for Compatibility {
+ fn from(abi: ABI) -> Self {
+ Compatibility {
+ abi,
+ level: Default::default(),
+ state: CompatState::Init,
+ }
+ }
+}
+
+impl Compatibility {
+ // Compatibility is a semi-opaque struct.
+ #[allow(clippy::new_without_default)]
+ pub(crate) fn new() -> Self {
+ ABI::new_current().into()
+ }
+
+ pub(crate) fn update(&mut self, state: CompatState) {
+ self.state.update(state);
+ }
+
+ pub(crate) fn abi(&self) -> ABI {
+ self.abi
+ }
+}
+
+pub(crate) mod private {
+ use crate::CompatLevel;
+
+ pub trait OptionCompatLevelMut {
+ fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel>;
+ }
+}
+
+/// Properly handles runtime unsupported features.
+///
+/// This guarantees consistent behaviors across crate users
+/// and runtime kernels even if this crate get new features.
+/// It eases backward compatibility and enables future-proofness.
+///
+/// Landlock is a security feature designed to help improve security of a running system
+/// thanks to application developers.
+/// To protect users as much as possible,
+/// compatibility with the running system should then be handled in a best-effort way,
+/// contrary to common system features.
+/// In some circumstances
+/// (e.g. applications carefully designed to only be run with a specific set of kernel features),
+/// it may be required to error out if some of these features are not available
+/// and will then not be enforced.
+pub trait Compatible: Sized + private::OptionCompatLevelMut {
+ /// To enable a best-effort security approach,
+ /// Landlock features that are not supported by the running system
+ /// are silently ignored by default,
+ /// which is a sane choice for most use cases.
+ /// However, on some rare circumstances,
+ /// developers may want to have some guarantees that their applications
+ /// will not run if a certain level of sandboxing is not possible.
+ /// If we really want to error out when not all our requested requirements are met,
+ /// then we can configure it with `set_compatibility()`.
+ ///
+ /// The `Compatible` trait is implemented for all object builders
+ /// (e.g. [`Ruleset`](crate::Ruleset)).
+ /// Such builders have a set of methods to incrementally build an object.
+ /// These build methods rely on kernel features that may not be available at runtime.
+ /// The `set_compatibility()` method enables to control the effect of
+ /// the following build method calls starting after the `set_compatibility()` call.
+ /// Such effect can be:
+ /// * to silently ignore unsupported features
+ /// and continue building ([`CompatLevel::BestEffort`]);
+ /// * to silently ignore unsupported features
+ /// and ignore the whole build ([`CompatLevel::SoftRequirement`]);
+ /// * to return an error for any unsupported feature ([`CompatLevel::HardRequirement`]).
+ ///
+ /// Taking [`Ruleset`](crate::Ruleset) as an example,
+ /// the [`handle_access()`](crate::RulesetAttr::handle_access()) build method
+ /// returns a [`Result`] that can be [`Err(RulesetError)`](crate::RulesetError)
+ /// with a nested [`CompatError`].
+ /// Such error can only occur with a running Linux kernel not supporting the requested
+ /// Landlock accesses *and* if the current compatibility level is
+ /// [`CompatLevel::HardRequirement`].
+ /// However, such error is not possible with [`CompatLevel::BestEffort`]
+ /// nor [`CompatLevel::SoftRequirement`].
+ ///
+ /// The order of this call is important because
+ /// it defines the behavior of the following build method calls that return a [`Result`].
+ /// If `set_compatibility(CompatLevel::HardRequirement)` is called on an object,
+ /// then a [`CompatError`] may be returned for the next method calls,
+ /// until the next call to `set_compatibility()`.
+ /// This enables to change the behavior of a set of build method calls,
+ /// for instance to be sure that the sandbox will at least restrict some access rights.
+ ///
+ /// New objects inherit the compatibility configuration of their parents, if any.
+ /// For instance, [`Ruleset::create()`](crate::Ruleset::create()) returns
+ /// a [`RulesetCreated`](crate::RulesetCreated) object that inherits the
+ /// `Ruleset`'s compatibility configuration.
+ ///
+ /// # Example with `SoftRequirement`
+ ///
+ /// Let's say an application legitimately needs to rename files between directories.
+ /// Because of [previous Landlock limitations](https://docs.kernel.org/userspace-api/landlock.html#file-renaming-and-linking-abi-2),
+ /// this was forbidden with the [first version of Landlock](ABI::V1),
+ /// but it is now handled starting with the [second version](ABI::V2).
+ /// For this use case, we only want the application to be sandboxed
+ /// if we have the guarantee that it will not break a legitimate usage (i.e. rename files).
+ /// We then create a ruleset which will either support file renaming
+ /// (thanks to [`AccessFs::Refer`](crate::AccessFs::Refer)) or silently do nothing.
+ ///
+ /// ```
+ /// use landlock::*;
+ ///
+ /// fn ruleset_handling_renames() -> Result<RulesetCreated, RulesetError> {
+ /// Ok(Ruleset::default()
+ /// // This ruleset must either handle the AccessFs::Refer right,
+ /// // or it must silently ignore the whole sandboxing.
+ /// .set_compatibility(CompatLevel::SoftRequirement)
+ /// .handle_access(AccessFs::Refer)?
+ /// // However, this ruleset may also handle other (future) access rights
+ /// // if they are supported by the running kernel.
+ /// .set_compatibility(CompatLevel::BestEffort)
+ /// .handle_access(AccessFs::from_all(ABI::V5))?
+ /// .create()?)
+ /// }
+ /// ```
+ ///
+ /// # Example with `HardRequirement`
+ ///
+ /// Security-dedicated applications may want to ensure that
+ /// an untrusted software component is subject to a minimum of restrictions before launching it.
+ /// In this case, we want to create a ruleset which will at least support
+ /// all restrictions provided by the [first version of Landlock](ABI::V1),
+ /// and opportunistically handle restrictions supported by newer kernels.
+ ///
+ /// ```
+ /// use landlock::*;
+ ///
+ /// fn ruleset_fragile() -> Result<RulesetCreated, RulesetError> {
+ /// Ok(Ruleset::default()
+ /// // This ruleset must either handle at least all accesses defined by
+ /// // the first Landlock version (e.g. AccessFs::WriteFile),
+ /// // or the following handle_access() call must return a wrapped
+ /// // AccessError<AccessFs>::Incompatible error.
+ /// .set_compatibility(CompatLevel::HardRequirement)
+ /// .handle_access(AccessFs::from_all(ABI::V1))?
+ /// // However, this ruleset may also handle new access rights
+ /// // (e.g. AccessFs::Refer defined by the second version of Landlock)
+ /// // if they are supported by the running kernel,
+ /// // but without returning any error otherwise.
+ /// .set_compatibility(CompatLevel::BestEffort)
+ /// .handle_access(AccessFs::from_all(ABI::V5))?
+ /// .create()?)
+ /// }
+ /// ```
+ fn set_compatibility(mut self, level: CompatLevel) -> Self {
+ *self.as_option_compat_level_mut() = Some(level);
+ self
+ }
+
+ /// Cf. [`set_compatibility()`](Compatible::set_compatibility()):
+ ///
+ /// - `set_best_effort(true)` translates to `set_compatibility(CompatLevel::BestEffort)`.
+ ///
+ /// - `set_best_effort(false)` translates to `set_compatibility(CompatLevel::HardRequirement)`.
+ #[deprecated(note = "Use set_compatibility() instead")]
+ fn set_best_effort(self, best_effort: bool) -> Self
+ where
+ Self: Sized,
+ {
+ self.set_compatibility(match best_effort {
+ true => CompatLevel::BestEffort,
+ false => CompatLevel::HardRequirement,
+ })
+ }
+}
+
+#[test]
+#[allow(deprecated)]
+fn deprecated_set_best_effort() {
+ use crate::{CompatLevel, Compatible, Ruleset};
+
+ assert_eq!(
+ Ruleset::default().set_best_effort(true).compat,
+ Ruleset::default()
+ .set_compatibility(CompatLevel::BestEffort)
+ .compat
+ );
+ assert_eq!(
+ Ruleset::default().set_best_effort(false).compat,
+ Ruleset::default()
+ .set_compatibility(CompatLevel::HardRequirement)
+ .compat
+ );
+}
+
+/// See the [`Compatible`] documentation.
+#[cfg_attr(test, derive(EnumIter))]
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum CompatLevel {
+ /// Takes into account the build requests if they are supported by the running system,
+ /// or silently ignores them otherwise.
+ /// Never returns a compatibility error.
+ #[default]
+ BestEffort,
+ /// Takes into account the build requests if they are supported by the running system,
+ /// or silently ignores the whole build object otherwise.
+ /// Never returns a compatibility error.
+ /// If not supported,
+ /// the call to [`RulesetCreated::restrict_self()`](crate::RulesetCreated::restrict_self())
+ /// will return a
+ /// [`RestrictionStatus { ruleset: RulesetStatus::NotEnforced, no_new_privs: false, }`](crate::RestrictionStatus).
+ SoftRequirement,
+ /// Takes into account the build requests if they are supported by the running system,
+ /// or returns a compatibility error otherwise ([`CompatError`]).
+ HardRequirement,
+}
+
+impl From<Option<CompatLevel>> for CompatLevel {
+ fn from(opt: Option<CompatLevel>) -> Self {
+ match opt {
+ None => CompatLevel::default(),
+ Some(ref level) => *level,
+ }
+ }
+}
+
+// TailoredCompatLevel could be replaced with AsMut<Option<CompatLevel>>, but only traits defined
+// in the current crate can be implemented for types defined outside of the crate. Furthermore it
+// provides a default implementation which is handy for types such as BitFlags.
+pub trait TailoredCompatLevel {
+ fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
+ where
+ L: Into<CompatLevel>,
+ {
+ parent_level.into()
+ }
+}
+
+impl<T> TailoredCompatLevel for T
+where
+ Self: Compatible,
+{
+ // Every Compatible trait implementation returns its own compatibility level, if set.
+ fn tailored_compat_level<L>(&mut self, parent_level: L) -> CompatLevel
+ where
+ L: Into<CompatLevel>,
+ {
+ // Using a mutable reference is not required but it makes the code simpler (no double AsRef
+ // implementations for each Compatible types), and more importantly it guarantees
+ // consistency with Compatible::set_compatibility().
+ match self.as_option_compat_level_mut() {
+ None => parent_level.into(),
+ // Returns the most constrained compatibility level.
+ Some(ref level) => parent_level.into().max(*level),
+ }
+ }
+}
+
+#[test]
+fn tailored_compat_level() {
+ use crate::{AccessFs, PathBeneath, PathFd};
+
+ fn new_path(level: CompatLevel) -> PathBeneath<PathFd> {
+ PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Execute).set_compatibility(level)
+ }
+
+ for parent_level in CompatLevel::iter() {
+ assert_eq!(
+ new_path(CompatLevel::BestEffort).tailored_compat_level(parent_level),
+ parent_level
+ );
+ assert_eq!(
+ new_path(CompatLevel::HardRequirement).tailored_compat_level(parent_level),
+ CompatLevel::HardRequirement
+ );
+ }
+
+ assert_eq!(
+ new_path(CompatLevel::SoftRequirement).tailored_compat_level(CompatLevel::SoftRequirement),
+ CompatLevel::SoftRequirement
+ );
+
+ for child_level in CompatLevel::iter() {
+ assert_eq!(
+ new_path(child_level).tailored_compat_level(CompatLevel::BestEffort),
+ child_level
+ );
+ assert_eq!(
+ new_path(child_level).tailored_compat_level(CompatLevel::HardRequirement),
+ CompatLevel::HardRequirement
+ );
+ }
+}
+
+// CompatResult is not public outside this crate.
+pub enum CompatResult<A>
+where
+ A: Access,
+{
+ // Fully matches the request.
+ Full,
+ // Partially matches the request.
+ Partial(CompatError<A>),
+ // Doesn't matches the request.
+ No(CompatError<A>),
+}
+
+// TryCompat is not public outside this crate.
+pub trait TryCompat<A>
+where
+ Self: Sized + TailoredCompatLevel,
+ A: Access,
+{
+ fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>>;
+
+ // Default implementation for objects without children.
+ //
+ // If returning something other than Ok(Some(self)), the implementation must use its own
+ // compatibility level, if any, with self.tailored_compat_level(default_compat_level), and pass
+ // it with the abi and compat_state to each child.try_compat(). See PathBeneath implementation
+ // and the self.allowed_access.try_compat() call.
+ //
+ // # Warning
+ //
+ // Errors must be prioritized over incompatibility (i.e. return Err(e) over Ok(None)) for all
+ // children.
+ fn try_compat_children<L>(
+ self,
+ _abi: ABI,
+ _parent_level: L,
+ _compat_state: &mut CompatState,
+ ) -> Result<Option<Self>, CompatError<A>>
+ where
+ L: Into<CompatLevel>,
+ {
+ Ok(Some(self))
+ }
+
+ // Update compat_state and return an error according to try_compat_*() error, or to the
+ // compatibility level, i.e. either route compatible object or error.
+ fn try_compat<L>(
+ mut self,
+ abi: ABI,
+ parent_level: L,
+ compat_state: &mut CompatState,
+ ) -> Result<Option<Self>, CompatError<A>>
+ where
+ L: Into<CompatLevel>,
+ {
+ let compat_level = self.tailored_compat_level(parent_level);
+ let some_inner = match self.try_compat_inner(abi) {
+ Ok(CompatResult::Full) => {
+ compat_state.update(CompatState::Full);
+ true
+ }
+ Ok(CompatResult::Partial(error)) => match compat_level {
+ CompatLevel::BestEffort => {
+ compat_state.update(CompatState::Partial);
+ true
+ }
+ CompatLevel::SoftRequirement => {
+ compat_state.update(CompatState::Dummy);
+ false
+ }
+ CompatLevel::HardRequirement => {
+ compat_state.update(CompatState::Dummy);
+ return Err(error);
+ }
+ },
+ Ok(CompatResult::No(error)) => match compat_level {
+ CompatLevel::BestEffort => {
+ compat_state.update(CompatState::No);
+ false
+ }
+ CompatLevel::SoftRequirement => {
+ compat_state.update(CompatState::Dummy);
+ false
+ }
+ CompatLevel::HardRequirement => {
+ compat_state.update(CompatState::Dummy);
+ return Err(error);
+ }
+ },
+ Err(error) => {
+ // Safeguard to help for test consistency.
+ compat_state.update(CompatState::Dummy);
+ return Err(error);
+ }
+ };
+
+ // At this point, any inner error have been returned, so we can proceed with
+ // try_compat_children()?.
+ match self.try_compat_children(abi, compat_level, compat_state)? {
+ Some(n) if some_inner => Ok(Some(n)),
+ _ => Ok(None),
+ }
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +
use crate::{Access, AccessFs, AccessNet, BitFlags};
+use std::io;
+use std::path::PathBuf;
+use thiserror::Error;
+
+/// Maps to all errors that can be returned by a ruleset action.
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum RulesetError {
+ #[error(transparent)]
+ HandleAccesses(#[from] HandleAccessesError),
+ #[error(transparent)]
+ CreateRuleset(#[from] CreateRulesetError),
+ #[error(transparent)]
+ AddRules(#[from] AddRulesError),
+ #[error(transparent)]
+ RestrictSelf(#[from] RestrictSelfError),
+}
+
+#[test]
+fn ruleset_error_breaking_change() {
+ use crate::*;
+
+ // Generics are part of the API and modifying them can lead to a breaking change.
+ let _: RulesetError = RulesetError::HandleAccesses(HandleAccessesError::Fs(
+ HandleAccessError::Compat(CompatError::Access(AccessError::Empty)),
+ ));
+}
+
+/// Identifies errors when updating the ruleset's handled access-rights.
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum HandleAccessError<T>
+where
+ T: Access,
+{
+ #[error(transparent)]
+ Compat(#[from] CompatError<T>),
+}
+
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum HandleAccessesError {
+ #[error(transparent)]
+ Fs(HandleAccessError<AccessFs>),
+ #[error(transparent)]
+ Net(HandleAccessError<AccessNet>),
+}
+
+// Generically implement for all the access implementations rather than for the cases listed in
+// HandleAccessesError (with #[from]).
+impl<A> From<HandleAccessError<A>> for HandleAccessesError
+where
+ A: Access,
+{
+ fn from(error: HandleAccessError<A>) -> Self {
+ A::into_handle_accesses_error(error)
+ }
+}
+
+/// Identifies errors when creating a ruleset.
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum CreateRulesetError {
+ /// The `landlock_create_ruleset()` system call failed.
+ #[error("failed to create a ruleset: {source}")]
+ #[non_exhaustive]
+ CreateRulesetCall { source: io::Error },
+ /// Missing call to [`RulesetAttr::handle_access()`](crate::RulesetAttr::handle_access).
+ #[error("missing handled access")]
+ MissingHandledAccess,
+}
+
+/// Identifies errors when adding a rule to a ruleset.
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum AddRuleError<T>
+where
+ T: Access,
+{
+ /// The `landlock_add_rule()` system call failed.
+ #[error("failed to add a rule: {source}")]
+ #[non_exhaustive]
+ AddRuleCall { source: io::Error },
+ /// The rule's access-rights are not all handled by the (requested) ruleset access-rights.
+ #[error("access-rights not handled by the ruleset: {incompatible:?}")]
+ UnhandledAccess {
+ access: BitFlags<T>,
+ incompatible: BitFlags<T>,
+ },
+ #[error(transparent)]
+ Compat(#[from] CompatError<T>),
+}
+
+// Generically implement for all the access implementations rather than for the cases listed in
+// AddRulesError (with #[from]).
+impl<A> From<AddRuleError<A>> for AddRulesError
+where
+ A: Access,
+{
+ fn from(error: AddRuleError<A>) -> Self {
+ A::into_add_rules_error(error)
+ }
+}
+
+/// Identifies errors when adding rules to a ruleset thanks to an iterator returning
+/// Result<Rule, E> items.
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum AddRulesError {
+ #[error(transparent)]
+ Fs(AddRuleError<AccessFs>),
+ #[error(transparent)]
+ Net(AddRuleError<AccessNet>),
+}
+
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum CompatError<T>
+where
+ T: Access,
+{
+ #[error(transparent)]
+ PathBeneath(#[from] PathBeneathError),
+ #[error(transparent)]
+ Access(#[from] AccessError<T>),
+}
+
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum PathBeneathError {
+ /// To check that access-rights are consistent with a file descriptor, a call to
+ /// [`RulesetCreatedAttr::add_rule()`](crate::RulesetCreatedAttr::add_rule)
+ /// looks at the file type with an `fstat()` system call.
+ #[error("failed to check file descriptor type: {source}")]
+ #[non_exhaustive]
+ StatCall { source: io::Error },
+ /// This error is returned by
+ /// [`RulesetCreatedAttr::add_rule()`](crate::RulesetCreatedAttr::add_rule)
+ /// if the related PathBeneath object is not set to best-effort,
+ /// and if its allowed access-rights contain directory-only ones
+ /// whereas the file descriptor doesn't point to a directory.
+ #[error("incompatible directory-only access-rights: {incompatible:?}")]
+ DirectoryAccess {
+ access: BitFlags<AccessFs>,
+ incompatible: BitFlags<AccessFs>,
+ },
+}
+
+#[derive(Debug, Error)]
+// Exhaustive enum
+pub enum AccessError<T>
+where
+ T: Access,
+{
+ /// The access-rights set is empty, which doesn't make sense and would be rejected by the
+ /// kernel.
+ #[error("empty access-right")]
+ Empty,
+ /// The access-rights set was forged with the unsafe `BitFlags::from_bits_unchecked()` and it
+ /// contains unknown bits.
+ #[error("unknown access-rights (at build time): {unknown:?}")]
+ Unknown {
+ access: BitFlags<T>,
+ unknown: BitFlags<T>,
+ },
+ /// The best-effort approach was (deliberately) disabled and the requested access-rights are
+ /// fully incompatible with the running kernel.
+ #[error("fully incompatible access-rights: {access:?}")]
+ Incompatible { access: BitFlags<T> },
+ /// The best-effort approach was (deliberately) disabled and the requested access-rights are
+ /// partially incompatible with the running kernel.
+ #[error("partially incompatible access-rights: {incompatible:?}")]
+ PartiallyCompatible {
+ access: BitFlags<T>,
+ incompatible: BitFlags<T>,
+ },
+}
+
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum RestrictSelfError {
+ /// The `prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)` system call failed.
+ #[error("failed to set no_new_privs: {source}")]
+ #[non_exhaustive]
+ SetNoNewPrivsCall { source: io::Error },
+ /// The `landlock_restrict_self() `system call failed.
+ #[error("failed to restrict the calling thread: {source}")]
+ #[non_exhaustive]
+ RestrictSelfCall { source: io::Error },
+}
+
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum PathFdError {
+ /// The `open()` system call failed.
+ #[error("failed to open \"{path}\": {source}")]
+ #[non_exhaustive]
+ OpenCall { source: io::Error, path: PathBuf },
+}
+
+#[cfg(test)]
+#[derive(Debug, Error)]
+pub(crate) enum TestRulesetError {
+ #[error(transparent)]
+ Ruleset(#[from] RulesetError),
+ #[error(transparent)]
+ PathFd(#[from] PathFdError),
+ #[error(transparent)]
+ File(#[from] std::io::Error),
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +
use crate::compat::private::OptionCompatLevelMut;
+use crate::{
+ uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState,
+ Compatible, HandleAccessError, HandleAccessesError, PathBeneathError, PathFdError,
+ PrivateAccess, PrivateRule, Rule, Ruleset, RulesetCreated, RulesetError, TailoredCompatLevel,
+ TryCompat, ABI,
+};
+use enumflags2::{bitflags, make_bitflags, BitFlags};
+use std::fs::OpenOptions;
+use std::io::Error;
+use std::mem::zeroed;
+use std::os::unix::fs::OpenOptionsExt;
+use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
+use std::path::Path;
+
+#[cfg(test)]
+use crate::{RulesetAttr, RulesetCreatedAttr};
+#[cfg(test)]
+use strum::IntoEnumIterator;
+
+/// File system access right.
+///
+/// Each variant of `AccessFs` is an [access right](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights)
+/// for the file system.
+/// A set of access rights can be created with [`BitFlags<AccessFs>`](BitFlags).
+///
+/// # Example
+///
+/// ```
+/// use landlock::{ABI, Access, AccessFs, BitFlags, make_bitflags};
+///
+/// let exec = AccessFs::Execute;
+///
+/// let exec_set: BitFlags<AccessFs> = exec.into();
+///
+/// let file_content = make_bitflags!(AccessFs::{Execute | WriteFile | ReadFile});
+///
+/// let fs_v1 = AccessFs::from_all(ABI::V1);
+///
+/// let without_exec = fs_v1 & !AccessFs::Execute;
+///
+/// assert_eq!(fs_v1 | AccessFs::Refer, AccessFs::from_all(ABI::V2));
+/// ```
+///
+/// # Warning
+///
+/// To avoid unknown restrictions **don't use `BitFlags::<AccessFs>::all()` nor `BitFlags::ALL`**,
+/// but use a version you tested and vetted instead,
+/// for instance [`AccessFs::from_all(ABI::V1)`](Access::from_all).
+/// Direct use of **the [`BitFlags`] API is deprecated**.
+/// See [`ABI`] for the rationale and help to test it.
+#[bitflags]
+#[repr(u64)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum AccessFs {
+ /// Execute a file.
+ Execute = uapi::LANDLOCK_ACCESS_FS_EXECUTE as u64,
+ /// Open a file with write access.
+ WriteFile = uapi::LANDLOCK_ACCESS_FS_WRITE_FILE as u64,
+ /// Open a file with read access.
+ ReadFile = uapi::LANDLOCK_ACCESS_FS_READ_FILE as u64,
+ /// Open a directory or list its content.
+ ReadDir = uapi::LANDLOCK_ACCESS_FS_READ_DIR as u64,
+ /// Remove an empty directory or rename one.
+ RemoveDir = uapi::LANDLOCK_ACCESS_FS_REMOVE_DIR as u64,
+ /// Unlink (or rename) a file.
+ RemoveFile = uapi::LANDLOCK_ACCESS_FS_REMOVE_FILE as u64,
+ /// Create (or rename or link) a character device.
+ MakeChar = uapi::LANDLOCK_ACCESS_FS_MAKE_CHAR as u64,
+ /// Create (or rename) a directory.
+ MakeDir = uapi::LANDLOCK_ACCESS_FS_MAKE_DIR as u64,
+ /// Create (or rename or link) a regular file.
+ MakeReg = uapi::LANDLOCK_ACCESS_FS_MAKE_REG as u64,
+ /// Create (or rename or link) a UNIX domain socket.
+ MakeSock = uapi::LANDLOCK_ACCESS_FS_MAKE_SOCK as u64,
+ /// Create (or rename or link) a named pipe.
+ MakeFifo = uapi::LANDLOCK_ACCESS_FS_MAKE_FIFO as u64,
+ /// Create (or rename or link) a block device.
+ MakeBlock = uapi::LANDLOCK_ACCESS_FS_MAKE_BLOCK as u64,
+ /// Create (or rename or link) a symbolic link.
+ MakeSym = uapi::LANDLOCK_ACCESS_FS_MAKE_SYM as u64,
+ /// Link or rename a file from or to a different directory.
+ Refer = uapi::LANDLOCK_ACCESS_FS_REFER as u64,
+ /// Truncate a file with `truncate(2)`, `ftruncate(2)`, `creat(2)`, or `open(2)` with `O_TRUNC`.
+ Truncate = uapi::LANDLOCK_ACCESS_FS_TRUNCATE as u64,
+ /// Send IOCL commands to a device file.
+ IoctlDev = uapi::LANDLOCK_ACCESS_FS_IOCTL_DEV as u64,
+}
+
+impl Access for AccessFs {
+ /// Union of [`from_read()`](AccessFs::from_read) and [`from_write()`](AccessFs::from_write).
+ fn from_all(abi: ABI) -> BitFlags<Self> {
+ // An empty access-right would be an error if passed to the kernel, but because the kernel
+ // doesn't support Landlock, no Landlock syscall should be called. try_compat() should
+ // also return RestrictionStatus::Unrestricted when called with unsupported/empty
+ // access-rights.
+ Self::from_read(abi) | Self::from_write(abi)
+ }
+}
+
+impl AccessFs {
+ // Roughly read (i.e. not all FS actions are handled).
+ /// Gets the access rights identified as read-only according to a specific ABI.
+ /// Exclusive with [`from_write()`](AccessFs::from_write).
+ pub fn from_read(abi: ABI) -> BitFlags<Self> {
+ match abi {
+ ABI::Unsupported => BitFlags::EMPTY,
+ ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 => make_bitflags!(AccessFs::{
+ Execute
+ | ReadFile
+ | ReadDir
+ }),
+ }
+ }
+
+ // Roughly write (i.e. not all FS actions are handled).
+ /// Gets the access rights identified as write-only according to a specific ABI.
+ /// Exclusive with [`from_read()`](AccessFs::from_read).
+ pub fn from_write(abi: ABI) -> BitFlags<Self> {
+ match abi {
+ ABI::Unsupported => BitFlags::EMPTY,
+ ABI::V1 => make_bitflags!(AccessFs::{
+ WriteFile
+ | RemoveDir
+ | RemoveFile
+ | MakeChar
+ | MakeDir
+ | MakeReg
+ | MakeSock
+ | MakeFifo
+ | MakeBlock
+ | MakeSym
+ }),
+ ABI::V2 => Self::from_write(ABI::V1) | AccessFs::Refer,
+ ABI::V3 | ABI::V4 => Self::from_write(ABI::V2) | AccessFs::Truncate,
+ ABI::V5 => Self::from_write(ABI::V4) | AccessFs::IoctlDev,
+ }
+ }
+
+ /// Gets the access rights legitimate for non-directory files.
+ pub fn from_file(abi: ABI) -> BitFlags<Self> {
+ Self::from_all(abi) & ACCESS_FILE
+ }
+}
+
+#[test]
+fn consistent_access_fs_rw() {
+ for abi in ABI::iter() {
+ let access_all = AccessFs::from_all(abi);
+ let access_read = AccessFs::from_read(abi);
+ let access_write = AccessFs::from_write(abi);
+ assert_eq!(access_read, !access_write & access_all);
+ assert_eq!(access_read | access_write, access_all);
+ }
+}
+
+impl PrivateAccess for AccessFs {
+ fn ruleset_handle_access(
+ ruleset: &mut Ruleset,
+ access: BitFlags<Self>,
+ ) -> Result<(), HandleAccessesError> {
+ // We need to record the requested accesses for PrivateRule::check_consistency().
+ ruleset.requested_handled_fs |= access;
+ ruleset.actual_handled_fs |= match access
+ .try_compat(
+ ruleset.compat.abi(),
+ ruleset.compat.level,
+ &mut ruleset.compat.state,
+ )
+ .map_err(HandleAccessError::Compat)?
+ {
+ Some(a) => a,
+ None => return Ok(()),
+ };
+ Ok(())
+ }
+
+ fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
+ AddRulesError::Fs(error)
+ }
+
+ fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
+ HandleAccessesError::Fs(error)
+ }
+}
+
+// TODO: Make ACCESS_FILE a property of AccessFs.
+// TODO: Add tests for ACCESS_FILE.
+const ACCESS_FILE: BitFlags<AccessFs> = make_bitflags!(AccessFs::{
+ ReadFile | WriteFile | Execute | Truncate | IoctlDev
+});
+
+// XXX: What should we do when a stat call failed?
+fn is_file<F>(fd: F) -> Result<bool, Error>
+where
+ F: AsFd,
+{
+ unsafe {
+ let mut stat = zeroed();
+ match libc::fstat(fd.as_fd().as_raw_fd(), &mut stat) {
+ 0 => Ok((stat.st_mode & libc::S_IFMT) != libc::S_IFDIR),
+ _ => Err(Error::last_os_error()),
+ }
+ }
+}
+
+/// Landlock rule for a file hierarchy.
+///
+/// # Example
+///
+/// ```
+/// use landlock::{AccessFs, PathBeneath, PathFd, PathFdError};
+///
+/// fn home_dir() -> Result<PathBeneath<PathFd>, PathFdError> {
+/// Ok(PathBeneath::new(PathFd::new("/home")?, AccessFs::ReadDir))
+/// }
+/// ```
+#[cfg_attr(test, derive(Debug))]
+pub struct PathBeneath<F> {
+ attr: uapi::landlock_path_beneath_attr,
+ // Ties the lifetime of a file descriptor to this object.
+ parent_fd: F,
+ allowed_access: BitFlags<AccessFs>,
+ compat_level: Option<CompatLevel>,
+}
+
+impl<F> PathBeneath<F>
+where
+ F: AsFd,
+{
+ /// Creates a new `PathBeneath` rule identifying the `parent` directory of a file hierarchy,
+ /// or just a file, and allows `access` on it.
+ /// The `parent` file descriptor will be automatically closed with the returned `PathBeneath`.
+ pub fn new<A>(parent: F, access: A) -> Self
+ where
+ A: Into<BitFlags<AccessFs>>,
+ {
+ PathBeneath {
+ // Invalid access rights until as_ptr() is called.
+ attr: unsafe { zeroed() },
+ parent_fd: parent,
+ allowed_access: access.into(),
+ compat_level: None,
+ }
+ }
+}
+
+impl<F> TryCompat<AccessFs> for PathBeneath<F>
+where
+ F: AsFd,
+{
+ fn try_compat_children<L>(
+ mut self,
+ abi: ABI,
+ parent_level: L,
+ compat_state: &mut CompatState,
+ ) -> Result<Option<Self>, CompatError<AccessFs>>
+ where
+ L: Into<CompatLevel>,
+ {
+ // Checks with our own compatibility level, if any.
+ self.allowed_access = match self.allowed_access.try_compat(
+ abi,
+ self.tailored_compat_level(parent_level),
+ compat_state,
+ )? {
+ Some(a) => a,
+ None => return Ok(None),
+ };
+ Ok(Some(self))
+ }
+
+ fn try_compat_inner(
+ &mut self,
+ _abi: ABI,
+ ) -> Result<CompatResult<AccessFs>, CompatError<AccessFs>> {
+ // Gets subset of valid accesses according the FD type.
+ let valid_access =
+ if is_file(&self.parent_fd).map_err(|e| PathBeneathError::StatCall { source: e })? {
+ self.allowed_access & ACCESS_FILE
+ } else {
+ self.allowed_access
+ };
+
+ if self.allowed_access != valid_access {
+ let error = PathBeneathError::DirectoryAccess {
+ access: self.allowed_access,
+ incompatible: self.allowed_access ^ valid_access,
+ }
+ .into();
+ self.allowed_access = valid_access;
+ // Linux would return EINVAL.
+ Ok(CompatResult::Partial(error))
+ } else {
+ Ok(CompatResult::Full)
+ }
+ }
+}
+
+#[test]
+fn path_beneath_try_compat_children() {
+ use crate::*;
+
+ // AccessFs::Refer is not handled by ABI::V1 and only for directories.
+ let access_file = AccessFs::ReadFile | AccessFs::Refer;
+
+ // Test error ordering with ABI::V1
+ let mut ruleset = Ruleset::from(ABI::V1).handle_access(access_file).unwrap();
+ // Do not actually perform any syscall.
+ ruleset.compat.state = CompatState::Dummy;
+ assert!(matches!(
+ RulesetCreated::new(ruleset, -1)
+ .set_compatibility(CompatLevel::HardRequirement)
+ .add_rule(PathBeneath::new(PathFd::new("/dev/null").unwrap(), access_file))
+ .unwrap_err(),
+ RulesetError::AddRules(AddRulesError::Fs(AddRuleError::Compat(
+ CompatError::PathBeneath(PathBeneathError::DirectoryAccess { access, incompatible })
+ ))) if access == access_file && incompatible == AccessFs::Refer
+ ));
+
+ // Test error ordering with ABI::V2
+ let mut ruleset = Ruleset::from(ABI::V2).handle_access(access_file).unwrap();
+ // Do not actually perform any syscall.
+ ruleset.compat.state = CompatState::Dummy;
+ assert!(matches!(
+ RulesetCreated::new(ruleset, -1)
+ .set_compatibility(CompatLevel::HardRequirement)
+ .add_rule(PathBeneath::new(PathFd::new("/dev/null").unwrap(), access_file))
+ .unwrap_err(),
+ RulesetError::AddRules(AddRulesError::Fs(AddRuleError::Compat(
+ CompatError::PathBeneath(PathBeneathError::DirectoryAccess { access, incompatible })
+ ))) if access == access_file && incompatible == AccessFs::Refer
+ ));
+}
+
+#[test]
+fn path_beneath_try_compat() {
+ use crate::*;
+
+ let abi = ABI::V1;
+
+ for file in &["/etc/passwd", "/dev/null"] {
+ let mut compat_state = CompatState::Init;
+ let ro_access = AccessFs::ReadDir | AccessFs::ReadFile;
+ assert!(matches!(
+ PathBeneath::new(PathFd::new(file).unwrap(), ro_access)
+ .try_compat(abi, CompatLevel::HardRequirement, &mut compat_state)
+ .unwrap_err(),
+ CompatError::PathBeneath(PathBeneathError::DirectoryAccess { access, incompatible })
+ if access == ro_access && incompatible == AccessFs::ReadDir
+ ));
+
+ let mut compat_state = CompatState::Init;
+ assert!(matches!(
+ PathBeneath::new(PathFd::new(file).unwrap(), BitFlags::EMPTY)
+ .try_compat(abi, CompatLevel::BestEffort, &mut compat_state)
+ .unwrap_err(),
+ CompatError::Access(AccessError::Empty)
+ ));
+ }
+
+ let full_access = AccessFs::from_all(ABI::V1);
+ for compat_level in &[
+ CompatLevel::BestEffort,
+ CompatLevel::SoftRequirement,
+ CompatLevel::HardRequirement,
+ ] {
+ let mut compat_state = CompatState::Init;
+ let mut path_beneath = PathBeneath::new(PathFd::new("/").unwrap(), full_access)
+ .try_compat(abi, *compat_level, &mut compat_state)
+ .unwrap()
+ .unwrap();
+ assert_eq!(compat_state, CompatState::Full);
+
+ // Without synchronization.
+ let raw_access = path_beneath.attr.allowed_access;
+ assert_eq!(raw_access, 0);
+
+ // Synchronize the inner attribute buffer.
+ let _ = path_beneath.as_ptr();
+ let raw_access = path_beneath.attr.allowed_access;
+ assert_eq!(raw_access, full_access.bits());
+ }
+}
+
+impl<F> OptionCompatLevelMut for PathBeneath<F> {
+ fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
+ &mut self.compat_level
+ }
+}
+
+impl<F> OptionCompatLevelMut for &mut PathBeneath<F> {
+ fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
+ &mut self.compat_level
+ }
+}
+
+impl<F> Compatible for PathBeneath<F> {}
+
+impl<F> Compatible for &mut PathBeneath<F> {}
+
+#[test]
+fn path_beneath_compatibility() {
+ let mut path = PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::from_all(ABI::V1));
+ let path_ref = &mut path;
+
+ let level = path_ref.as_option_compat_level_mut();
+ assert_eq!(level, &None);
+ assert_eq!(
+ <Option<CompatLevel> as Into<CompatLevel>>::into(*level),
+ CompatLevel::BestEffort
+ );
+
+ path_ref.set_compatibility(CompatLevel::SoftRequirement);
+ assert_eq!(
+ path_ref.as_option_compat_level_mut(),
+ &Some(CompatLevel::SoftRequirement)
+ );
+
+ path.set_compatibility(CompatLevel::HardRequirement);
+}
+
+// It is useful for documentation generation to explicitely implement Rule for every types, instead
+// of doing it generically.
+impl<F> Rule<AccessFs> for PathBeneath<F> where F: AsFd {}
+
+impl<F> PrivateRule<AccessFs> for PathBeneath<F>
+where
+ F: AsFd,
+{
+ const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH;
+
+ fn as_ptr(&mut self) -> *const libc::c_void {
+ self.attr.parent_fd = self.parent_fd.as_fd().as_raw_fd();
+ self.attr.allowed_access = self.allowed_access.bits();
+ &self.attr as *const _ as _
+ }
+
+ fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {
+ // Checks that this rule doesn't contain a superset of the access-rights handled by the
+ // ruleset. This check is about requested access-rights but not actual access-rights.
+ // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel
+ // (which is handled by Ruleset and RulesetCreated).
+ if ruleset.requested_handled_fs.contains(self.allowed_access) {
+ Ok(())
+ } else {
+ Err(AddRuleError::UnhandledAccess {
+ access: self.allowed_access,
+ incompatible: self.allowed_access & !ruleset.requested_handled_fs,
+ }
+ .into())
+ }
+ }
+}
+
+#[test]
+fn path_beneath_check_consistency() {
+ use crate::*;
+
+ let ro_access = AccessFs::ReadDir | AccessFs::ReadFile;
+ let rx_access = AccessFs::Execute | AccessFs::ReadFile;
+ assert!(matches!(
+ Ruleset::from(ABI::Unsupported)
+ .handle_access(ro_access)
+ .unwrap()
+ .create()
+ .unwrap()
+ .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), rx_access))
+ .unwrap_err(),
+ RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { access, incompatible }))
+ if access == rx_access && incompatible == AccessFs::Execute
+ ));
+}
+
+/// Simple helper to open a file or a directory with the `O_PATH` flag.
+///
+/// This is the recommended way to identify a path
+/// and manage the lifetime of the underlying opened file descriptor.
+/// Indeed, using other [`AsFd`] implementations such as [`File`] brings more complexity
+/// and may lead to unexpected errors (e.g., denied access).
+///
+/// [`File`]: std::fs::File
+///
+/// # Example
+///
+/// ```
+/// use landlock::{AccessFs, PathBeneath, PathFd, PathFdError};
+///
+/// fn allowed_root_dir(access: AccessFs) -> Result<PathBeneath<PathFd>, PathFdError> {
+/// let fd = PathFd::new("/")?;
+/// Ok(PathBeneath::new(fd, access))
+/// }
+/// ```
+#[cfg_attr(test, derive(Debug))]
+pub struct PathFd {
+ fd: OwnedFd,
+}
+
+impl PathFd {
+ pub fn new<T>(path: T) -> Result<Self, PathFdError>
+ where
+ T: AsRef<Path>,
+ {
+ Ok(PathFd {
+ fd: OpenOptions::new()
+ .read(true)
+ // If the O_PATH is not supported, it is automatically ignored (Linux < 2.6.39).
+ .custom_flags(libc::O_PATH | libc::O_CLOEXEC)
+ .open(path.as_ref())
+ .map_err(|e| PathFdError::OpenCall {
+ source: e,
+ path: path.as_ref().into(),
+ })?
+ .into(),
+ })
+ }
+}
+
+impl AsFd for PathFd {
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.fd.as_fd()
+ }
+}
+
+#[test]
+fn path_fd() {
+ use std::fs::File;
+ use std::io::Read;
+
+ PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Execute);
+ PathBeneath::new(File::open("/").unwrap(), AccessFs::Execute);
+
+ let mut buffer = [0; 1];
+ // Checks that PathFd really returns an FD opened with O_PATH (Bad file descriptor error).
+ File::from(PathFd::new("/etc/passwd").unwrap().fd)
+ .read(&mut buffer)
+ .unwrap_err();
+}
+
+/// Helper to quickly create an iterator of PathBeneath rules.
+///
+/// Silently ignores paths that cannot be opened, and automatically adjust access rights according
+/// to file types when possible.
+///
+/// # Example
+///
+/// ```
+/// use landlock::{
+/// ABI, Access, AccessFs, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus, RulesetError,
+/// path_beneath_rules,
+/// };
+///
+/// fn restrict_thread() -> Result<(), RulesetError> {
+/// let abi = ABI::V1;
+/// let status = Ruleset::default()
+/// .handle_access(AccessFs::from_all(abi))?
+/// .create()?
+/// // Read-only access to /usr, /etc and /dev.
+/// .add_rules(path_beneath_rules(&["/usr", "/etc", "/dev"], AccessFs::from_read(abi)))?
+/// // Read-write access to /home and /tmp.
+/// .add_rules(path_beneath_rules(&["/home", "/tmp"], AccessFs::from_all(abi)))?
+/// .restrict_self()?;
+/// match status.ruleset {
+/// // The FullyEnforced case must be tested by the developer.
+/// RulesetStatus::FullyEnforced => println!("Fully sandboxed."),
+/// RulesetStatus::PartiallyEnforced => println!("Partially sandboxed."),
+/// // Users should be warned that they are not protected.
+/// RulesetStatus::NotEnforced => println!("Not sandboxed! Please update your kernel."),
+/// }
+/// Ok(())
+/// }
+/// ```
+pub fn path_beneath_rules<I, P, A>(
+ paths: I,
+ access: A,
+) -> impl Iterator<Item = Result<PathBeneath<PathFd>, RulesetError>>
+where
+ I: IntoIterator<Item = P>,
+ P: AsRef<Path>,
+ A: Into<BitFlags<AccessFs>>,
+{
+ let access = access.into();
+ paths.into_iter().filter_map(move |p| match PathFd::new(p) {
+ Ok(f) => {
+ let valid_access = match is_file(&f) {
+ Ok(true) => access & ACCESS_FILE,
+ // If the stat call failed, let's blindly rely on the requested access rights.
+ Err(_) | Ok(false) => access,
+ };
+ Some(Ok(PathBeneath::new(f, valid_access)))
+ }
+ Err(_) => None,
+ })
+}
+
+#[test]
+fn path_beneath_rules_iter() {
+ let _ = Ruleset::default()
+ .handle_access(AccessFs::from_all(ABI::V1))
+ .unwrap()
+ .create()
+ .unwrap()
+ .add_rules(path_beneath_rules(
+ &["/usr", "/opt", "/does-not-exist", "/root"],
+ AccessFs::Execute,
+ ))
+ .unwrap();
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +
//! Landlock is a security feature available since Linux 5.13.
+//! The goal is to enable to restrict ambient rights
+//! (e.g., global filesystem access)
+//! for a set of processes by creating safe security sandboxes as new security layers
+//! in addition to the existing system-wide access-controls.
+//! This kind of sandbox is expected to help mitigate the security impact of bugs,
+//! unexpected or malicious behaviors in applications.
+//! Landlock empowers any process, including unprivileged ones, to securely restrict themselves.
+//! More information about Landlock can be found in the [official website](https://landlock.io).
+//!
+//! This crate provides a safe abstraction for the Landlock system calls, along with some helpers.
+//!
+//! Minimum Supported Rust Version (MSRV): 1.63
+//!
+//! # Use cases
+//!
+//! This crate is especially useful to protect users' data by sandboxing:
+//! * trusted applications dealing with potentially malicious data
+//! (e.g., complex file format, network request) that could exploit security vulnerabilities;
+//! * sandbox managers, container runtimes or shells launching untrusted applications.
+//!
+//! # Examples
+//!
+//! A simple example can be found with the [`path_beneath_rules()`] helper.
+//! More complex examples can be found with the [`Ruleset` documentation](Ruleset)
+//! and the [sandboxer example](https://github.com/landlock-lsm/rust-landlock/blob/master/examples/sandboxer.rs).
+//!
+//! # Current limitations
+//!
+//! This crate exposes the Landlock features available as of Linux 5.19
+//! and then inherits some [kernel limitations](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#current-limitations)
+//! that will be addressed with future kernel releases
+//! (e.g., arbitrary mounts are always denied).
+//!
+//! # Compatibility
+//!
+//! Types defined in this crate are designed to enable the strictest Landlock configuration
+//! for the given kernel on which the program runs.
+//! In the default [best-effort](CompatLevel::BestEffort) mode,
+//! [`Ruleset`] will determine compatibility
+//! with the intersection of the currently running kernel's features
+//! and those required by the caller.
+//! This way, callers can distinguish between
+//! Landlock compatibility issues inherent to the current system
+//! (e.g., file names that don't exist)
+//! and misconfiguration that should be fixed in the program
+//! (e.g., empty or inconsistent access rights).
+//! [`RulesetError`] identifies such kind of errors.
+//!
+//! With [`set_compatibility(CompatLevel::BestEffort)`](Compatible::set_compatibility),
+//! users of the crate may mark Landlock features that are deemed required
+//! and other features that may be downgraded to use lower security on systems
+//! where they can't be enforced.
+//! It is discouraged to compare the system's provided [Landlock ABI](ABI) version directly,
+//! as it is difficult to track detailed ABI differences
+//! which are handled thanks to the [`Compatible`] trait.
+//!
+//! To make it easier to migrate to a new version of this library,
+//! we use the builder pattern
+//! and designed objects to require the minimal set of method arguments.
+//! Most `enum` are marked as `non_exhaustive` to enable backward-compatible evolutions.
+//!
+//! ## Test strategy
+//!
+//! Developers should test their sandboxed applications
+//! with a kernel that supports all requested Landlock features
+//! and check that [`RulesetCreated::restrict_self()`] returns a status matching
+//! [`Ok(RestrictionStatus { ruleset: RulesetStatus::FullyEnforced, no_new_privs: true, })`](RestrictionStatus)
+//! to make sure everything works as expected in an enforced sandbox.
+//! Alternatively, using [`set_compatibility(CompatLevel::HardRequirement)`](Compatible::set_compatibility)
+//! will immediately inform about unsupported Landlock features.
+//! These configurations should only depend on the test environment
+//! (e.g. [by checking an environment variable](https://github.com/landlock-lsm/rust-landlock/search?q=LANDLOCK_CRATE_TEST_ABI)).
+//! However, applications should only check that no error is returned (i.e. `Ok(_)`)
+//! and optionally log and inform users that the application is not fully sandboxed
+//! because of missing features from the running kernel.
+
+#[cfg(test)]
+#[macro_use]
+extern crate lazy_static;
+
+pub use access::Access;
+pub use compat::{CompatLevel, Compatible, ABI};
+pub use enumflags2::{make_bitflags, BitFlags};
+pub use errors::{
+ AccessError, AddRuleError, AddRulesError, CompatError, CreateRulesetError, HandleAccessError,
+ HandleAccessesError, PathBeneathError, PathFdError, RestrictSelfError, RulesetError,
+};
+pub use fs::{path_beneath_rules, AccessFs, PathBeneath, PathFd};
+pub use net::{AccessNet, NetPort};
+pub use ruleset::{
+ RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr,
+ RulesetStatus,
+};
+
+use access::PrivateAccess;
+use compat::{CompatResult, CompatState, Compatibility, TailoredCompatLevel, TryCompat};
+use ruleset::PrivateRule;
+
+#[cfg(test)]
+use compat::{can_emulate, get_errno_from_landlock_status};
+#[cfg(test)]
+use errors::TestRulesetError;
+#[cfg(test)]
+use strum::IntoEnumIterator;
+
+mod access;
+mod compat;
+mod errors;
+mod fs;
+mod net;
+mod ruleset;
+mod uapi;
+
+#[cfg(test)]
+mod tests {
+ use crate::*;
+
+ // Emulate old kernel supports.
+ fn check_ruleset_support<F>(
+ partial: ABI,
+ full: Option<ABI>,
+ check: F,
+ error_if_abi_lt_partial: bool,
+ ) where
+ F: Fn(Ruleset) -> Result<RestrictionStatus, TestRulesetError> + Send + Copy + 'static,
+ {
+ // If there is no partial support, it means that `full == partial`.
+ assert!(partial <= full.unwrap_or(partial));
+ for abi in ABI::iter() {
+ // Ensures restrict_self() is called on a dedicated thread to avoid inconsistent tests.
+ let ret = std::thread::spawn(move || check(Ruleset::from(abi)))
+ .join()
+ .unwrap();
+
+ // Useful for failed tests and with cargo test -- --show-output
+ println!("Checking ABI {abi:?}: received {ret:#?}");
+ if can_emulate(abi, partial, full) {
+ if abi < partial && error_if_abi_lt_partial {
+ // TODO: Check exact error type; this may require better error types.
+ assert!(matches!(ret, Err(TestRulesetError::Ruleset(_))));
+ } else {
+ let full_support = if let Some(full_inner) = full {
+ abi >= full_inner
+ } else {
+ false
+ };
+ let ruleset_status = if full_support {
+ RulesetStatus::FullyEnforced
+ } else if abi >= partial {
+ RulesetStatus::PartiallyEnforced
+ } else {
+ RulesetStatus::NotEnforced
+ };
+ println!("Expecting ruleset status {ruleset_status:?}");
+ assert!(matches!(
+ ret,
+ Ok(RestrictionStatus {
+ ruleset,
+ no_new_privs: true,
+ }) if ruleset == ruleset_status
+ ))
+ }
+ } else {
+ // The errno value should be ENOSYS, EOPNOTSUPP, EINVAL (e.g. when an unknown
+ // access right is provided), or E2BIG (e.g. when there is an unknown field in a
+ // Landlock syscall attribute).
+ let errno = get_errno_from_landlock_status();
+ println!("Expecting error {errno:?}");
+ match ret {
+ Err(TestRulesetError::Ruleset(RulesetError::CreateRuleset(
+ CreateRulesetError::CreateRulesetCall { source },
+ ))) => match (source.raw_os_error(), errno) {
+ (Some(e1), Some(e2)) => assert_eq!(e1, e2),
+ (Some(e1), None) => assert!(matches!(e1, libc::EINVAL | libc::E2BIG)),
+ _ => unreachable!(),
+ },
+ _ => unreachable!(),
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn allow_root_compat() {
+ let abi = ABI::V1;
+
+ check_ruleset_support(
+ abi,
+ Some(abi),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessFs::from_all(abi))?
+ .create()?
+ .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::from_all(abi)))?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+
+ #[test]
+ fn too_much_access_rights_for_a_file() {
+ let abi = ABI::V1;
+
+ check_ruleset_support(
+ abi,
+ Some(abi),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessFs::from_all(abi))?
+ .create()?
+ // Same code as allow_root_compat() but with /etc/passwd instead of /
+ .add_rule(PathBeneath::new(
+ PathFd::new("/etc/passwd")?,
+ // Only allow legitimate access rights on a file.
+ AccessFs::from_file(abi),
+ ))?
+ .restrict_self()?)
+ },
+ false,
+ );
+
+ check_ruleset_support(
+ abi,
+ None,
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessFs::from_all(abi))?
+ .create()?
+ // Same code as allow_root_compat() but with /etc/passwd instead of /
+ .add_rule(PathBeneath::new(
+ PathFd::new("/etc/passwd")?,
+ // Tries to allow all access rights on a file.
+ AccessFs::from_all(abi),
+ ))?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+
+ #[test]
+ fn path_beneath_rules_with_too_much_access_rights_for_a_file() {
+ let abi = ABI::V1;
+
+ check_ruleset_support(
+ abi,
+ Some(abi),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessFs::from_all(ABI::V1))?
+ .create()?
+ // Same code as too_much_access_rights_for_a_file() but using path_beneath_rules()
+ .add_rules(path_beneath_rules(["/etc/passwd"], AccessFs::from_all(abi)))?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+
+ #[test]
+ fn allow_root_fragile() {
+ let abi = ABI::V1;
+
+ check_ruleset_support(
+ abi,
+ Some(abi),
+ move |ruleset: Ruleset| -> _ {
+ // Sets default support requirement: abort the whole sandboxing for any Landlock error.
+ Ok(ruleset
+ // Must have at least the execute check…
+ .set_compatibility(CompatLevel::HardRequirement)
+ .handle_access(AccessFs::Execute)?
+ // …and possibly others.
+ .set_compatibility(CompatLevel::BestEffort)
+ .handle_access(AccessFs::from_all(abi))?
+ .create()?
+ .set_no_new_privs(true)
+ .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::from_all(abi)))?
+ .restrict_self()?)
+ },
+ true,
+ );
+ }
+
+ #[test]
+ fn ruleset_enforced() {
+ let abi = ABI::V1;
+
+ check_ruleset_support(
+ abi,
+ Some(abi),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ // Restricting without rule exceptions is legitimate to forbid a set of actions.
+ .handle_access(AccessFs::Execute)?
+ .create()?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+
+ #[test]
+ fn abi_v2_exec_refer() {
+ check_ruleset_support(
+ ABI::V1,
+ Some(ABI::V2),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessFs::Execute)?
+ // AccessFs::Refer is not supported by ABI::V1 (best-effort).
+ .handle_access(AccessFs::Refer)?
+ .create()?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+
+ #[test]
+ fn abi_v2_refer_only() {
+ // When no access is handled, do not try to create a ruleset without access.
+ check_ruleset_support(
+ ABI::V2,
+ Some(ABI::V2),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessFs::Refer)?
+ .create()?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+
+ #[test]
+ fn abi_v3_truncate() {
+ check_ruleset_support(
+ ABI::V2,
+ Some(ABI::V3),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessFs::Refer)?
+ .handle_access(AccessFs::Truncate)?
+ .create()?
+ .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::Refer))?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+
+ #[test]
+ fn ruleset_created_try_clone() {
+ check_ruleset_support(
+ ABI::V1,
+ Some(ABI::V1),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessFs::Execute)?
+ .create()?
+ .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::Execute))?
+ .try_clone()?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+
+ #[test]
+ fn abi_v4_tcp() {
+ check_ruleset_support(
+ ABI::V3,
+ Some(ABI::V4),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessFs::Truncate)?
+ .handle_access(AccessNet::BindTcp | AccessNet::ConnectTcp)?
+ .create()?
+ .add_rule(NetPort::new(1, AccessNet::ConnectTcp))?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+
+ #[test]
+ fn abi_v5_ioctl_dev() {
+ check_ruleset_support(
+ ABI::V4,
+ Some(ABI::V5),
+ move |ruleset: Ruleset| -> _ {
+ Ok(ruleset
+ .handle_access(AccessNet::BindTcp)?
+ .handle_access(AccessFs::IoctlDev)?
+ .create()?
+ .add_rule(PathBeneath::new(PathFd::new("/")?, AccessFs::IoctlDev))?
+ .restrict_self()?)
+ },
+ false,
+ );
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +
use crate::compat::private::OptionCompatLevelMut;
+use crate::{
+ uapi, Access, AddRuleError, AddRulesError, CompatError, CompatLevel, CompatResult, CompatState,
+ Compatible, HandleAccessError, HandleAccessesError, PrivateAccess, PrivateRule, Rule, Ruleset,
+ RulesetCreated, TailoredCompatLevel, TryCompat, ABI,
+};
+use enumflags2::{bitflags, BitFlags};
+use std::mem::zeroed;
+
+/// Network access right.
+///
+/// Each variant of `AccessNet` is an [access right](https://www.kernel.org/doc/html/latest/userspace-api/landlock.html#access-rights)
+/// for the network.
+/// A set of access rights can be created with [`BitFlags<AccessNet>`](BitFlags).
+///
+/// # Example
+///
+/// ```
+/// use landlock::{ABI, Access, AccessNet, BitFlags, make_bitflags};
+///
+/// let bind = AccessNet::BindTcp;
+///
+/// let bind_set: BitFlags<AccessNet> = bind.into();
+///
+/// let bind_connect = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
+///
+/// let net_v4 = AccessNet::from_all(ABI::V4);
+///
+/// assert_eq!(bind_connect, net_v4);
+/// ```
+///
+/// # Warning
+///
+/// To avoid unknown restrictions **don't use `BitFlags::<AccessNet>::all()` nor `BitFlags::ALL`**,
+/// but use a version you tested and vetted instead,
+/// for instance [`AccessNet::from_all(ABI::V4)`](Access::from_all).
+/// Direct use of **the [`BitFlags`] API is deprecated**.
+/// See [`ABI`] for the rationale and help to test it.
+#[bitflags]
+#[repr(u64)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum AccessNet {
+ /// Bind to a TCP port.
+ BindTcp = uapi::LANDLOCK_ACCESS_NET_BIND_TCP as u64,
+ /// Connect to a TCP port.
+ ConnectTcp = uapi::LANDLOCK_ACCESS_NET_CONNECT_TCP as u64,
+}
+
+/// # Warning
+///
+/// If `ABI <= ABI::V3`, `AccessNet::from_all()` returns an empty `BitFlags<AccessNet>`, which
+/// makes `Ruleset::handle_access(AccessNet::from_all(ABI::V3))` return an error.
+impl Access for AccessNet {
+ fn from_all(abi: ABI) -> BitFlags<Self> {
+ match abi {
+ ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 => BitFlags::EMPTY,
+ ABI::V4 | ABI::V5 => AccessNet::BindTcp | AccessNet::ConnectTcp,
+ }
+ }
+}
+
+impl PrivateAccess for AccessNet {
+ fn ruleset_handle_access(
+ ruleset: &mut Ruleset,
+ access: BitFlags<Self>,
+ ) -> Result<(), HandleAccessesError> {
+ // We need to record the requested accesses for PrivateRule::check_consistency().
+ ruleset.requested_handled_net |= access;
+ ruleset.actual_handled_net |= match access
+ .try_compat(
+ ruleset.compat.abi(),
+ ruleset.compat.level,
+ &mut ruleset.compat.state,
+ )
+ .map_err(HandleAccessError::Compat)?
+ {
+ Some(a) => a,
+ None => return Ok(()),
+ };
+ Ok(())
+ }
+
+ fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
+ AddRulesError::Net(error)
+ }
+
+ fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
+ HandleAccessesError::Net(error)
+ }
+}
+
+/// Landlock rule for a network port.
+///
+/// # Example
+///
+/// ```
+/// use landlock::{AccessNet, NetPort};
+///
+/// fn bind_http() -> NetPort {
+/// NetPort::new(80, AccessNet::BindTcp)
+/// }
+/// ```
+#[cfg_attr(test, derive(Debug))]
+pub struct NetPort {
+ attr: uapi::landlock_net_port_attr,
+ // Only 16-bit port make sense for now.
+ port: u16,
+ allowed_access: BitFlags<AccessNet>,
+ compat_level: Option<CompatLevel>,
+}
+
+// If we need support for 32 or 64 ports, we'll add a new_32() or a new_64() method returning a
+// Result with a potential overflow error.
+impl NetPort {
+ /// Creates a new TCP port rule.
+ ///
+ /// As defined by the Linux ABI, `port` with a value of `0` means that TCP bindings will be
+ /// allowed for a port range defined by `/proc/sys/net/ipv4/ip_local_port_range`.
+ pub fn new<A>(port: u16, access: A) -> Self
+ where
+ A: Into<BitFlags<AccessNet>>,
+ {
+ NetPort {
+ // Invalid access-rights until as_ptr() is called.
+ attr: unsafe { zeroed() },
+ port,
+ allowed_access: access.into(),
+ compat_level: None,
+ }
+ }
+}
+
+impl Rule<AccessNet> for NetPort {}
+
+impl PrivateRule<AccessNet> for NetPort {
+ const TYPE_ID: uapi::landlock_rule_type = uapi::landlock_rule_type_LANDLOCK_RULE_NET_PORT;
+
+ fn as_ptr(&mut self) -> *const libc::c_void {
+ self.attr.port = self.port as u64;
+ self.attr.allowed_access = self.allowed_access.bits();
+ &self.attr as *const _ as _
+ }
+
+ fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError> {
+ // Checks that this rule doesn't contain a superset of the access-rights handled by the
+ // ruleset. This check is about requested access-rights but not actual access-rights.
+ // Indeed, we want to get a deterministic behavior, i.e. not based on the running kernel
+ // (which is handled by Ruleset and RulesetCreated).
+ if ruleset.requested_handled_net.contains(self.allowed_access) {
+ Ok(())
+ } else {
+ Err(AddRuleError::UnhandledAccess {
+ access: self.allowed_access,
+ incompatible: self.allowed_access & !ruleset.requested_handled_net,
+ }
+ .into())
+ }
+ }
+}
+
+#[test]
+fn net_port_check_consistency() {
+ use crate::*;
+
+ let bind = AccessNet::BindTcp;
+ let bind_connect = bind | AccessNet::ConnectTcp;
+
+ assert!(matches!(
+ Ruleset::from(ABI::Unsupported)
+ .handle_access(bind)
+ .unwrap()
+ .create()
+ .unwrap()
+ .add_rule(NetPort::new(1, bind_connect))
+ .unwrap_err(),
+ RulesetError::AddRules(AddRulesError::Net(AddRuleError::UnhandledAccess { access, incompatible }))
+ if access == bind_connect && incompatible == AccessNet::ConnectTcp
+ ));
+}
+
+impl TryCompat<AccessNet> for NetPort {
+ fn try_compat_children<L>(
+ mut self,
+ abi: ABI,
+ parent_level: L,
+ compat_state: &mut CompatState,
+ ) -> Result<Option<Self>, CompatError<AccessNet>>
+ where
+ L: Into<CompatLevel>,
+ {
+ // Checks with our own compatibility level, if any.
+ self.allowed_access = match self.allowed_access.try_compat(
+ abi,
+ self.tailored_compat_level(parent_level),
+ compat_state,
+ )? {
+ Some(a) => a,
+ None => return Ok(None),
+ };
+ Ok(Some(self))
+ }
+
+ fn try_compat_inner(
+ &mut self,
+ _abi: ABI,
+ ) -> Result<CompatResult<AccessNet>, CompatError<AccessNet>> {
+ Ok(CompatResult::Full)
+ }
+}
+
+impl OptionCompatLevelMut for NetPort {
+ fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
+ &mut self.compat_level
+ }
+}
+
+impl OptionCompatLevelMut for &mut NetPort {
+ fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
+ &mut self.compat_level
+ }
+}
+
+impl Compatible for NetPort {}
+
+impl Compatible for &mut NetPort {}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +901 +902 +903 +904 +905 +906 +907 +908 +909 +910 +911 +912 +913 +914 +915 +916 +917 +918 +919 +920 +921 +922 +923 +924 +925 +926 +927 +928 +929 +930 +931 +932 +933 +934 +935 +936 +937 +938 +939 +940 +941 +942 +943 +944 +945 +946 +947 +948 +949 +950 +951 +952 +953 +954 +955 +956 +957 +958 +959 +960 +961 +962 +963 +964 +965 +966 +967 +968 +969 +970 +971 +972 +973 +974 +975 +976 +977 +978 +979 +980 +981 +982 +983 +984 +985 +986 +987 +988 +989 +990 +991 +992 +993 +994 +995 +996 +997 +998 +999 +1000 +1001 +1002 +1003 +1004 +1005 +1006 +1007 +1008 +1009 +1010 +1011 +1012 +1013 +1014 +1015 +1016 +1017 +1018 +
use crate::compat::private::OptionCompatLevelMut;
+use crate::{
+ uapi, Access, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel,
+ CompatState, Compatibility, Compatible, CreateRulesetError, RestrictSelfError, RulesetError,
+ TryCompat,
+};
+use libc::close;
+use std::io::Error;
+use std::mem::size_of_val;
+use std::os::unix::io::RawFd;
+
+#[cfg(test)]
+use crate::*;
+
+// Public interface without methods and which is impossible to implement outside this crate.
+pub trait Rule<T>: PrivateRule<T>
+where
+ T: Access,
+{
+}
+
+// PrivateRule is not public outside this crate.
+pub trait PrivateRule<T>
+where
+ Self: TryCompat<T> + Compatible,
+ T: Access,
+{
+ const TYPE_ID: uapi::landlock_rule_type;
+
+ /// Returns a raw pointer to the rule's inner attribute.
+ ///
+ /// The caller must ensure that the rule outlives the pointer this function returns, or else it
+ /// will end up pointing to garbage.
+ fn as_ptr(&mut self) -> *const libc::c_void;
+
+ fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError>;
+}
+
+/// Enforcement status of a ruleset.
+#[derive(Debug, PartialEq, Eq)]
+pub enum RulesetStatus {
+ /// All requested restrictions are enforced.
+ FullyEnforced,
+ /// Some requested restrictions are enforced,
+ /// following a best-effort approach.
+ PartiallyEnforced,
+ /// The running system doesn't support Landlock
+ /// or a subset of the requested Landlock features.
+ NotEnforced,
+}
+
+impl From<CompatState> for RulesetStatus {
+ fn from(state: CompatState) -> Self {
+ match state {
+ CompatState::Init | CompatState::No | CompatState::Dummy => RulesetStatus::NotEnforced,
+ CompatState::Full => RulesetStatus::FullyEnforced,
+ CompatState::Partial => RulesetStatus::PartiallyEnforced,
+ }
+ }
+}
+
+// The Debug, PartialEq and Eq implementations are useful for crate users to debug and check the
+// result of a Landlock ruleset enforcement.
+/// Status of a [`RulesetCreated`]
+/// after calling [`restrict_self()`](RulesetCreated::restrict_self).
+#[derive(Debug, PartialEq, Eq)]
+#[non_exhaustive]
+pub struct RestrictionStatus {
+ /// Status of the Landlock ruleset enforcement.
+ pub ruleset: RulesetStatus,
+ /// Status of `prctl(2)`'s `PR_SET_NO_NEW_PRIVS` enforcement.
+ pub no_new_privs: bool,
+}
+
+fn prctl_set_no_new_privs() -> Result<(), Error> {
+ match unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } {
+ 0 => Ok(()),
+ _ => Err(Error::last_os_error()),
+ }
+}
+
+fn support_no_new_privs() -> bool {
+ // Only Linux < 3.5 or kernel with seccomp filters should return an error.
+ matches!(
+ unsafe { libc::prctl(libc::PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) },
+ 0 | 1
+ )
+}
+
+/// Landlock ruleset builder.
+///
+/// `Ruleset` enables to create a Landlock ruleset in a flexible way
+/// following the builder pattern.
+/// Most build steps return a [`Result`] with [`RulesetError`].
+///
+/// You should probably not create more than one ruleset per application.
+/// Creating multiple rulesets is only useful when gradually restricting an application
+/// (e.g., a first set of generic restrictions before reading any file,
+/// then a second set of tailored restrictions after reading the configuration).
+///
+/// # Simple example
+///
+/// Simple helper handling only Landlock-related errors.
+///
+/// ```
+/// use landlock::{
+/// Access, 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,
+/// {
+/// // The Landlock ABI should be incremented (and tested) regularly.
+/// 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");
+/// ```
+///
+/// # Generic example
+///
+/// More generic helper handling a set of file hierarchies
+/// and multiple types of error (i.e. [`RulesetError`](crate::RulesetError)
+/// and [`PathFdError`](crate::PathFdError).
+///
+/// ```
+/// use landlock::{
+/// Access, AccessFs, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
+/// RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
+/// };
+/// use thiserror::Error;
+///
+/// #[derive(Debug, Error)]
+/// enum MyRestrictError {
+/// #[error(transparent)]
+/// Ruleset(#[from] RulesetError),
+/// #[error(transparent)]
+/// AddRule(#[from] PathFdError),
+/// }
+///
+/// fn restrict_paths(hierarchies: &[&str]) -> Result<RestrictionStatus, MyRestrictError> {
+/// // The Landlock ABI should be incremented (and tested) regularly.
+/// 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_rules(
+/// hierarchies
+/// .iter()
+/// .map::<Result<_, MyRestrictError>, _>(|p| {
+/// Ok(PathBeneath::new(PathFd::new(p)?, access_read))
+/// }),
+/// )?
+/// .restrict_self()?)
+/// }
+///
+/// let status = restrict_paths(&["/usr", "/home"]).expect("failed to build the ruleset");
+/// ```
+#[cfg_attr(test, derive(Debug))]
+pub struct Ruleset {
+ pub(crate) requested_handled_fs: BitFlags<AccessFs>,
+ pub(crate) requested_handled_net: BitFlags<AccessNet>,
+ pub(crate) actual_handled_fs: BitFlags<AccessFs>,
+ pub(crate) actual_handled_net: BitFlags<AccessNet>,
+ pub(crate) compat: Compatibility,
+}
+
+impl From<Compatibility> for Ruleset {
+ fn from(compat: Compatibility) -> Self {
+ Ruleset {
+ // Non-working default handled FS accesses to force users to set them explicitely.
+ requested_handled_fs: Default::default(),
+ requested_handled_net: Default::default(),
+ actual_handled_fs: Default::default(),
+ actual_handled_net: Default::default(),
+ compat,
+ }
+ }
+}
+
+#[cfg(test)]
+impl From<ABI> for Ruleset {
+ fn from(abi: ABI) -> Self {
+ Ruleset::from(Compatibility::from(abi))
+ }
+}
+
+#[test]
+fn ruleset_add_rule_iter() {
+ assert!(matches!(
+ Ruleset::from(ABI::Unsupported)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .create()
+ .unwrap()
+ .add_rule(PathBeneath::new(
+ PathFd::new("/").unwrap(),
+ AccessFs::ReadFile
+ ))
+ .unwrap_err(),
+ RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
+ ));
+}
+
+impl Default for Ruleset {
+ /// Returns a new `Ruleset`.
+ /// This call automatically probes the running kernel to know if it supports Landlock.
+ ///
+ /// To be able to successfully call [`create()`](Ruleset::create),
+ /// it is required to set the handled accesses with
+ /// [`handle_access()`](Ruleset::handle_access).
+ fn default() -> Self {
+ // The API should be future-proof: one Rust program or library should have the same
+ // behavior if built with an old or a newer crate (e.g. with an extended ruleset_attr
+ // enum). It should then not be possible to give an "all-possible-handled-accesses" to the
+ // Ruleset builder because this value would be relative to the running kernel.
+ Compatibility::new().into()
+ }
+}
+
+impl Ruleset {
+ #[allow(clippy::new_without_default)]
+ #[deprecated(note = "Use Ruleset::default() instead")]
+ pub fn new() -> Self {
+ Ruleset::default()
+ }
+
+ /// Attempts to create a real Landlock ruleset (if supported by the running kernel).
+ /// The returned [`RulesetCreated`] is also a builder.
+ ///
+ /// On error, returns a wrapped [`CreateRulesetError`].
+ pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
+ let body = || -> Result<RulesetCreated, CreateRulesetError> {
+ match self.compat.state {
+ CompatState::Init => {
+ // Checks that there is at least one requested access (e.g.
+ // requested_handled_fs): one call to handle_access().
+ Err(CreateRulesetError::MissingHandledAccess)
+ }
+ CompatState::No | CompatState::Dummy => {
+ // There is at least one requested access.
+ #[cfg(test)]
+ assert!(
+ !self.requested_handled_fs.is_empty()
+ || !self.requested_handled_net.is_empty()
+ );
+
+ // CompatState::No should be handled as CompatState::Dummy because it is not
+ // possible to create an actual ruleset.
+ self.compat.update(CompatState::Dummy);
+ match self.compat.level.into() {
+ CompatLevel::HardRequirement => {
+ Err(CreateRulesetError::MissingHandledAccess)
+ }
+ _ => Ok(RulesetCreated::new(self, -1)),
+ }
+ }
+ CompatState::Full | CompatState::Partial => {
+ // There is at least one actual handled access.
+ #[cfg(test)]
+ assert!(
+ !self.actual_handled_fs.is_empty() || !self.actual_handled_net.is_empty()
+ );
+
+ let attr = uapi::landlock_ruleset_attr {
+ handled_access_fs: self.actual_handled_fs.bits(),
+ handled_access_net: self.actual_handled_net.bits(),
+ };
+ match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {
+ fd if fd >= 0 => Ok(RulesetCreated::new(self, fd)),
+ _ => Err(CreateRulesetError::CreateRulesetCall {
+ source: Error::last_os_error(),
+ }),
+ }
+ }
+ }
+ };
+ Ok(body()?)
+ }
+}
+
+impl OptionCompatLevelMut for Ruleset {
+ fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
+ &mut self.compat.level
+ }
+}
+
+impl OptionCompatLevelMut for &mut Ruleset {
+ fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
+ &mut self.compat.level
+ }
+}
+
+impl Compatible for Ruleset {}
+
+impl Compatible for &mut Ruleset {}
+
+impl AsMut<Ruleset> for Ruleset {
+ fn as_mut(&mut self) -> &mut Ruleset {
+ self
+ }
+}
+
+// Tests unambiguous type.
+#[test]
+fn ruleset_as_mut() {
+ let mut ruleset = Ruleset::from(ABI::Unsupported);
+ let _ = ruleset.as_mut();
+
+ let mut ruleset_created = Ruleset::from(ABI::Unsupported)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .create()
+ .unwrap();
+ let _ = ruleset_created.as_mut();
+}
+
+pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
+ /// Attempts to add a set of access rights that will be supported by this ruleset.
+ /// By default, all actions requiring these access rights will be denied.
+ /// Consecutive calls to `handle_access()` will be interpreted as logical ORs
+ /// with the previous handled accesses.
+ ///
+ /// On error, returns a wrapped [`HandleAccessesError`](crate::HandleAccessesError).
+ /// E.g., `RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError<AccessFs>))`
+ fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
+ where
+ T: Into<BitFlags<U>>,
+ U: Access,
+ {
+ U::ruleset_handle_access(self.as_mut(), access.into())?;
+ Ok(self)
+ }
+}
+
+impl RulesetAttr for Ruleset {}
+
+impl RulesetAttr for &mut Ruleset {}
+
+#[test]
+fn ruleset_attr() {
+ let mut ruleset = Ruleset::from(ABI::Unsupported);
+ let ruleset_ref = &mut ruleset;
+
+ // Can pass this reference to prepare the ruleset...
+ ruleset_ref
+ .set_compatibility(CompatLevel::BestEffort)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .handle_access(AccessFs::ReadFile)
+ .unwrap();
+
+ // ...and finally create the ruleset (thanks to non-lexical lifetimes).
+ ruleset
+ .set_compatibility(CompatLevel::BestEffort)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .handle_access(AccessFs::WriteFile)
+ .unwrap()
+ .create()
+ .unwrap();
+}
+
+#[test]
+fn ruleset_created_handle_access_fs() {
+ // Tests AccessFs::ruleset_handle_access()
+ let ruleset = Ruleset::from(ABI::V1)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .handle_access(AccessFs::ReadDir)
+ .unwrap();
+ let access = make_bitflags!(AccessFs::{Execute | ReadDir});
+ assert_eq!(ruleset.requested_handled_fs, access);
+ assert_eq!(ruleset.actual_handled_fs, access);
+
+ // Tests that only the required handled accesses are reported as incompatible:
+ // access should not contains AccessFs::Execute.
+ assert!(matches!(Ruleset::from(ABI::Unsupported)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .set_compatibility(CompatLevel::HardRequirement)
+ .handle_access(AccessFs::ReadDir)
+ .unwrap_err(),
+ RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
+ CompatError::Access(AccessError::Incompatible { access })
+ ))) if access == AccessFs::ReadDir
+ ));
+}
+
+#[test]
+fn ruleset_created_handle_access_net_tcp() {
+ let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
+
+ // Tests AccessNet::ruleset_handle_access() with ABI that doesn't support TCP rights.
+ let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();
+ assert_eq!(ruleset.requested_handled_net, access);
+ assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);
+
+ // Tests AccessNet::ruleset_handle_access() with ABI that supports TCP rights.
+ let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
+ assert_eq!(ruleset.requested_handled_net, access);
+ assert_eq!(ruleset.actual_handled_net, access);
+
+ // Tests that only the required handled accesses are reported as incompatible:
+ // access should not contains AccessNet::BindTcp.
+ assert!(matches!(Ruleset::from(ABI::Unsupported)
+ .handle_access(AccessNet::BindTcp)
+ .unwrap()
+ .set_compatibility(CompatLevel::HardRequirement)
+ .handle_access(AccessNet::ConnectTcp)
+ .unwrap_err(),
+ RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
+ CompatError::Access(AccessError::Incompatible { access })
+ ))) if access == AccessNet::ConnectTcp
+ ));
+}
+
+impl OptionCompatLevelMut for RulesetCreated {
+ fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
+ &mut self.compat.level
+ }
+}
+
+impl OptionCompatLevelMut for &mut RulesetCreated {
+ fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
+ &mut self.compat.level
+ }
+}
+
+impl Compatible for RulesetCreated {}
+
+impl Compatible for &mut RulesetCreated {}
+
+pub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {
+ /// Attempts to add a new rule to the ruleset.
+ ///
+ /// On error, returns a wrapped [`AddRulesError`].
+ fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
+ where
+ T: Rule<U>,
+ U: Access,
+ {
+ let body = || -> Result<Self, AddRulesError> {
+ let self_ref = self.as_mut();
+ rule.check_consistency(self_ref)?;
+ let mut compat_rule = match rule
+ .try_compat(
+ self_ref.compat.abi(),
+ self_ref.compat.level,
+ &mut self_ref.compat.state,
+ )
+ .map_err(AddRuleError::Compat)?
+ {
+ Some(r) => r,
+ None => return Ok(self),
+ };
+ match self_ref.compat.state {
+ CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
+ CompatState::Full | CompatState::Partial => match unsafe {
+ uapi::landlock_add_rule(self_ref.fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
+ } {
+ 0 => Ok(self),
+ _ => Err(AddRuleError::<U>::AddRuleCall {
+ source: Error::last_os_error(),
+ }
+ .into()),
+ },
+ }
+ };
+ Ok(body()?)
+ }
+
+ /// Attempts to add a set of new rules to the ruleset.
+ ///
+ /// On error, returns a (double) wrapped [`AddRulesError`].
+ ///
+ /// # Example
+ ///
+ /// Create a custom iterator to read paths from environment variable.
+ ///
+ /// ```
+ /// use landlock::{
+ /// Access, AccessFs, BitFlags, PathBeneath, PathFd, PathFdError, RestrictionStatus, Ruleset,
+ /// RulesetAttr, RulesetCreatedAttr, RulesetError, ABI,
+ /// };
+ /// use std::env;
+ /// use std::ffi::OsStr;
+ /// use std::os::unix::ffi::{OsStrExt, OsStringExt};
+ /// use thiserror::Error;
+ ///
+ /// #[derive(Debug, Error)]
+ /// enum PathEnvError<'a> {
+ /// #[error(transparent)]
+ /// Ruleset(#[from] RulesetError),
+ /// #[error(transparent)]
+ /// AddRuleIter(#[from] PathFdError),
+ /// #[error("missing environment variable {0}")]
+ /// MissingVar(&'a str),
+ /// }
+ ///
+ /// struct PathEnv {
+ /// paths: Vec<u8>,
+ /// access: BitFlags<AccessFs>,
+ /// }
+ ///
+ /// impl PathEnv {
+ /// // env_var is the name of an environment variable
+ /// // containing paths requested to be allowed.
+ /// // Paths are separated with ":", e.g. "/bin:/lib:/usr:/proc".
+ /// // In case an empty string is provided,
+ /// // no restrictions are applied.
+ /// // `access` is the set of access rights allowed for each of the parsed paths.
+ /// fn new<'a>(
+ /// env_var: &'a str, access: BitFlags<AccessFs>
+ /// ) -> Result<Self, PathEnvError<'a>> {
+ /// Ok(Self {
+ /// paths: env::var_os(env_var)
+ /// .ok_or(PathEnvError::MissingVar(env_var))?
+ /// .into_vec(),
+ /// access,
+ /// })
+ /// }
+ ///
+ /// fn iter(
+ /// &self,
+ /// ) -> impl Iterator<Item = Result<PathBeneath<PathFd>, PathEnvError<'static>>> + '_ {
+ /// let is_empty = self.paths.is_empty();
+ /// self.paths
+ /// .split(|b| *b == b':')
+ /// // Skips the first empty element from of an empty string.
+ /// .skip_while(move |_| is_empty)
+ /// .map(OsStr::from_bytes)
+ /// .map(move |path|
+ /// Ok(PathBeneath::new(PathFd::new(path)?, self.access)))
+ /// }
+ /// }
+ ///
+ /// fn restrict_env() -> Result<RestrictionStatus, PathEnvError<'static>> {
+ /// Ok(Ruleset::default()
+ /// .handle_access(AccessFs::from_all(ABI::V1))?
+ /// .create()?
+ /// // In the shell: export EXECUTABLE_PATH="/usr:/bin:/sbin"
+ /// .add_rules(PathEnv::new("EXECUTABLE_PATH", AccessFs::Execute.into())?.iter())?
+ /// .restrict_self()?)
+ /// }
+ /// ```
+ fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
+ where
+ I: IntoIterator<Item = Result<T, E>>,
+ T: Rule<U>,
+ U: Access,
+ E: From<RulesetError>,
+ {
+ for rule in rules {
+ self = self.add_rule(rule?)?;
+ }
+ Ok(self)
+ }
+
+ /// Configures the ruleset to call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS` command
+ /// in [`restrict_self()`](RulesetCreated::restrict_self).
+ ///
+ /// This `prctl(2)` call is never ignored, even if an error was encountered on a [`Ruleset`] or
+ /// [`RulesetCreated`] method call while [`CompatLevel::SoftRequirement`] was set.
+ fn set_no_new_privs(mut self, no_new_privs: bool) -> Self {
+ <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = no_new_privs;
+ self
+ }
+}
+
+/// Ruleset created with [`Ruleset::create()`].
+#[cfg_attr(test, derive(Debug))]
+pub struct RulesetCreated {
+ fd: RawFd,
+ no_new_privs: bool,
+ pub(crate) requested_handled_fs: BitFlags<AccessFs>,
+ pub(crate) requested_handled_net: BitFlags<AccessNet>,
+ compat: Compatibility,
+}
+
+impl RulesetCreated {
+ pub(crate) fn new(ruleset: Ruleset, fd: RawFd) -> Self {
+ // The compatibility state is initialized by Ruleset::create().
+ #[cfg(test)]
+ assert!(!matches!(ruleset.compat.state, CompatState::Init));
+
+ RulesetCreated {
+ fd,
+ no_new_privs: true,
+ requested_handled_fs: ruleset.requested_handled_fs,
+ requested_handled_net: ruleset.requested_handled_net,
+ compat: ruleset.compat,
+ }
+ }
+
+ /// Attempts to restrict the calling thread with the ruleset
+ /// according to the best-effort configuration
+ /// (see [`RulesetCreated::set_compatibility()`] and [`CompatLevel::BestEffort`]).
+ /// Call `prctl(2)` with the `PR_SET_NO_NEW_PRIVS`
+ /// according to the ruleset configuration.
+ ///
+ /// On error, returns a wrapped [`RestrictSelfError`].
+ pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
+ let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
+ // Enforce no_new_privs even if something failed with SoftRequirement. The rationale is
+ // that no_new_privs should not be an issue on its own if it is not explicitly
+ // deactivated.
+ let enforced_nnp = if self.no_new_privs {
+ if let Err(e) = prctl_set_no_new_privs() {
+ match self.compat.level.into() {
+ CompatLevel::BestEffort => {}
+ CompatLevel::SoftRequirement => {
+ self.compat.update(CompatState::Dummy);
+ }
+ CompatLevel::HardRequirement => {
+ return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
+ }
+ }
+ // To get a consistent behavior, calls this prctl whether or not
+ // Landlock is supported by the running kernel.
+ let support_nnp = support_no_new_privs();
+ match self.compat.state {
+ // It should not be an error for kernel (older than 3.5) not supporting
+ // no_new_privs.
+ CompatState::Init | CompatState::No | CompatState::Dummy => {
+ if support_nnp {
+ // The kernel seems to be between 3.5 (included) and 5.13 (excluded),
+ // or Landlock is not enabled; no_new_privs should be supported anyway.
+ return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
+ }
+ }
+ // A kernel supporting Landlock should also support no_new_privs (unless
+ // filtered by seccomp).
+ CompatState::Full | CompatState::Partial => {
+ return Err(RestrictSelfError::SetNoNewPrivsCall { source: e })
+ }
+ }
+ false
+ } else {
+ true
+ }
+ } else {
+ false
+ };
+
+ match self.compat.state {
+ CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
+ ruleset: self.compat.state.into(),
+ no_new_privs: enforced_nnp,
+ }),
+ CompatState::Full | CompatState::Partial => {
+ match unsafe { uapi::landlock_restrict_self(self.fd, 0) } {
+ 0 => {
+ self.compat.update(CompatState::Full);
+ Ok(RestrictionStatus {
+ ruleset: self.compat.state.into(),
+ no_new_privs: enforced_nnp,
+ })
+ }
+ // TODO: match specific Landlock restrict self errors
+ _ => Err(RestrictSelfError::RestrictSelfCall {
+ source: Error::last_os_error(),
+ }),
+ }
+ }
+ }
+ };
+ Ok(body()?)
+ }
+
+ /// Creates a new `RulesetCreated` instance by duplicating the underlying file descriptor.
+ /// Rule modification will affect both `RulesetCreated` instances simultaneously.
+ ///
+ /// On error, returns [`std::io::Error`].
+ pub fn try_clone(&self) -> std::io::Result<Self> {
+ Ok(RulesetCreated {
+ fd: match self.fd {
+ -1 => -1,
+ self_fd => match unsafe { libc::fcntl(self_fd, libc::F_DUPFD_CLOEXEC, 0) } {
+ dup_fd if dup_fd >= 0 => dup_fd,
+ _ => return Err(Error::last_os_error()),
+ },
+ },
+ no_new_privs: self.no_new_privs,
+ requested_handled_fs: self.requested_handled_fs,
+ requested_handled_net: self.requested_handled_net,
+ compat: self.compat,
+ })
+ }
+}
+
+impl Drop for RulesetCreated {
+ fn drop(&mut self) {
+ if self.fd >= 0 {
+ unsafe { close(self.fd) };
+ }
+ }
+}
+
+impl AsMut<RulesetCreated> for RulesetCreated {
+ fn as_mut(&mut self) -> &mut RulesetCreated {
+ self
+ }
+}
+
+impl RulesetCreatedAttr for RulesetCreated {}
+
+impl RulesetCreatedAttr for &mut RulesetCreated {}
+
+#[test]
+fn ruleset_created_attr() {
+ let mut ruleset_created = Ruleset::from(ABI::Unsupported)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .create()
+ .unwrap();
+ let ruleset_created_ref = &mut ruleset_created;
+
+ // Can pass this reference to populate the ruleset...
+ ruleset_created_ref
+ .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();
+
+ // ...and finally restrict with the last rules (thanks to non-lexical lifetimes).
+ assert_eq!(
+ ruleset_created
+ .set_compatibility(CompatLevel::BestEffort)
+ .add_rule(PathBeneath::new(
+ PathFd::new("/tmp").unwrap(),
+ AccessFs::Execute,
+ ))
+ .unwrap()
+ .add_rule(PathBeneath::new(
+ PathFd::new("/var").unwrap(),
+ AccessFs::Execute,
+ ))
+ .unwrap()
+ .restrict_self()
+ .unwrap(),
+ RestrictionStatus {
+ ruleset: RulesetStatus::NotEnforced,
+ no_new_privs: true,
+ }
+ );
+}
+
+#[test]
+fn ruleset_compat_dummy() {
+ for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
+ println!("level: {:?}", level);
+
+ // ABI:Unsupported does not support AccessFs::Execute.
+ let ruleset = Ruleset::from(ABI::Unsupported);
+ assert_eq!(ruleset.compat.state, CompatState::Init);
+
+ let ruleset = ruleset.set_compatibility(level);
+ assert_eq!(ruleset.compat.state, CompatState::Init);
+
+ let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
+ assert_eq!(
+ ruleset.compat.state,
+ match level {
+ CompatLevel::BestEffort => CompatState::No,
+ CompatLevel::SoftRequirement => CompatState::Dummy,
+ _ => unreachable!(),
+ }
+ );
+
+ let ruleset_created = ruleset.create().unwrap();
+ // Because the compatibility state was either No or Dummy, calling create() updates it to
+ // Dummy.
+ assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
+
+ let ruleset_created = ruleset_created
+ .add_rule(PathBeneath::new(
+ PathFd::new("/usr").unwrap(),
+ AccessFs::Execute,
+ ))
+ .unwrap();
+ assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
+ }
+}
+
+#[test]
+fn ruleset_compat_partial() {
+ // CompatLevel::BestEffort
+ let ruleset = Ruleset::from(ABI::V1);
+ assert_eq!(ruleset.compat.state, CompatState::Init);
+
+ // ABI::V1 does not support AccessFs::Refer.
+ let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
+ assert_eq!(ruleset.compat.state, CompatState::No);
+
+ let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
+ assert_eq!(ruleset.compat.state, CompatState::Partial);
+
+ // Requesting to handle another unsupported handled access does not change anything.
+ let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
+ assert_eq!(ruleset.compat.state, CompatState::Partial);
+}
+
+#[test]
+fn ruleset_unsupported() {
+ assert_eq!(
+ Ruleset::from(ABI::Unsupported)
+ // BestEffort for Ruleset.
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .create()
+ .unwrap()
+ .restrict_self()
+ .unwrap(),
+ RestrictionStatus {
+ ruleset: RulesetStatus::NotEnforced,
+ // With BestEffort, no_new_privs is still enabled.
+ no_new_privs: true,
+ }
+ );
+
+ assert_eq!(
+ Ruleset::from(ABI::Unsupported)
+ // SoftRequirement for Ruleset.
+ .set_compatibility(CompatLevel::SoftRequirement)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .create()
+ .unwrap()
+ .restrict_self()
+ .unwrap(),
+ RestrictionStatus {
+ ruleset: RulesetStatus::NotEnforced,
+ // With SoftRequirement, no_new_privs is still enabled.
+ no_new_privs: true,
+ }
+ );
+
+ matches!(
+ Ruleset::from(ABI::Unsupported)
+ // HardRequirement for Ruleset.
+ .set_compatibility(CompatLevel::HardRequirement)
+ .handle_access(AccessFs::Execute)
+ .unwrap_err(),
+ RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
+ );
+
+ assert_eq!(
+ Ruleset::from(ABI::Unsupported)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .create()
+ .unwrap()
+ // SoftRequirement for RulesetCreated without any rule.
+ .set_compatibility(CompatLevel::SoftRequirement)
+ .restrict_self()
+ .unwrap(),
+ RestrictionStatus {
+ ruleset: RulesetStatus::NotEnforced,
+ // With SoftRequirement, no_new_privs is untouched if there is no error (e.g. no rule).
+ no_new_privs: true,
+ }
+ );
+
+ // Don't explicitly call create() on a CI that doesn't support Landlock.
+ if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
+ assert_eq!(
+ Ruleset::from(ABI::V1)
+ .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
+ .unwrap()
+ .create()
+ .unwrap()
+ // SoftRequirement for RulesetCreated with a rule.
+ .set_compatibility(CompatLevel::SoftRequirement)
+ .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
+ .unwrap()
+ .restrict_self()
+ .unwrap(),
+ RestrictionStatus {
+ ruleset: RulesetStatus::NotEnforced,
+ // With SoftRequirement, no_new_privs is still enabled, even if there is an error
+ // (e.g. unsupported access right).
+ no_new_privs: true,
+ }
+ );
+ }
+
+ assert_eq!(
+ Ruleset::from(ABI::Unsupported)
+ .handle_access(AccessFs::Execute)
+ .unwrap()
+ .create()
+ .unwrap()
+ .set_no_new_privs(false)
+ .restrict_self()
+ .unwrap(),
+ RestrictionStatus {
+ ruleset: RulesetStatus::NotEnforced,
+ no_new_privs: false,
+ }
+ );
+
+ assert!(matches!(
+ Ruleset::from(ABI::Unsupported)
+ // Empty access-rights
+ .handle_access(AccessFs::from_all(ABI::Unsupported))
+ .unwrap_err(),
+ RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
+ CompatError::Access(AccessError::Empty)
+ )))
+ ));
+
+ assert!(matches!(
+ Ruleset::from(ABI::Unsupported)
+ // No handle_access() call.
+ .create()
+ .unwrap_err(),
+ RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
+ ));
+
+ assert!(matches!(
+ Ruleset::from(ABI::V1)
+ // Empty access-rights
+ .handle_access(AccessFs::from_all(ABI::Unsupported))
+ .unwrap_err(),
+ RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
+ CompatError::Access(AccessError::Empty)
+ )))
+ ));
+
+ // Tests inconsistency between the ruleset handled access-rights and the rule access-rights.
+ for handled_access in &[
+ make_bitflags!(AccessFs::{Execute | WriteFile}),
+ AccessFs::Execute.into(),
+ ] {
+ let ruleset = Ruleset::from(ABI::V1)
+ .handle_access(*handled_access)
+ .unwrap();
+ // Fakes a call to create() to test without involving the kernel (i.e. no
+ // landlock_ruleset_create() call).
+ let ruleset_created = RulesetCreated::new(ruleset, -1);
+ assert!(matches!(
+ ruleset_created
+ .add_rule(PathBeneath::new(
+ PathFd::new("/").unwrap(),
+ AccessFs::ReadFile
+ ))
+ .unwrap_err(),
+ RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
+ ));
+ }
+}
+
+#[test]
+fn ignore_abi_v2_with_abi_v1() {
+ // We don't need kernel/CI support for Landlock because no related syscalls should actually be
+ // performed.
+ assert_eq!(
+ Ruleset::from(ABI::V1)
+ .set_compatibility(CompatLevel::HardRequirement)
+ .handle_access(AccessFs::from_all(ABI::V1))
+ .unwrap()
+ .set_compatibility(CompatLevel::SoftRequirement)
+ // Because Ruleset only supports V1, Refer will be ignored.
+ .handle_access(AccessFs::Refer)
+ .unwrap()
+ .create()
+ .unwrap()
+ .add_rule(PathBeneath::new(
+ PathFd::new("/tmp").unwrap(),
+ AccessFs::from_all(ABI::V2)
+ ))
+ .unwrap()
+ .add_rule(PathBeneath::new(
+ PathFd::new("/usr").unwrap(),
+ make_bitflags!(AccessFs::{ReadFile | ReadDir})
+ ))
+ .unwrap()
+ .restrict_self()
+ .unwrap(),
+ RestrictionStatus {
+ ruleset: RulesetStatus::NotEnforced,
+ no_new_privs: true,
+ }
+ );
+}
+
+#[test]
+fn unsupported_handled_access() {
+ matches!(
+ Ruleset::from(ABI::V3)
+ .handle_access(AccessNet::from_all(ABI::V3))
+ .unwrap_err(),
+ RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
+ CompatError::Access(AccessError::Empty)
+ )))
+ );
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +
/* automatically generated by rust-bindgen 0.69.4 */
+
+pub const __BITS_PER_LONG: u32 = 64;
+pub const __BITS_PER_LONG_LONG: u32 = 64;
+pub const __FD_SETSIZE: u32 = 1024;
+pub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1;
+pub const LANDLOCK_ACCESS_FS_EXECUTE: u32 = 1;
+pub const LANDLOCK_ACCESS_FS_WRITE_FILE: u32 = 2;
+pub const LANDLOCK_ACCESS_FS_READ_FILE: u32 = 4;
+pub const LANDLOCK_ACCESS_FS_READ_DIR: u32 = 8;
+pub const LANDLOCK_ACCESS_FS_REMOVE_DIR: u32 = 16;
+pub const LANDLOCK_ACCESS_FS_REMOVE_FILE: u32 = 32;
+pub const LANDLOCK_ACCESS_FS_MAKE_CHAR: u32 = 64;
+pub const LANDLOCK_ACCESS_FS_MAKE_DIR: u32 = 128;
+pub const LANDLOCK_ACCESS_FS_MAKE_REG: u32 = 256;
+pub const LANDLOCK_ACCESS_FS_MAKE_SOCK: u32 = 512;
+pub const LANDLOCK_ACCESS_FS_MAKE_FIFO: u32 = 1024;
+pub const LANDLOCK_ACCESS_FS_MAKE_BLOCK: u32 = 2048;
+pub const LANDLOCK_ACCESS_FS_MAKE_SYM: u32 = 4096;
+pub const LANDLOCK_ACCESS_FS_REFER: u32 = 8192;
+pub const LANDLOCK_ACCESS_FS_TRUNCATE: u32 = 16384;
+pub const LANDLOCK_ACCESS_FS_IOCTL_DEV: u32 = 32768;
+pub const LANDLOCK_ACCESS_NET_BIND_TCP: u32 = 1;
+pub const LANDLOCK_ACCESS_NET_CONNECT_TCP: u32 = 2;
+pub type __s8 = ::std::os::raw::c_schar;
+pub type __u8 = ::std::os::raw::c_uchar;
+pub type __s16 = ::std::os::raw::c_short;
+pub type __u16 = ::std::os::raw::c_ushort;
+pub type __s32 = ::std::os::raw::c_int;
+pub type __u32 = ::std::os::raw::c_uint;
+pub type __s64 = ::std::os::raw::c_longlong;
+pub type __u64 = ::std::os::raw::c_ulonglong;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct __kernel_fd_set {
+ pub fds_bits: [::std::os::raw::c_ulong; 16usize],
+}
+#[test]
+fn bindgen_test_layout___kernel_fd_set() {
+ const UNINIT: ::std::mem::MaybeUninit<__kernel_fd_set> = ::std::mem::MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ ::std::mem::size_of::<__kernel_fd_set>(),
+ 128usize,
+ concat!("Size of: ", stringify!(__kernel_fd_set))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<__kernel_fd_set>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(__kernel_fd_set))
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).fds_bits) as usize - ptr as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(__kernel_fd_set),
+ "::",
+ stringify!(fds_bits)
+ )
+ );
+}
+pub type __kernel_sighandler_t =
+ ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
+pub type __kernel_key_t = ::std::os::raw::c_int;
+pub type __kernel_mqd_t = ::std::os::raw::c_int;
+pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
+pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
+pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
+pub type __kernel_long_t = ::std::os::raw::c_long;
+pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
+pub type __kernel_ino_t = __kernel_ulong_t;
+pub type __kernel_mode_t = ::std::os::raw::c_uint;
+pub type __kernel_pid_t = ::std::os::raw::c_int;
+pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
+pub type __kernel_uid_t = ::std::os::raw::c_uint;
+pub type __kernel_gid_t = ::std::os::raw::c_uint;
+pub type __kernel_suseconds_t = __kernel_long_t;
+pub type __kernel_daddr_t = ::std::os::raw::c_int;
+pub type __kernel_uid32_t = ::std::os::raw::c_uint;
+pub type __kernel_gid32_t = ::std::os::raw::c_uint;
+pub type __kernel_size_t = __kernel_ulong_t;
+pub type __kernel_ssize_t = __kernel_long_t;
+pub type __kernel_ptrdiff_t = __kernel_long_t;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct __kernel_fsid_t {
+ pub val: [::std::os::raw::c_int; 2usize],
+}
+#[test]
+fn bindgen_test_layout___kernel_fsid_t() {
+ const UNINIT: ::std::mem::MaybeUninit<__kernel_fsid_t> = ::std::mem::MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ ::std::mem::size_of::<__kernel_fsid_t>(),
+ 8usize,
+ concat!("Size of: ", stringify!(__kernel_fsid_t))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<__kernel_fsid_t>(),
+ 4usize,
+ concat!("Alignment of ", stringify!(__kernel_fsid_t))
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).val) as usize - ptr as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(__kernel_fsid_t),
+ "::",
+ stringify!(val)
+ )
+ );
+}
+pub type __kernel_off_t = __kernel_long_t;
+pub type __kernel_loff_t = ::std::os::raw::c_longlong;
+pub type __kernel_old_time_t = __kernel_long_t;
+pub type __kernel_time_t = __kernel_long_t;
+pub type __kernel_time64_t = ::std::os::raw::c_longlong;
+pub type __kernel_clock_t = __kernel_long_t;
+pub type __kernel_timer_t = ::std::os::raw::c_int;
+pub type __kernel_clockid_t = ::std::os::raw::c_int;
+pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
+pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
+pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
+pub type __s128 = i128;
+pub type __u128 = u128;
+pub type __le16 = __u16;
+pub type __be16 = __u16;
+pub type __le32 = __u32;
+pub type __be32 = __u32;
+pub type __le64 = __u64;
+pub type __be64 = __u64;
+pub type __sum16 = __u16;
+pub type __wsum = __u32;
+pub type __poll_t = ::std::os::raw::c_uint;
+#[doc = " struct landlock_ruleset_attr - Ruleset definition\n\n Argument of sys_landlock_create_ruleset(). This structure can grow in\n future versions."]
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct landlock_ruleset_attr {
+ #[doc = " @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_)\n that is handled by this ruleset and should then be forbidden if no\n rule explicitly allow them: it is a deny-by-default list that should\n contain as much Landlock access rights as possible. Indeed, all\n Landlock filesystem access rights that are not part of\n handled_access_fs are allowed. This is needed for backward\n compatibility reasons. One exception is the\n %LANDLOCK_ACCESS_FS_REFER access right, which is always implicitly\n handled, but must still be explicitly handled to add new rules with\n this access right."]
+ pub handled_access_fs: __u64,
+ #[doc = " @handled_access_net: Bitmask of actions (cf. `Network flags`_)\n that is handled by this ruleset and should then be forbidden if no\n rule explicitly allow them."]
+ pub handled_access_net: __u64,
+}
+#[test]
+fn bindgen_test_layout_landlock_ruleset_attr() {
+ const UNINIT: ::std::mem::MaybeUninit<landlock_ruleset_attr> =
+ ::std::mem::MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ ::std::mem::size_of::<landlock_ruleset_attr>(),
+ 16usize,
+ concat!("Size of: ", stringify!(landlock_ruleset_attr))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<landlock_ruleset_attr>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(landlock_ruleset_attr))
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).handled_access_fs) as usize - ptr as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(landlock_ruleset_attr),
+ "::",
+ stringify!(handled_access_fs)
+ )
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).handled_access_net) as usize - ptr as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(landlock_ruleset_attr),
+ "::",
+ stringify!(handled_access_net)
+ )
+ );
+}
+#[doc = " @LANDLOCK_RULE_PATH_BENEATH: Type of a &struct\n landlock_path_beneath_attr ."]
+pub const landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH: landlock_rule_type = 1;
+#[doc = " @LANDLOCK_RULE_NET_PORT: Type of a &struct\n landlock_net_port_attr ."]
+pub const landlock_rule_type_LANDLOCK_RULE_NET_PORT: landlock_rule_type = 2;
+#[doc = " enum landlock_rule_type - Landlock rule type\n\n Argument of sys_landlock_add_rule()."]
+pub type landlock_rule_type = ::std::os::raw::c_uint;
+#[doc = " struct landlock_path_beneath_attr - Path hierarchy definition\n\n Argument of sys_landlock_add_rule()."]
+#[repr(C, packed)]
+#[derive(Debug, Copy, Clone)]
+pub struct landlock_path_beneath_attr {
+ #[doc = " @allowed_access: Bitmask of allowed actions for this file hierarchy\n (cf. `Filesystem flags`_)."]
+ pub allowed_access: __u64,
+ #[doc = " @parent_fd: File descriptor, preferably opened with ``O_PATH``,\n which identifies the parent directory of a file hierarchy, or just a\n file."]
+ pub parent_fd: __s32,
+}
+#[test]
+fn bindgen_test_layout_landlock_path_beneath_attr() {
+ const UNINIT: ::std::mem::MaybeUninit<landlock_path_beneath_attr> =
+ ::std::mem::MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ ::std::mem::size_of::<landlock_path_beneath_attr>(),
+ 12usize,
+ concat!("Size of: ", stringify!(landlock_path_beneath_attr))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<landlock_path_beneath_attr>(),
+ 1usize,
+ concat!("Alignment of ", stringify!(landlock_path_beneath_attr))
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).allowed_access) as usize - ptr as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(landlock_path_beneath_attr),
+ "::",
+ stringify!(allowed_access)
+ )
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).parent_fd) as usize - ptr as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(landlock_path_beneath_attr),
+ "::",
+ stringify!(parent_fd)
+ )
+ );
+}
+#[doc = " struct landlock_net_port_attr - Network port definition\n\n Argument of sys_landlock_add_rule()."]
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct landlock_net_port_attr {
+ #[doc = " @allowed_access: Bitmask of allowed access network for a port\n (cf. `Network flags`_)."]
+ pub allowed_access: __u64,
+ #[doc = " @port: Network port in host endianness.\n\n It should be noted that port 0 passed to :manpage:`bind(2)` will\n bind to an available port from a specific port range. This can be\n configured thanks to the ``/proc/sys/net/ipv4/ip_local_port_range``\n sysctl (also used for IPv6). A Landlock rule with port 0 and the\n ``LANDLOCK_ACCESS_NET_BIND_TCP`` right means that requesting to bind\n on port 0 is allowed and it will automatically translate to binding\n on the related port range."]
+ pub port: __u64,
+}
+#[test]
+fn bindgen_test_layout_landlock_net_port_attr() {
+ const UNINIT: ::std::mem::MaybeUninit<landlock_net_port_attr> =
+ ::std::mem::MaybeUninit::uninit();
+ let ptr = UNINIT.as_ptr();
+ assert_eq!(
+ ::std::mem::size_of::<landlock_net_port_attr>(),
+ 16usize,
+ concat!("Size of: ", stringify!(landlock_net_port_attr))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<landlock_net_port_attr>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(landlock_net_port_attr))
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).allowed_access) as usize - ptr as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(landlock_net_port_attr),
+ "::",
+ stringify!(allowed_access)
+ )
+ );
+ assert_eq!(
+ unsafe { ::std::ptr::addr_of!((*ptr).port) as usize - ptr as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(landlock_net_port_attr),
+ "::",
+ stringify!(port)
+ )
+ );
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +
#[allow(dead_code)]
+#[allow(non_camel_case_types)]
+#[allow(non_snake_case)]
+#[allow(non_upper_case_globals)]
+mod landlock;
+
+#[rustfmt::skip]
+pub use self::landlock::{
+ landlock_net_port_attr,
+ landlock_path_beneath_attr,
+ landlock_rule_type,
+ landlock_rule_type_LANDLOCK_RULE_NET_PORT,
+ landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH,
+ landlock_ruleset_attr,
+ LANDLOCK_ACCESS_FS_EXECUTE,
+ LANDLOCK_ACCESS_FS_WRITE_FILE,
+ LANDLOCK_ACCESS_FS_READ_FILE,
+ LANDLOCK_ACCESS_FS_READ_DIR,
+ LANDLOCK_ACCESS_FS_REMOVE_DIR,
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ LANDLOCK_ACCESS_FS_MAKE_CHAR,
+ LANDLOCK_ACCESS_FS_MAKE_DIR,
+ LANDLOCK_ACCESS_FS_MAKE_REG,
+ LANDLOCK_ACCESS_FS_MAKE_SOCK,
+ LANDLOCK_ACCESS_FS_MAKE_FIFO,
+ LANDLOCK_ACCESS_FS_MAKE_BLOCK,
+ LANDLOCK_ACCESS_FS_MAKE_SYM,
+ LANDLOCK_ACCESS_FS_REFER,
+ LANDLOCK_ACCESS_FS_TRUNCATE,
+ LANDLOCK_ACCESS_FS_IOCTL_DEV,
+ LANDLOCK_ACCESS_NET_BIND_TCP,
+ LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ LANDLOCK_CREATE_RULESET_VERSION,
+};
+
+use libc::{
+ __u32, c_int, c_void, size_t, syscall, SYS_landlock_add_rule, SYS_landlock_create_ruleset,
+ SYS_landlock_restrict_self,
+};
+
+#[rustfmt::skip]
+pub unsafe fn landlock_create_ruleset(attr: *const landlock_ruleset_attr, size: size_t,
+ flags: __u32) -> c_int {
+ syscall(SYS_landlock_create_ruleset, attr, size, flags) as c_int
+}
+
+#[rustfmt::skip]
+pub unsafe fn landlock_add_rule(ruleset_fd: c_int, rule_type: landlock_rule_type,
+ rule_attr: *const c_void, flags: __u32) -> c_int {
+ syscall(SYS_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags) as c_int
+}
+
+pub unsafe fn landlock_restrict_self(ruleset_fd: c_int, flags: __u32) -> c_int {
+ syscall(SYS_landlock_restrict_self, ruleset_fd, flags) as c_int
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`