diff --git a/src/compat.rs b/src/compat.rs index cb67c3b..fb9ad2b 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -70,6 +70,9 @@ pub enum ABI { /// Sixth Landlock ABI, introduced with /// [Linux 6.12](https://git.kernel.org/stable/c/e1b061b444fb01c237838f0d8238653afe6a8094). V6 = 6, + /// Seventh Landlock ABI, introduced with + /// [Linux 6.15](https://git.kernel.org/stable/c/72885116069abdd05c245707c3989fc605632970). + V7 = 7, } // ABI should not be dynamically created (in other crates) according to the running kernel @@ -94,8 +97,9 @@ impl From for ABI { 3 => ABI::V3, 4 => ABI::V4, 5 => ABI::V5, + 6 => ABI::V6, // Returns the greatest known ABI. - _ => ABI::V6, + _ => ABI::V7, } } } diff --git a/src/fs.rs b/src/fs.rs index df587c1..976dda2 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -112,11 +112,13 @@ impl AccessFs { pub fn from_read(abi: ABI) -> BitFlags { match abi { ABI::Unsupported => BitFlags::EMPTY, - ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 | ABI::V6 => make_bitflags!(AccessFs::{ - Execute - | ReadFile - | ReadDir - }), + ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 | ABI::V6 | ABI::V7 => { + make_bitflags!(AccessFs::{ + Execute + | ReadFile + | ReadDir + }) + } } } @@ -140,7 +142,7 @@ impl AccessFs { }), ABI::V2 => Self::from_write(ABI::V1) | AccessFs::Refer, ABI::V3 | ABI::V4 => Self::from_write(ABI::V2) | AccessFs::Truncate, - ABI::V5 | ABI::V6 => Self::from_write(ABI::V4) | AccessFs::IoctlDev, + ABI::V5 | ABI::V6 | ABI::V7 => Self::from_write(ABI::V4) | AccessFs::IoctlDev, } } diff --git a/src/lib.rs b/src/lib.rs index 175071f..b35d7e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,8 +92,8 @@ pub use errors::{ pub use fs::{path_beneath_rules, AccessFs, PathBeneath, PathFd}; pub use net::{AccessNet, NetPort}; pub use ruleset::{ - RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, RulesetCreatedAttr, - RulesetStatus, + RestrictSelfFlag, RestrictionStatus, Rule, Ruleset, RulesetAttr, RulesetCreated, + RulesetCreatedAttr, RulesetStatus, }; pub use scope::Scope; @@ -123,6 +123,7 @@ mod private { impl Sealed for crate::AccessFs {} impl Sealed for crate::AccessNet {} + impl Sealed for crate::RestrictSelfFlag {} impl Sealed for crate::Scope {} } diff --git a/src/net.rs b/src/net.rs index 1ba1dd4..05cfc29 100644 --- a/src/net.rs +++ b/src/net.rs @@ -57,7 +57,7 @@ impl Access for AccessNet { fn from_all(abi: ABI) -> BitFlags { match abi { ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 => BitFlags::EMPTY, - ABI::V4 | ABI::V5 | ABI::V6 => AccessNet::BindTcp | AccessNet::ConnectTcp, + ABI::V4 | ABI::V5 | ABI::V6 | ABI::V7 => AccessNet::BindTcp | AccessNet::ConnectTcp, } } } diff --git a/src/ruleset.rs b/src/ruleset.rs index d3f71ca..5329f6b 100644 --- a/src/ruleset.rs +++ b/src/ruleset.rs @@ -2,10 +2,11 @@ use crate::compat::private::OptionCompatLevelMut; use crate::{ - uapi, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel, CompatState, - Compatibility, Compatible, CreateRulesetError, HandledAccess, LandlockStatus, - PrivateHandledAccess, RestrictSelfError, RulesetError, Scope, ScopeError, TryCompat, + uapi, Access, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel, + CompatState, Compatibility, Compatible, CreateRulesetError, HandledAccess, LandlockStatus, + PrivateHandledAccess, RestrictSelfError, RulesetError, Scope, ScopeError, TryCompat, ABI, }; +use enumflags2::bitflags; use std::io::Error; use std::mem::size_of_val; use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd}; @@ -50,6 +51,65 @@ pub enum RulesetStatus { NotEnforced, } +/// Flags to use when applying ruleset restrictions +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[bitflags] +#[repr(u32)] +#[non_exhaustive] +pub enum RestrictSelfFlag { + /// Disables logging of denied accesses originating + /// from the thread creating the Landlock domain, as + /// well as its children, as long as they continue + /// running the same executable code (i.e., without + /// an intervening execve(2) call). This is intended + /// for programs that execute unknown code without + /// invoking execve(2), such as script interpreters. + /// Programs that only sandbox themselves should not + /// set this flag, so users can be notified of + /// unauthorized access attempts via system logs. + LogSameExecOff = uapi::LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF, + /// Enables logging of denied accesses after an + /// execve(2) call, providing visibility into + /// unauthorized access attempts by newly executed + /// programs within the created Landlock domain. + /// This flag is recommended only when all potential + /// executables in the domain are expected to comply + /// with the access restrictions, as excessive audit + /// log entries could make it more difficult to + /// identify critical events. + LogNewExecOn = uapi::LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON, + /// Disables logging of denied accesses originating + /// from nested Landlock domains created by the caller + /// or its descendants. This flag should be set + /// according to runtime configuration, not hardcoded, + /// to avoid suppressing important security events. + /// It is useful for container runtimes or sandboxing + /// tools that may launch programs which themselves + /// create Landlock domains and could otherwise + /// generate excessive logs. Unlike + /// LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF, this flag + /// only affects future nested domains, not the one + /// being created. It can also be used with a ruleset_fd + /// value of -1 to mute subdomain logs without creating + /// a domain. + LogSubdomainsOff = uapi::LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF, +} + +impl Access for RestrictSelfFlag { + fn from_all(abi: ABI) -> BitFlags { + match abi { + ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 | ABI::V6 => { + BitFlags::EMPTY + } + ABI::V7 => { + RestrictSelfFlag::LogSameExecOff + | RestrictSelfFlag::LogNewExecOn + | RestrictSelfFlag::LogSubdomainsOff + } + } + } +} + impl From for RulesetStatus { fn from(state: CompatState) -> Self { match state { @@ -746,14 +806,17 @@ impl RulesetCreated { } } - /// Attempts to restrict the calling thread with the ruleset + /// Attempts to restrict the calling thread with the ruleset and flags /// 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 { + pub fn restrict_self_with_flags( + mut self, + flags: BitFlags, + ) -> Result { let mut body = || -> Result { // 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 @@ -807,7 +870,7 @@ impl RulesetCreated { assert!(self.fd.is_some()); // Does not consume ruleset FD, which will be automatically closed after this block. let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1); - match unsafe { uapi::landlock_restrict_self(fd, 0) } { + match unsafe { uapi::landlock_restrict_self(fd, flags.bits()) } { 0 => { self.compat.update(CompatState::Full); Ok(RestrictionStatus { @@ -827,6 +890,17 @@ impl RulesetCreated { Ok(body()?) } + /// 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(self) -> Result { + return self.restrict_self_with_flags(BitFlags::::empty()); + } + /// Creates a new `RulesetCreated` instance by duplicating the underlying file descriptor. /// Rule modification will affect both `RulesetCreated` instances simultaneously. /// diff --git a/src/scope.rs b/src/scope.rs index 074c6b6..2e3b389 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -51,7 +51,7 @@ impl Access for Scope { fn from_all(abi: ABI) -> BitFlags { match abi { ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 => BitFlags::EMPTY, - ABI::V6 => Scope::AbstractUnixSocket | Scope::Signal, + ABI::V6 | ABI::V7 => Scope::AbstractUnixSocket | Scope::Signal, } } } diff --git a/src/uapi/landlock_all.rs b/src/uapi/landlock_all.rs index d34003c..73702f5 100644 --- a/src/uapi/landlock_all.rs +++ b/src/uapi/landlock_all.rs @@ -1,6 +1,10 @@ -/* automatically generated by rust-bindgen 0.72.0 */ +/* automatically generated by rust-bindgen 0.72.1 */ pub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1; +pub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2; +pub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1; +pub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2; +pub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4; 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; diff --git a/src/uapi/landlock_i686.rs b/src/uapi/landlock_i686.rs index 874b583..8d7c624 100644 --- a/src/uapi/landlock_i686.rs +++ b/src/uapi/landlock_i686.rs @@ -1,6 +1,10 @@ -/* automatically generated by rust-bindgen 0.72.0 */ +/* automatically generated by rust-bindgen 0.72.1 */ pub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1; +pub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2; +pub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1; +pub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2; +pub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4; 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; diff --git a/src/uapi/landlock_x86_64.rs b/src/uapi/landlock_x86_64.rs index 026ab6b..c085263 100644 --- a/src/uapi/landlock_x86_64.rs +++ b/src/uapi/landlock_x86_64.rs @@ -1,6 +1,10 @@ -/* automatically generated by rust-bindgen 0.72.0 */ +/* automatically generated by rust-bindgen 0.72.1 */ pub const LANDLOCK_CREATE_RULESET_VERSION: u32 = 1; +pub const LANDLOCK_CREATE_RULESET_ERRATA: u32 = 2; +pub const LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF: u32 = 1; +pub const LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON: u32 = 2; +pub const LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF: u32 = 4; 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; diff --git a/src/uapi/mod.rs b/src/uapi/mod.rs index ec7d1f1..c1c99b9 100644 --- a/src/uapi/mod.rs +++ b/src/uapi/mod.rs @@ -36,6 +36,9 @@ pub use self::landlock::{ landlock_rule_type_LANDLOCK_RULE_NET_PORT, landlock_rule_type_LANDLOCK_RULE_PATH_BENEATH, landlock_ruleset_attr, + LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF, + LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON, + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF, LANDLOCK_ACCESS_FS_EXECUTE, LANDLOCK_ACCESS_FS_WRITE_FILE, LANDLOCK_ACCESS_FS_READ_FILE,