landlock/flags.rs
1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3//! Syscall flag types for `landlock_restrict_self()` and future syscall flags.
4//!
5//! This module provides a compatibility mechanism for individual syscall flags,
6//! parallel to but simpler than the [`Access`](crate::Access) /
7//! [`TryCompat`](crate::TryCompat) machinery.
8//!
9//! Key differences from the [`Access`](crate::Access) pattern:
10//!
11//! - Access types ([`AccessFs`](crate::AccessFs), [`AccessNet`](crate::AccessNet),
12//! [`Scope`](crate::Scope)) operate on **sets** of rights passed as
13//! [`BitFlags<T>`](enumflags2::BitFlags), where the compat result can be Full,
14//! Partial, or No depending on which bits are supported. They use the
15//! [`TryCompat`](crate::TryCompat) trait and the full
16//! [`AccessError`](crate::AccessError) hierarchy.
17//!
18//! - Syscall flags are set **individually** through boolean setter methods
19//! (e.g., [`log_same_exec()`](crate::RulesetCreatedAttr::log_same_exec)), so
20//! the compat result is binary: supported or not. There is no Partial case,
21//! no set-level validation, and no need for an
22//! [`AccessError`](crate::AccessError)-like hierarchy. The
23//! [`try_compat()`](SyscallFlagExt::try_compat) default method on
24//! [`SyscallFlagExt`] handles the compat dispatch directly, using
25//! [`Compatibility::try_compat_binary()`](crate::compat::Compatibility::try_compat_binary)
26//! factored out from the No branch of
27//! [`TryCompat::try_compat()`](crate::TryCompat::try_compat).
28//!
29//! - Access types implement the [`Access`](crate::Access) trait (which requires
30//! [`BitFlag`](enumflags2::BitFlag)) and are used with
31//! [`BitFlags<T>`](enumflags2::BitFlags) throughout the builder. Syscall
32//! flags are plain enums (no `#[bitflags]`); the raw UAPI bitmask is built
33//! internally by OR-ing the constants returned by
34//! [`raw_bit()`](SyscallFlagExt::raw_bit) into a `u32`.
35
36use crate::compat::{Compatibility, ABI};
37use crate::errors::SyscallFlagError;
38use crate::private;
39use crate::uapi;
40
41/// Marker trait for syscall flag types used in compatibility checks.
42///
43/// This is the syscall-flag equivalent of the [`Access`](crate::Access) trait,
44/// but without the [`BitFlag`](enumflags2::BitFlag) requirement since syscall
45/// flags are set individually rather than as sets.
46///
47/// This trait is sealed and cannot be implemented outside of this crate.
48pub trait SyscallFlag: Copy + core::fmt::Debug + private::Sealed {}
49
50/// Internal extension providing compatibility logic for syscall flags.
51///
52/// This is the syscall-flag equivalent of [`TryCompat`](crate::TryCompat),
53/// simplified for single-flag boolean setters where the compat result is
54/// binary (supported or not) rather than Full/Partial/No.
55///
56/// Implementors provide [`default_value()`](Self::default_value),
57/// [`raw_bit()`](Self::raw_bit), and [`since()`](Self::since); the
58/// [`try_compat()`](Self::try_compat) default method handles the compat
59/// state update and level dispatch.
60pub(crate) trait SyscallFlagExt: SyscallFlag {
61 /// Returns the kernel's default state for this flag.
62 fn default_value(self) -> bool;
63
64 /// Returns the raw UAPI constant for this flag.
65 fn raw_bit(self) -> u32;
66
67 /// Returns the minimum ABI version that supports this flag.
68 fn since(self) -> ABI;
69
70 /// Checks compatibility and returns whether the non-default bit should
71 /// be applied.
72 ///
73 /// Returns `Ok(true)` if [`raw_bit()`](Self::raw_bit) should be set in
74 /// the actual flags. Returns `Ok(false)` if the flag should be cleared
75 /// to its default state, either because `set == default_value()` or
76 /// because the kernel does not support the flag and the compat level
77 /// permits dropping it. Returns `Err` if the flag is unsupported with
78 /// [`CompatLevel::HardRequirement`](crate::CompatLevel::HardRequirement).
79 ///
80 /// Setting to the default value never requires a compat check.
81 ///
82 /// This mirrors the compat dispatch in
83 /// [`TryCompat::try_compat()`](crate::TryCompat::try_compat) but without
84 /// the Partial case (a single flag is either fully supported or not).
85 fn try_compat(
86 self,
87 set: bool,
88 compat: &mut Compatibility,
89 ) -> Result<bool, SyscallFlagError<Self>> {
90 if set == self.default_value() {
91 // Setting to the default value is always safe; no compat check
92 // needed and the caller should clear the bit.
93 return Ok(false);
94 }
95 compat.try_compat_binary(compat.abi() >= self.since(), || {
96 SyscallFlagError::NotSupported { flag: self, set }
97 })
98 }
99}
100
101/// Identifies a configuration flag for the `landlock_restrict_self()` syscall.
102///
103/// Unlike access rights ([`AccessFs`](crate::AccessFs), [`AccessNet`](crate::AccessNet))
104/// and scopes ([`Scope`](crate::Scope)), these flags are not passed to
105/// `landlock_create_ruleset()` but to `landlock_restrict_self()`. They control
106/// audit logging behavior rather than access restrictions.
107///
108/// Each flag is set through a dedicated boolean method on
109/// [`RulesetCreatedAttr`](crate::RulesetCreatedAttr) or
110/// [`RestrictSelfAttr`](crate::RestrictSelfAttr). The polarity mapping
111/// (e.g., `log_same_exec(false)` maps to `LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF`)
112/// is handled internally.
113#[derive(Copy, Clone, Debug, PartialEq, Eq)]
114#[non_exhaustive]
115// All variants intentionally share the "Log" prefix to match the kernel's
116// LANDLOCK_RESTRICT_SELF_LOG_* naming convention. Future non-logging flags
117// (e.g., TSYNC) will break the shared prefix, removing the need for this allow.
118#[allow(clippy::enum_variant_names)]
119pub enum RestrictSelfFlag {
120 /// Same-exec logging (see [`RulesetCreatedAttr::log_same_exec()`](crate::RulesetCreatedAttr::log_same_exec)).
121 LogSameExec,
122 /// New-exec logging (see [`RulesetCreatedAttr::log_new_exec()`](crate::RulesetCreatedAttr::log_new_exec)).
123 LogNewExec,
124 /// Subdomain logging (see [`RestrictSelfAttr::log_subdomains()`](crate::RestrictSelfAttr::log_subdomains)).
125 LogSubdomains,
126}
127
128impl SyscallFlag for RestrictSelfFlag {}
129
130impl RestrictSelfFlag {
131 /// Returns the effective state of this flag given a raw bitmask of
132 /// applied flags.
133 pub(crate) fn is_set(self, raw_flags: u32) -> bool {
134 // Each flag's raw_bit() returns either an OFF or ON UAPI constant
135 // depending on the kernel's default for that flag. When the bit
136 // is set in raw_flags, the kernel applies the opposite of the
137 // default; when unset, the default applies.
138 if raw_flags & self.raw_bit() != 0 {
139 !self.default_value()
140 } else {
141 self.default_value()
142 }
143 }
144}
145
146impl SyscallFlagExt for RestrictSelfFlag {
147 fn default_value(self) -> bool {
148 match self {
149 // Same-exec logging is enabled by default.
150 Self::LogSameExec => true,
151 // New-exec logging is disabled by default.
152 Self::LogNewExec => false,
153 // Subdomain logging is enabled by default.
154 Self::LogSubdomains => true,
155 }
156 }
157
158 fn raw_bit(self) -> u32 {
159 match self {
160 Self::LogSameExec => uapi::LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF,
161 Self::LogNewExec => uapi::LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON,
162 Self::LogSubdomains => uapi::LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
163 }
164 }
165
166 fn since(self) -> ABI {
167 match self {
168 Self::LogSameExec => ABI::V7,
169 Self::LogNewExec => ABI::V7,
170 Self::LogSubdomains => ABI::V7,
171 }
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::uapi;
179
180 #[test]
181 fn restrict_self_flag_raw_bit() {
182 assert_eq!(
183 RestrictSelfFlag::LogSameExec.raw_bit(),
184 uapi::LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF,
185 );
186 assert_eq!(
187 RestrictSelfFlag::LogNewExec.raw_bit(),
188 uapi::LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON,
189 );
190 assert_eq!(
191 RestrictSelfFlag::LogSubdomains.raw_bit(),
192 uapi::LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
193 );
194 }
195
196 #[test]
197 fn restrict_self_flag_default_value() {
198 assert!(RestrictSelfFlag::LogSameExec.default_value());
199 assert!(!RestrictSelfFlag::LogNewExec.default_value());
200 assert!(RestrictSelfFlag::LogSubdomains.default_value());
201 }
202
203 #[test]
204 fn restrict_self_flag_since() {
205 assert_eq!(RestrictSelfFlag::LogSameExec.since(), ABI::V7);
206 assert_eq!(RestrictSelfFlag::LogNewExec.since(), ABI::V7);
207 assert_eq!(RestrictSelfFlag::LogSubdomains.since(), ABI::V7);
208 }
209}