landlock/restrict_self.rs
1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3//! Restrict_self flag configuration.
4//!
5//! The [`RestrictSelfAttr`] trait provides the
6//! [`log_subdomains()`](RestrictSelfAttr::log_subdomains) setter shared
7//! between [`RulesetCreated`](crate::RulesetCreated) (with a domain) and
8//! [`RestrictSelf`] (without a domain).
9//!
10//! Domain-specific setters ([`log_same_exec()`](crate::RulesetCreatedAttr::log_same_exec),
11//! [`log_new_exec()`](crate::RulesetCreatedAttr::log_new_exec)) are on
12//! [`RulesetCreatedAttr`](crate::RulesetCreatedAttr) which requires
13//! `RestrictSelfAttr` as a supertrait.
14
15use crate::compat::private::OptionCompatLevelMut;
16use crate::compat::Compatibility;
17use crate::flags::{RestrictSelfFlag, SyscallFlagExt};
18use crate::prctl::try_set_no_new_privs;
19use crate::{
20 uapi, CompatLevel, CompatState, Compatible, LandlockStatus, RestrictSelfError, RulesetError,
21};
22use private::RestrictSelfFlagsState;
23
24#[cfg(test)]
25use crate::ABI;
26
27pub(crate) mod private {
28 use crate::RulesetError;
29
30 /// Private plumbing trait for types that store restrict_self flags.
31 ///
32 /// Follows the same pattern as
33 /// [`OptionCompatLevelMut`](crate::compat::private::OptionCompatLevelMut)
34 /// for [`Compatible`](crate::Compatible).
35 ///
36 /// The `try_set_flag()` method encapsulates all internal state access
37 /// (requested/actual flags and compat state) to avoid exposing
38 /// `pub(crate)` types in the trait interface.
39 pub trait RestrictSelfFlagsState {
40 fn try_set_flag(
41 &mut self,
42 flag: super::RestrictSelfFlag,
43 set: bool,
44 ) -> Result<(), RulesetError>;
45 }
46}
47
48/// Trait for types that accept restrict_self flag configuration.
49///
50/// Provides [`log_subdomains()`](Self::log_subdomains) which works both
51/// with and without a Landlock domain.
52///
53/// Implemented by [`RulesetCreated`](crate::RulesetCreated) (via
54/// [`RulesetCreatedAttr`](crate::RulesetCreatedAttr) supertrait) and
55/// [`RestrictSelf`].
56///
57/// Domain-specific setters (`log_same_exec`, `log_new_exec`) are on
58/// [`RulesetCreatedAttr`](crate::RulesetCreatedAttr).
59pub trait RestrictSelfAttr: Sized + private::RestrictSelfFlagsState {
60 /// Controls logging of denied accesses from nested Landlock domains.
61 /// Logging is **enabled** by default. See the
62 /// [kernel documentation](https://docs.kernel.org/userspace-api/landlock.html#enforcing-a-ruleset).
63 ///
64 /// Calling with `false` sets the `LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF` flag.
65 /// Setters are last-call-wins: calling again with a different boolean
66 /// re-configures the flag (e.g., `log_subdomains(false).log_subdomains(true)`
67 /// leaves logging enabled).
68 ///
69 /// Setting to the default value never triggers a compatibility check,
70 /// so it cannot error even under
71 /// [`CompatLevel::HardRequirement`](crate::CompatLevel::HardRequirement)
72 /// on an unsupported kernel.
73 ///
74 /// Available since Landlock [ABI v7](crate::ABI::V7).
75 ///
76 /// On error, returns a wrapped
77 /// [`SyscallFlagError<RestrictSelfFlag>`](crate::SyscallFlagError).
78 fn log_subdomains(mut self, set: bool) -> Result<Self, RulesetError> {
79 self.try_set_flag(RestrictSelfFlag::LogSubdomains, set)?;
80 Ok(self)
81 }
82}
83
84/// Builder for calling `landlock_restrict_self()` without creating a
85/// Landlock domain.
86///
87/// Use this when you want to configure `landlock_restrict_self()` flags
88/// without creating a ruleset or a Landlock domain (e.g., muting
89/// subdomain audit logs for nested domains).
90///
91/// Only [`log_subdomains()`](RestrictSelfAttr::log_subdomains) is available
92/// on this builder. Domain-specific setters
93/// ([`log_same_exec()`](crate::RulesetCreatedAttr::log_same_exec),
94/// [`log_new_exec()`](crate::RulesetCreatedAttr::log_new_exec)) require a
95/// Landlock domain via [`RulesetCreated`](crate::RulesetCreated).
96///
97/// Available since Landlock [ABI v7](crate::ABI::V7).
98///
99/// `no_new_privs` is enforced by default; call
100/// [`no_new_privs(false)`](Self::no_new_privs) to opt out.
101///
102/// # Example
103///
104/// ```no_run
105/// use landlock::*;
106///
107/// let status = RestrictSelf::default()
108/// .log_subdomains(false)?
109/// .apply()?;
110/// println!("Landlock status: {:?}", status.landlock);
111/// # Ok::<(), RulesetError>(())
112/// ```
113///
114/// Use [`set_compatibility()`](Compatible::set_compatibility) to control
115/// how unsupported flags are handled.
116///
117/// [`apply()`](Self::apply) returns a [`RestrictSelfStatus`] with the
118/// Landlock support status and the effective flag states. Its name
119/// differs from [`RulesetCreated::restrict_self()`](crate::RulesetCreated::restrict_self)
120/// to avoid the redundant `RestrictSelf::restrict_self()`.
121#[derive(Debug)]
122pub struct RestrictSelf {
123 requested_flags: u32,
124 actual_flags: u32,
125 no_new_privs: bool,
126 compat: Compatibility,
127}
128
129impl Default for RestrictSelf {
130 /// Returns a new `RestrictSelf`.
131 /// This call automatically probes the running kernel to know if it
132 /// supports Landlock.
133 fn default() -> Self {
134 Self {
135 requested_flags: 0,
136 actual_flags: 0,
137 no_new_privs: true,
138 compat: Compatibility::new(),
139 }
140 }
141}
142
143#[cfg(test)]
144impl From<ABI> for RestrictSelf {
145 fn from(abi: ABI) -> Self {
146 Self {
147 requested_flags: 0,
148 actual_flags: 0,
149 no_new_privs: true,
150 compat: Compatibility::from(abi),
151 }
152 }
153}
154
155impl RestrictSelfFlagsState for RestrictSelf {
156 fn try_set_flag(&mut self, flag: RestrictSelfFlag, set: bool) -> Result<(), RulesetError> {
157 let raw_bit = flag.raw_bit();
158 // Last-call-wins: requested tracks non-default user intent, actual
159 // tracks the bit that will be passed to the kernel.
160 //
161 // requested_flags is updated unconditionally; actual_flags is
162 // updated only if try_compat succeeds. On HardRequirement +
163 // unsupported, try_compat returns Err and requested_flags is
164 // left in a "user requested this" state; the builder is consumed
165 // by `?` on error so this inconsistency is not observable.
166 if set == flag.default_value() {
167 self.requested_flags &= !raw_bit;
168 } else {
169 self.requested_flags |= raw_bit;
170 }
171 if flag.try_compat(set, &mut self.compat)? {
172 self.actual_flags |= raw_bit;
173 } else {
174 self.actual_flags &= !raw_bit;
175 }
176 Ok(())
177 }
178}
179
180impl RestrictSelfAttr for RestrictSelf {}
181
182impl OptionCompatLevelMut for RestrictSelf {
183 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
184 &mut self.compat.level
185 }
186}
187
188impl Compatible for RestrictSelf {}
189
190/// Status returned by [`RestrictSelf::apply()`].
191///
192/// This is a proper subset of [`RestrictionStatus`](crate::RestrictionStatus):
193/// `log_same_exec` and `log_new_exec` are domain-specific and not configurable
194/// on [`RestrictSelf`], so they are not reported here; `ruleset` does not
195/// apply without a domain.
196#[derive(Debug, PartialEq, Eq)]
197#[non_exhaustive]
198pub struct RestrictSelfStatus {
199 /// Landlock support status of the running system.
200 pub landlock: LandlockStatus,
201 /// `no_new_privs` was successfully enforced via
202 /// `prctl(PR_SET_NO_NEW_PRIVS, 1)`.
203 pub no_new_privs: bool,
204 /// Subdomain logging is enabled (default: true).
205 pub log_subdomains: bool,
206}
207
208impl RestrictSelf {
209 /// Configures whether to call `prctl(PR_SET_NO_NEW_PRIVS)` during
210 /// [`apply()`](Self::apply). Defaults to `true`.
211 ///
212 /// This `prctl(2)` call is never ignored, even if an error was
213 /// encountered while [`CompatLevel::SoftRequirement`] was set.
214 ///
215 /// See [`RestrictSelfAttr::log_subdomains()`] for compat-state
216 /// behavior when toggling this setter on unsupported kernels.
217 pub fn no_new_privs(mut self, yes: bool) -> Self {
218 self.no_new_privs = yes;
219 self
220 }
221
222 /// Applies the configured restrict_self flags by calling
223 /// `landlock_restrict_self(-1, flags)`.
224 ///
225 /// If `no_new_privs` is configured (default), also calls
226 /// `prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)` first, since the kernel
227 /// requires `no_new_privs` (or `CAP_SYS_ADMIN`) for
228 /// `landlock_restrict_self()`. See
229 /// [`no_new_privs()`](Self::no_new_privs) to opt out.
230 ///
231 /// Returns a [`RestrictSelfStatus`] with the Landlock support status.
232 /// Skips the restrict_self syscall if no flags are enforceable.
233 pub fn apply(mut self) -> Result<RestrictSelfStatus, RulesetError> {
234 let enforced_nnp = if self.no_new_privs {
235 try_set_no_new_privs(&mut self.compat)?
236 } else {
237 false
238 };
239
240 let log_subdomains = RestrictSelfFlag::LogSubdomains.is_set(self.actual_flags);
241
242 let status = RestrictSelfStatus {
243 landlock: self.compat.status(),
244 no_new_privs: enforced_nnp,
245 log_subdomains,
246 };
247
248 // Skip the syscall when the compat state indicates no features are
249 // enforceable, mirroring RulesetCreated::restrict_self().
250 match self.compat.state {
251 CompatState::Init | CompatState::No | CompatState::Dummy => return Ok(status),
252 CompatState::Full | CompatState::Partial => {
253 if self.actual_flags == 0 {
254 return Ok(status);
255 }
256 }
257 }
258
259 match unsafe { uapi::landlock_restrict_self(-1, self.actual_flags) } {
260 0 => Ok(status),
261 _ => Err(RestrictSelfError::RestrictSelfCall {
262 source: std::io::Error::last_os_error(),
263 }
264 .into()),
265 }
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use crate::uapi;
273 use crate::*;
274
275 #[test]
276 fn restrict_self_default() {
277 let rs = RestrictSelf::default();
278 assert_eq!(rs.requested_flags, 0);
279 assert_eq!(rs.actual_flags, 0);
280
281 // apply() on an unconfigured RestrictSelf returns the kernel's default
282 // flag states. The compat state is Init, so no real syscall is made.
283 let status = rs.apply().unwrap();
284 assert!(status.log_subdomains);
285 }
286
287 #[test]
288 fn restrict_self_log_subdomains() {
289 // With mocked V7: flag should be set.
290 // TODO: Add real kernel test with audit parsing for end-to-end validation.
291 let rs = RestrictSelf {
292 requested_flags: 0,
293 actual_flags: 0,
294 no_new_privs: true,
295 compat: ABI::V7.into(),
296 };
297 let rs = rs.log_subdomains(false).unwrap();
298 assert_ne!(rs.requested_flags, 0);
299 assert_ne!(rs.actual_flags, 0);
300 assert_eq!(rs.requested_flags, rs.actual_flags);
301 }
302
303 #[test]
304 fn restrict_self_compatibility() {
305 // HardRequirement on unsupported ABI returns error.
306 let rs = RestrictSelf {
307 requested_flags: 0,
308 actual_flags: 0,
309 no_new_privs: true,
310 compat: ABI::Unsupported.into(),
311 };
312 assert!(matches!(
313 rs.set_compatibility(CompatLevel::HardRequirement)
314 .log_subdomains(false)
315 .unwrap_err(),
316 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
317 flag: RestrictSelfFlag::LogSubdomains,
318 set: false,
319 })
320 ));
321 }
322
323 #[test]
324 fn restrict_self_no_flags() {
325 // apply() with no flags set should skip the syscall.
326 let rs = RestrictSelf {
327 requested_flags: 0,
328 actual_flags: 0,
329 no_new_privs: true,
330 compat: ABI::V7.into(),
331 };
332 let status = rs.apply().unwrap();
333 assert!(matches!(status.landlock, LandlockStatus::Available { .. }));
334 assert!(status.log_subdomains); // default: enabled
335 }
336
337 #[test]
338 fn restrict_self_partial_no_op() {
339 // When all flags are dropped by BestEffort (actual_flags == 0),
340 // apply() should still return the Landlock status from the kernel probe.
341 let mut compat: Compatibility = ABI::V7.into();
342 compat.update(CompatState::No);
343 assert_eq!(compat.state, CompatState::No);
344
345 let rs = RestrictSelf {
346 requested_flags: 0b01,
347 actual_flags: 0,
348 no_new_privs: true,
349 compat,
350 };
351 let status = rs.apply().unwrap();
352 assert!(matches!(status.landlock, LandlockStatus::Available { .. }));
353 assert!(status.log_subdomains); // default: enabled (flag was dropped)
354 }
355
356 #[test]
357 fn restrict_self_best_effort_drops_unsupported() {
358 // On an unsupported ABI, BestEffort silently drops all flags.
359 let rs = RestrictSelf {
360 requested_flags: 0,
361 actual_flags: 0,
362 no_new_privs: true,
363 compat: ABI::Unsupported.into(),
364 };
365 let rs = rs.log_subdomains(false).unwrap();
366 assert_ne!(rs.requested_flags, 0);
367 assert_eq!(rs.actual_flags, 0);
368 let status = rs.apply().unwrap();
369 assert_eq!(status.landlock, LandlockStatus::NotImplemented);
370 assert!(status.log_subdomains); // flag was dropped, logging still enabled
371 }
372
373 #[test]
374 fn restrict_self_soft_requirement_drops_unsupported() {
375 // On an unsupported ABI, SoftRequirement transitions to Dummy and
376 // silently drops the flag (without erroring like HardRequirement).
377 let rs = RestrictSelf {
378 requested_flags: 0,
379 actual_flags: 0,
380 no_new_privs: true,
381 compat: ABI::Unsupported.into(),
382 };
383 let rs = rs
384 .set_compatibility(CompatLevel::SoftRequirement)
385 .log_subdomains(false)
386 .unwrap();
387 assert_ne!(rs.requested_flags, 0);
388 assert_eq!(rs.actual_flags, 0);
389 let status = rs.apply().unwrap();
390 assert_eq!(status.landlock, LandlockStatus::NotImplemented);
391 assert!(status.log_subdomains); // flag was dropped, logging still enabled
392 }
393
394 #[test]
395 fn restrict_self_subdomains_applied() {
396 let rs = RestrictSelf {
397 requested_flags: 0,
398 actual_flags: 0,
399 no_new_privs: true,
400 compat: ABI::V7.into(),
401 };
402 let rs = rs.log_subdomains(false).unwrap();
403 assert_ne!(rs.actual_flags, 0);
404 assert_ne!(
405 rs.actual_flags & uapi::LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
406 0
407 );
408 }
409
410 #[test]
411 fn restrict_self_hard_requirement_supported() {
412 let rs = RestrictSelf {
413 requested_flags: 0,
414 actual_flags: 0,
415 no_new_privs: true,
416 compat: ABI::V7.into(),
417 };
418 let rs = rs
419 .set_compatibility(CompatLevel::HardRequirement)
420 .log_subdomains(false)
421 .unwrap();
422 assert_ne!(rs.requested_flags, 0);
423 assert_ne!(rs.actual_flags, 0);
424 }
425
426 #[test]
427 fn restrict_self_last_call_wins() {
428 // Setting a flag to non-default then back to default should clear
429 // both requested and actual (last call wins).
430 let rs = RestrictSelf {
431 requested_flags: 0,
432 actual_flags: 0,
433 no_new_privs: true,
434 compat: ABI::V7.into(),
435 };
436 let rs = rs
437 .log_subdomains(false)
438 .unwrap()
439 .log_subdomains(true)
440 .unwrap();
441 assert_eq!(rs.requested_flags, 0);
442 assert_eq!(rs.actual_flags, 0);
443 }
444
445 #[test]
446 fn restrict_self_default_after_hard_requirement() {
447 // Setting a flag to its default value never requires a compat check,
448 // so HardRequirement on an unsupported ABI does not error.
449 let rs = RestrictSelf {
450 requested_flags: 0,
451 actual_flags: 0,
452 no_new_privs: true,
453 compat: ABI::Unsupported.into(),
454 };
455 let rs = rs
456 .set_compatibility(CompatLevel::HardRequirement)
457 .log_subdomains(true)
458 .unwrap();
459 assert_eq!(rs.requested_flags, 0);
460 assert_eq!(rs.actual_flags, 0);
461 }
462
463 #[test]
464 fn restrict_self_no_nnp() {
465 // With no_new_privs(false) and Unsupported (state Init, syscall
466 // skipped), apply() reports no_new_privs: false without calling
467 // prctl.
468 let rs = RestrictSelf {
469 requested_flags: 0,
470 actual_flags: 0,
471 no_new_privs: true,
472 compat: ABI::Unsupported.into(),
473 };
474 let status = rs.no_new_privs(false).apply().unwrap();
475 assert!(!status.no_new_privs);
476 assert_eq!(status.landlock, LandlockStatus::NotImplemented);
477 }
478}