1use crate::compat::private::OptionCompatLevelMut;
4use crate::flags::{RestrictSelfFlag, SyscallFlagExt};
5use crate::prctl::try_set_no_new_privs;
6use crate::restrict_self::private::RestrictSelfFlagsState;
7use crate::{
8 uapi, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel, CompatState,
9 Compatibility, Compatible, CreateRulesetError, HandledAccess, LandlockStatus,
10 PrivateHandledAccess, RestrictSelfAttr, RestrictSelfError, RulesetError, Scope, ScopeError,
11 TryCompat,
12};
13use std::io::Error;
14use std::mem::size_of_val;
15use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
16
17#[cfg(test)]
18use crate::*;
19
20pub trait Rule<T>: PrivateRule<T>
22where
23 T: HandledAccess,
24{
25}
26
27pub trait PrivateRule<T>
29where
30 Self: TryCompat<T> + Compatible,
31 T: HandledAccess,
32{
33 const TYPE_ID: uapi::landlock_rule_type;
34
35 fn as_ptr(&mut self) -> *const libc::c_void;
40
41 fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError>;
42}
43
44#[derive(Debug, PartialEq, Eq)]
46pub enum RulesetStatus {
47 FullyEnforced,
49 PartiallyEnforced,
52 NotEnforced,
55}
56
57impl From<CompatState> for RulesetStatus {
58 fn from(state: CompatState) -> Self {
59 match state {
60 CompatState::Init | CompatState::No | CompatState::Dummy => RulesetStatus::NotEnforced,
61 CompatState::Full => RulesetStatus::FullyEnforced,
62 CompatState::Partial => RulesetStatus::PartiallyEnforced,
63 }
64 }
65}
66
67#[derive(Debug, PartialEq, Eq)]
72#[non_exhaustive]
73pub struct RestrictionStatus {
74 pub ruleset: RulesetStatus,
76 pub no_new_privs: bool,
78 pub landlock: LandlockStatus,
80 pub log_same_exec: bool,
82 pub log_new_exec: bool,
84 pub log_subdomains: bool,
86 pub all_threads: bool,
88}
89
90#[derive(Debug)]
173pub struct Ruleset {
174 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
175 pub(crate) requested_handled_net: BitFlags<AccessNet>,
176 pub(crate) requested_scoped: BitFlags<Scope>,
177 pub(crate) actual_handled_fs: BitFlags<AccessFs>,
178 pub(crate) actual_handled_net: BitFlags<AccessNet>,
179 pub(crate) actual_scoped: BitFlags<Scope>,
180 pub(crate) compat: Compatibility,
181}
182
183impl From<Compatibility> for Ruleset {
184 fn from(compat: Compatibility) -> Self {
185 Ruleset {
186 requested_handled_fs: Default::default(),
188 requested_handled_net: Default::default(),
189 requested_scoped: Default::default(),
190 actual_handled_fs: Default::default(),
191 actual_handled_net: Default::default(),
192 actual_scoped: Default::default(),
193 compat,
194 }
195 }
196}
197
198#[cfg(test)]
199impl From<ABI> for Ruleset {
200 fn from(abi: ABI) -> Self {
201 Ruleset::from(Compatibility::from(abi))
202 }
203}
204
205#[test]
206fn ruleset_add_rule_iter() {
207 assert!(matches!(
208 Ruleset::from(ABI::Unsupported)
209 .handle_access(AccessFs::Execute)
210 .unwrap()
211 .create()
212 .unwrap()
213 .add_rule(PathBeneath::new(
214 PathFd::new("/").unwrap(),
215 AccessFs::ReadFile
216 ))
217 .unwrap_err(),
218 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
219 ));
220}
221
222impl Default for Ruleset {
223 fn default() -> Self {
230 Compatibility::new().into()
235 }
236}
237
238impl Ruleset {
239 #[allow(clippy::new_without_default)]
240 #[deprecated(note = "Use Ruleset::default() instead")]
241 pub fn new() -> Self {
242 Ruleset::default()
243 }
244
245 pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
250 let body = || -> Result<RulesetCreated, CreateRulesetError> {
251 match self.compat.state {
252 CompatState::Init => {
253 Err(CreateRulesetError::MissingHandledAccess)
256 }
257 CompatState::No | CompatState::Dummy => {
258 #[cfg(test)]
260 assert!(
261 !self.requested_handled_fs.is_empty()
262 || !self.requested_handled_net.is_empty()
263 || !self.requested_scoped.is_empty()
264 );
265
266 self.compat.update(CompatState::Dummy);
269 match self.compat.level.into() {
270 CompatLevel::HardRequirement => {
271 Err(CreateRulesetError::MissingHandledAccess)
272 }
273 _ => Ok(RulesetCreated::new(self, None)),
274 }
275 }
276 CompatState::Full | CompatState::Partial => {
277 #[cfg(test)]
279 assert!(
280 !self.actual_handled_fs.is_empty()
281 || !self.actual_handled_net.is_empty()
282 || !self.actual_scoped.is_empty()
283 );
284
285 let attr = uapi::landlock_ruleset_attr {
286 handled_access_fs: self.actual_handled_fs.bits(),
287 handled_access_net: self.actual_handled_net.bits(),
288 scoped: self.actual_scoped.bits(),
289 };
290 match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {
291 fd if fd >= 0 => Ok(RulesetCreated::new(
292 self,
293 Some(unsafe { OwnedFd::from_raw_fd(fd) }),
294 )),
295 _ => Err(CreateRulesetError::CreateRulesetCall {
296 source: Error::last_os_error(),
297 }),
298 }
299 }
300 }
301 };
302 Ok(body()?)
303 }
304}
305
306impl OptionCompatLevelMut for Ruleset {
307 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
308 &mut self.compat.level
309 }
310}
311
312impl OptionCompatLevelMut for &mut Ruleset {
313 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
314 &mut self.compat.level
315 }
316}
317
318impl Compatible for Ruleset {}
319
320impl Compatible for &mut Ruleset {}
321
322impl AsMut<Ruleset> for Ruleset {
323 fn as_mut(&mut self) -> &mut Ruleset {
324 self
325 }
326}
327
328#[test]
330fn ruleset_as_mut() {
331 let mut ruleset = Ruleset::from(ABI::Unsupported);
332 let _ = ruleset.as_mut();
333
334 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
335 .handle_access(AccessFs::Execute)
336 .unwrap()
337 .create()
338 .unwrap();
339 let _ = ruleset_created.as_mut();
340}
341
342pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
343 fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
351 where
352 T: Into<BitFlags<U>>,
353 U: HandledAccess + PrivateHandledAccess,
354 {
355 U::ruleset_handle_access(self.as_mut(), access.into())?;
356 Ok(self)
357 }
358
359 fn scope<T>(mut self, scope: T) -> Result<Self, RulesetError>
366 where
367 T: Into<BitFlags<Scope>>,
368 {
369 let scope = scope.into();
370 let ruleset = self.as_mut();
371 ruleset.requested_scoped |= scope;
372 if let Some(a) = scope
373 .try_compat(
374 ruleset.compat.abi(),
375 ruleset.compat.level,
376 &mut ruleset.compat.state,
377 )
378 .map_err(ScopeError::Compat)?
379 {
380 ruleset.actual_scoped |= a;
381 }
382 Ok(self)
383 }
384}
385
386impl RulesetAttr for Ruleset {}
387
388impl RulesetAttr for &mut Ruleset {}
389
390#[test]
391fn ruleset_attr() {
392 let mut ruleset = Ruleset::from(ABI::Unsupported);
393 let ruleset_ref = &mut ruleset;
394
395 ruleset_ref
397 .set_compatibility(CompatLevel::BestEffort)
398 .handle_access(AccessFs::Execute)
399 .unwrap()
400 .handle_access(AccessFs::ReadFile)
401 .unwrap();
402
403 ruleset
405 .set_compatibility(CompatLevel::BestEffort)
406 .handle_access(AccessFs::Execute)
407 .unwrap()
408 .handle_access(AccessFs::WriteFile)
409 .unwrap()
410 .create()
411 .unwrap();
412}
413
414#[test]
415fn ruleset_created_handle_access_fs() {
416 let access = make_bitflags!(AccessFs::{Execute | ReadDir});
417
418 let ruleset = Ruleset::from(ABI::V1).handle_access(access).unwrap();
420 assert_eq!(ruleset.requested_handled_fs, access);
421 assert_eq!(ruleset.actual_handled_fs, access);
422
423 let ruleset = Ruleset::from(ABI::V1)
425 .handle_access(AccessFs::Execute)
426 .unwrap()
427 .handle_access(AccessFs::ReadDir)
428 .unwrap()
429 .handle_access(AccessFs::Execute)
430 .unwrap();
431 assert_eq!(ruleset.requested_handled_fs, access);
432 assert_eq!(ruleset.actual_handled_fs, access);
433
434 assert!(matches!(Ruleset::from(ABI::Unsupported)
437 .handle_access(AccessFs::Execute)
438 .unwrap()
439 .set_compatibility(CompatLevel::HardRequirement)
440 .handle_access(AccessFs::ReadDir)
441 .unwrap_err(),
442 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
443 CompatError::Access(AccessError::Incompatible { access })
444 ))) if access == AccessFs::ReadDir
445 ));
446}
447
448#[test]
449fn ruleset_created_handle_access_net_tcp() {
450 let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
451
452 let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();
454 assert_eq!(ruleset.requested_handled_net, access);
455 assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);
456
457 let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
459 assert_eq!(ruleset.requested_handled_net, access);
460 assert_eq!(ruleset.actual_handled_net, access);
461
462 let ruleset = Ruleset::from(ABI::V4)
464 .handle_access(AccessNet::BindTcp)
465 .unwrap()
466 .handle_access(AccessNet::ConnectTcp)
467 .unwrap()
468 .handle_access(AccessNet::BindTcp)
469 .unwrap();
470 assert_eq!(ruleset.requested_handled_net, access);
471 assert_eq!(ruleset.actual_handled_net, access);
472
473 assert!(matches!(Ruleset::from(ABI::Unsupported)
476 .handle_access(AccessNet::BindTcp)
477 .unwrap()
478 .set_compatibility(CompatLevel::HardRequirement)
479 .handle_access(AccessNet::ConnectTcp)
480 .unwrap_err(),
481 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
482 CompatError::Access(AccessError::Incompatible { access })
483 ))) if access == AccessNet::ConnectTcp
484 ));
485}
486
487#[test]
488fn ruleset_created_scope() {
489 let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
490
491 let ruleset = Ruleset::from(ABI::V5).scope(scopes).unwrap();
493 assert_eq!(ruleset.requested_scoped, scopes);
494 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
495
496 let ruleset = Ruleset::from(ABI::V6).scope(scopes).unwrap();
498 assert_eq!(ruleset.requested_scoped, scopes);
499 assert_eq!(ruleset.actual_scoped, scopes);
500
501 let ruleset = Ruleset::from(ABI::V6)
503 .scope(Scope::AbstractUnixSocket)
504 .unwrap()
505 .scope(Scope::Signal)
506 .unwrap()
507 .scope(Scope::AbstractUnixSocket)
508 .unwrap();
509 assert_eq!(ruleset.requested_scoped, scopes);
510 assert_eq!(ruleset.actual_scoped, scopes);
511
512 assert!(matches!(Ruleset::from(ABI::Unsupported)
515 .scope(Scope::AbstractUnixSocket)
516 .unwrap()
517 .set_compatibility(CompatLevel::HardRequirement)
518 .scope(Scope::Signal)
519 .unwrap_err(),
520 RulesetError::Scope(ScopeError::Compat(
521 CompatError::Access(AccessError::Incompatible { access })
522 )) if access == Scope::Signal
523 ));
524}
525
526#[test]
527fn ruleset_created_fs_net_scope() {
528 let access_fs = make_bitflags!(AccessFs::{Execute | ReadDir});
529 let access_net = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
530 let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
531
532 let ruleset = Ruleset::from(ABI::V5)
534 .handle_access(access_fs)
535 .unwrap()
536 .scope(scopes)
537 .unwrap()
538 .handle_access(access_net)
539 .unwrap();
540 assert_eq!(ruleset.requested_handled_fs, access_fs);
541 assert_eq!(ruleset.actual_handled_fs, access_fs);
542 assert_eq!(ruleset.requested_handled_net, access_net);
543 assert_eq!(ruleset.actual_handled_net, access_net);
544 assert_eq!(ruleset.requested_scoped, scopes);
545 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
546
547 let ruleset = Ruleset::from(ABI::V6)
549 .handle_access(access_fs)
550 .unwrap()
551 .scope(scopes)
552 .unwrap()
553 .handle_access(access_net)
554 .unwrap();
555 assert_eq!(ruleset.requested_handled_fs, access_fs);
556 assert_eq!(ruleset.actual_handled_fs, access_fs);
557 assert_eq!(ruleset.requested_handled_net, access_net);
558 assert_eq!(ruleset.actual_handled_net, access_net);
559 assert_eq!(ruleset.requested_scoped, scopes);
560 assert_eq!(ruleset.actual_scoped, scopes);
561}
562
563#[test]
564fn ruleset_created_log_flags() {
565 let all_raw = uapi::LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
566 | uapi::LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
567 | uapi::LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF;
568
569 let ruleset_created = Ruleset::from(ABI::Unsupported)
571 .handle_access(AccessFs::Execute)
572 .unwrap()
573 .create()
574 .unwrap()
575 .log_same_exec(false)
576 .unwrap()
577 .log_new_exec(true)
578 .unwrap()
579 .log_subdomains(false)
580 .unwrap();
581 assert_eq!(ruleset_created.requested_restrict_self_flags, all_raw);
582 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
583
584 let ruleset_created = Ruleset::from(ABI::Unsupported)
586 .handle_access(AccessFs::Execute)
587 .unwrap()
588 .create()
589 .unwrap()
590 .log_same_exec(true)
591 .unwrap()
592 .log_new_exec(false)
593 .unwrap()
594 .log_subdomains(true)
595 .unwrap();
596 assert_eq!(ruleset_created.requested_restrict_self_flags, 0);
597 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
598
599 let ruleset_created = Ruleset::from(ABI::Unsupported)
601 .handle_access(AccessFs::Execute)
602 .unwrap()
603 .create()
604 .unwrap()
605 .set_compatibility(CompatLevel::SoftRequirement)
606 .log_same_exec(false)
607 .unwrap();
608 assert_eq!(
609 ruleset_created.requested_restrict_self_flags,
610 uapi::LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
611 );
612 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
613
614 Ruleset::from(ABI::Unsupported)
616 .handle_access(AccessFs::Execute)
617 .unwrap()
618 .create()
619 .unwrap()
620 .set_compatibility(CompatLevel::HardRequirement)
621 .log_same_exec(true)
622 .unwrap()
623 .log_new_exec(false)
624 .unwrap()
625 .log_subdomains(true)
626 .unwrap();
627
628 assert!(matches!(
630 Ruleset::from(ABI::Unsupported)
631 .handle_access(AccessFs::Execute)
632 .unwrap()
633 .create()
634 .unwrap()
635 .set_compatibility(CompatLevel::HardRequirement)
636 .log_same_exec(false)
637 .unwrap_err(),
638 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
639 flag: RestrictSelfFlag::LogSameExec,
640 set: false,
641 })
642 ));
643}
644
645#[test]
646fn ruleset_created_all_threads() {
647 let ruleset_created = Ruleset::from(ABI::Unsupported)
655 .handle_access(AccessFs::Execute)
656 .unwrap()
657 .create()
658 .unwrap()
659 .all_threads(true)
660 .unwrap();
661 assert_eq!(
662 ruleset_created.requested_restrict_self_flags,
663 uapi::LANDLOCK_RESTRICT_SELF_TSYNC
664 );
665 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
666
667 let ruleset_created = Ruleset::from(ABI::Unsupported)
671 .handle_access(AccessFs::Execute)
672 .unwrap()
673 .create()
674 .unwrap()
675 .set_compatibility(CompatLevel::HardRequirement)
676 .all_threads(false)
677 .unwrap();
678 assert_eq!(ruleset_created.requested_restrict_self_flags, 0);
679 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
680
681 assert!(matches!(
683 Ruleset::from(ABI::Unsupported)
684 .handle_access(AccessFs::Execute)
685 .unwrap()
686 .create()
687 .unwrap()
688 .set_compatibility(CompatLevel::HardRequirement)
689 .all_threads(true)
690 .unwrap_err(),
691 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
692 flag: RestrictSelfFlag::AllThreads,
693 set: true,
694 })
695 ));
696}
697
698impl OptionCompatLevelMut for RulesetCreated {
699 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
700 &mut self.compat.level
701 }
702}
703
704impl OptionCompatLevelMut for &mut RulesetCreated {
705 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
706 &mut self.compat.level
707 }
708}
709
710impl Compatible for RulesetCreated {}
711
712impl Compatible for &mut RulesetCreated {}
713
714impl RestrictSelfFlagsState for RulesetCreated {
715 fn try_set_flag(&mut self, flag: RestrictSelfFlag, set: bool) -> Result<(), RulesetError> {
716 let raw_bit = flag.raw_bit();
717 if set == flag.default_value() {
727 self.requested_restrict_self_flags &= !raw_bit;
728 } else {
729 self.requested_restrict_self_flags |= raw_bit;
730 }
731 if flag.try_compat(set, &mut self.compat)? {
732 self.actual_restrict_self_flags |= raw_bit;
733 } else {
734 self.actual_restrict_self_flags &= !raw_bit;
735 }
736 Ok(())
737 }
738}
739
740impl RestrictSelfFlagsState for &mut RulesetCreated {
741 fn try_set_flag(&mut self, flag: RestrictSelfFlag, set: bool) -> Result<(), RulesetError> {
742 (**self).try_set_flag(flag, set)
743 }
744}
745
746impl RestrictSelfAttr for RulesetCreated {}
747impl RestrictSelfAttr for &mut RulesetCreated {}
748
749pub trait RulesetCreatedAttr:
750 Sized + AsMut<RulesetCreated> + Compatible + RestrictSelfAttr
751{
752 fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
756 where
757 T: Rule<U>,
758 U: HandledAccess + PrivateHandledAccess,
759 {
760 let body = || -> Result<Self, AddRulesError> {
761 let self_ref = self.as_mut();
762 rule.check_consistency(self_ref)?;
763 let mut compat_rule = match rule
764 .try_compat(
765 self_ref.compat.abi(),
766 self_ref.compat.level,
767 &mut self_ref.compat.state,
768 )
769 .map_err(AddRuleError::Compat)?
770 {
771 Some(r) => r,
772 None => return Ok(self),
773 };
774 match self_ref.compat.state {
775 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
776 CompatState::Full | CompatState::Partial => {
777 #[cfg(test)]
778 assert!(self_ref.fd.is_some());
779 let fd = self_ref.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
780 match unsafe {
781 uapi::landlock_add_rule(fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
782 } {
783 0 => Ok(self),
784 _ => Err(AddRuleError::<U>::AddRuleCall {
785 source: Error::last_os_error(),
786 }
787 .into()),
788 }
789 }
790 }
791 };
792 Ok(body()?)
793 }
794
795 fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
870 where
871 I: IntoIterator<Item = Result<T, E>>,
872 T: Rule<U>,
873 U: HandledAccess + PrivateHandledAccess,
874 E: From<RulesetError>,
875 {
876 for rule in rules {
877 self = self.add_rule(rule?)?;
878 }
879 Ok(self)
880 }
881
882 fn no_new_privs(mut self, yes: bool) -> Self {
888 <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = yes;
889 self
890 }
891
892 #[deprecated(note = "Use no_new_privs() instead.")]
894 fn set_no_new_privs(self, yes: bool) -> Self {
895 self.no_new_privs(yes)
896 }
897
898 fn log_same_exec(mut self, set: bool) -> Result<Self, RulesetError> {
911 self.try_set_flag(RestrictSelfFlag::LogSameExec, set)?;
912 Ok(self)
913 }
914
915 fn log_new_exec(mut self, set: bool) -> Result<Self, RulesetError> {
927 self.try_set_flag(RestrictSelfFlag::LogNewExec, set)?;
928 Ok(self)
929 }
930}
931
932#[derive(Debug)]
934pub struct RulesetCreated {
935 fd: Option<OwnedFd>,
936 no_new_privs: bool,
937 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
938 pub(crate) requested_handled_net: BitFlags<AccessNet>,
939 requested_restrict_self_flags: u32,
940 actual_restrict_self_flags: u32,
941 compat: Compatibility,
942}
943
944impl RulesetCreated {
945 pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
946 #[cfg(test)]
948 assert!(!matches!(ruleset.compat.state, CompatState::Init));
949
950 RulesetCreated {
951 fd,
952 no_new_privs: true,
953 requested_handled_fs: ruleset.requested_handled_fs,
954 requested_handled_net: ruleset.requested_handled_net,
955 requested_restrict_self_flags: 0,
956 actual_restrict_self_flags: 0,
957 compat: ruleset.compat,
958 }
959 }
960
961 pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
969 let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
970 let enforced_nnp = if self.no_new_privs {
974 try_set_no_new_privs(&mut self.compat)?
975 } else {
976 false
977 };
978
979 let raw = self.actual_restrict_self_flags;
980 let log_same_exec = RestrictSelfFlag::LogSameExec.is_set(raw);
981 let log_new_exec = RestrictSelfFlag::LogNewExec.is_set(raw);
982 let log_subdomains = RestrictSelfFlag::LogSubdomains.is_set(raw);
983 let all_threads = RestrictSelfFlag::AllThreads.is_set(raw);
984
985 match self.compat.state {
986 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
987 ruleset: self.compat.state.into(),
988 landlock: self.compat.status(),
989 no_new_privs: enforced_nnp,
990 log_same_exec,
991 log_new_exec,
992 log_subdomains,
993 all_threads,
994 }),
995 CompatState::Full | CompatState::Partial => {
996 #[cfg(test)]
997 assert!(self.fd.is_some());
998 let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
1000 match unsafe {
1001 uapi::landlock_restrict_self(fd, self.actual_restrict_self_flags)
1002 } {
1003 0 => {
1004 self.compat.update(CompatState::Full);
1005 Ok(RestrictionStatus {
1006 ruleset: self.compat.state.into(),
1007 landlock: self.compat.status(),
1008 no_new_privs: enforced_nnp,
1009 log_same_exec,
1010 log_new_exec,
1011 log_subdomains,
1012 all_threads,
1013 })
1014 }
1015 _ => Err(RestrictSelfError::RestrictSelfCall {
1017 source: Error::last_os_error(),
1018 }),
1019 }
1020 }
1021 }
1022 };
1023 Ok(body()?)
1024 }
1025
1026 pub fn try_clone(&self) -> std::io::Result<Self> {
1031 Ok(RulesetCreated {
1032 fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
1033 no_new_privs: self.no_new_privs,
1034 requested_handled_fs: self.requested_handled_fs,
1035 requested_handled_net: self.requested_handled_net,
1036 requested_restrict_self_flags: self.requested_restrict_self_flags,
1037 actual_restrict_self_flags: self.actual_restrict_self_flags,
1038 compat: self.compat,
1039 })
1040 }
1041}
1042
1043impl From<RulesetCreated> for Option<OwnedFd> {
1044 fn from(ruleset: RulesetCreated) -> Self {
1045 ruleset.fd
1046 }
1047}
1048
1049#[test]
1050fn ruleset_created_ownedfd_none() {
1051 let ruleset = Ruleset::from(ABI::Unsupported)
1052 .handle_access(AccessFs::Execute)
1053 .unwrap()
1054 .create()
1055 .unwrap();
1056 let fd: Option<OwnedFd> = ruleset.into();
1057 assert!(fd.is_none());
1058}
1059
1060impl AsMut<RulesetCreated> for RulesetCreated {
1061 fn as_mut(&mut self) -> &mut RulesetCreated {
1062 self
1063 }
1064}
1065
1066impl RulesetCreatedAttr for RulesetCreated {}
1067
1068impl RulesetCreatedAttr for &mut RulesetCreated {}
1069
1070#[test]
1071fn ruleset_created_attr() {
1072 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
1073 .handle_access(AccessFs::Execute)
1074 .unwrap()
1075 .create()
1076 .unwrap();
1077 let ruleset_created_ref = &mut ruleset_created;
1078
1079 ruleset_created_ref
1081 .set_compatibility(CompatLevel::BestEffort)
1082 .add_rule(PathBeneath::new(
1083 PathFd::new("/usr").unwrap(),
1084 AccessFs::Execute,
1085 ))
1086 .unwrap()
1087 .add_rule(PathBeneath::new(
1088 PathFd::new("/etc").unwrap(),
1089 AccessFs::Execute,
1090 ))
1091 .unwrap();
1092
1093 assert_eq!(
1095 ruleset_created
1096 .set_compatibility(CompatLevel::BestEffort)
1097 .add_rule(PathBeneath::new(
1098 PathFd::new("/tmp").unwrap(),
1099 AccessFs::Execute,
1100 ))
1101 .unwrap()
1102 .add_rule(PathBeneath::new(
1103 PathFd::new("/var").unwrap(),
1104 AccessFs::Execute,
1105 ))
1106 .unwrap()
1107 .restrict_self()
1108 .unwrap(),
1109 RestrictionStatus {
1110 ruleset: RulesetStatus::NotEnforced,
1111 landlock: LandlockStatus::NotImplemented,
1112 no_new_privs: true,
1113 log_same_exec: true,
1114 log_new_exec: false,
1115 log_subdomains: true,
1116 all_threads: false,
1117 }
1118 );
1119}
1120
1121#[test]
1122fn ruleset_compat_dummy() {
1123 for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
1124 println!("level: {:?}", level);
1125
1126 let ruleset = Ruleset::from(ABI::Unsupported);
1128 assert_eq!(ruleset.compat.state, CompatState::Init);
1129
1130 let ruleset = ruleset.set_compatibility(level);
1131 assert_eq!(ruleset.compat.state, CompatState::Init);
1132
1133 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1134 assert_eq!(
1135 ruleset.compat.state,
1136 match level {
1137 CompatLevel::BestEffort => CompatState::No,
1138 CompatLevel::SoftRequirement => CompatState::Dummy,
1139 _ => unreachable!(),
1140 }
1141 );
1142
1143 let ruleset_created = ruleset.create().unwrap();
1144 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1147
1148 let ruleset_created = ruleset_created
1149 .add_rule(PathBeneath::new(
1150 PathFd::new("/usr").unwrap(),
1151 AccessFs::Execute,
1152 ))
1153 .unwrap();
1154 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1155 }
1156}
1157
1158#[test]
1159fn ruleset_compat_partial() {
1160 let ruleset = Ruleset::from(ABI::V1);
1162 assert_eq!(ruleset.compat.state, CompatState::Init);
1163
1164 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1166 assert_eq!(ruleset.compat.state, CompatState::No);
1167
1168 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1169 assert_eq!(ruleset.compat.state, CompatState::Partial);
1170
1171 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1173 assert_eq!(ruleset.compat.state, CompatState::Partial);
1174}
1175
1176#[test]
1177fn ruleset_unsupported() {
1178 assert_eq!(
1179 Ruleset::from(ABI::Unsupported)
1180 .handle_access(AccessFs::Execute)
1182 .unwrap()
1183 .create()
1184 .unwrap()
1185 .restrict_self()
1186 .unwrap(),
1187 RestrictionStatus {
1188 ruleset: RulesetStatus::NotEnforced,
1189 landlock: LandlockStatus::NotImplemented,
1190 no_new_privs: true,
1192 log_same_exec: true,
1193 log_new_exec: false,
1194 log_subdomains: true,
1195 all_threads: false,
1196 }
1197 );
1198
1199 assert_eq!(
1200 Ruleset::from(ABI::Unsupported)
1201 .set_compatibility(CompatLevel::SoftRequirement)
1203 .handle_access(AccessFs::Execute)
1204 .unwrap()
1205 .create()
1206 .unwrap()
1207 .restrict_self()
1208 .unwrap(),
1209 RestrictionStatus {
1210 ruleset: RulesetStatus::NotEnforced,
1211 landlock: LandlockStatus::NotImplemented,
1212 no_new_privs: true,
1214 log_same_exec: true,
1215 log_new_exec: false,
1216 log_subdomains: true,
1217 all_threads: false,
1218 }
1219 );
1220
1221 assert!(matches!(
1223 Ruleset::from(ABI::Unsupported)
1224 .set_compatibility(CompatLevel::HardRequirement)
1226 .handle_access(AccessFs::Execute)
1227 .unwrap_err(),
1228 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1229 CompatError::Access(AccessError::Incompatible { .. })
1230 )))
1231 ));
1232
1233 assert!(matches!(
1235 Ruleset::from(ABI::Unsupported)
1236 .set_compatibility(CompatLevel::HardRequirement)
1238 .scope(Scope::Signal)
1239 .unwrap_err(),
1240 RulesetError::Scope(ScopeError::Compat(CompatError::Access(
1241 AccessError::Incompatible { .. }
1242 )))
1243 ));
1244
1245 assert_eq!(
1246 Ruleset::from(ABI::Unsupported)
1247 .handle_access(AccessFs::Execute)
1248 .unwrap()
1249 .create()
1250 .unwrap()
1251 .set_compatibility(CompatLevel::SoftRequirement)
1253 .restrict_self()
1254 .unwrap(),
1255 RestrictionStatus {
1256 ruleset: RulesetStatus::NotEnforced,
1257 landlock: LandlockStatus::NotImplemented,
1258 no_new_privs: true,
1260 log_same_exec: true,
1261 log_new_exec: false,
1262 log_subdomains: true,
1263 all_threads: false,
1264 }
1265 );
1266
1267 if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
1269 assert_eq!(
1270 Ruleset::from(ABI::V1)
1271 .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
1272 .unwrap()
1273 .create()
1274 .unwrap()
1275 .set_compatibility(CompatLevel::SoftRequirement)
1277 .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
1278 .unwrap()
1279 .restrict_self()
1280 .unwrap(),
1281 RestrictionStatus {
1282 ruleset: RulesetStatus::NotEnforced,
1283 landlock: LandlockStatus::Available {
1284 effective_abi: ABI::V1,
1285 kernel_abi: None,
1286 },
1287 no_new_privs: true,
1290 log_same_exec: true,
1291 log_new_exec: false,
1292 log_subdomains: true,
1293 all_threads: false,
1294 }
1295 );
1296 }
1297
1298 assert_eq!(
1299 Ruleset::from(ABI::Unsupported)
1300 .handle_access(AccessFs::Execute)
1301 .unwrap()
1302 .create()
1303 .unwrap()
1304 .no_new_privs(false)
1305 .restrict_self()
1306 .unwrap(),
1307 RestrictionStatus {
1308 ruleset: RulesetStatus::NotEnforced,
1309 landlock: LandlockStatus::NotImplemented,
1310 no_new_privs: false,
1311 log_same_exec: true,
1312 log_new_exec: false,
1313 log_subdomains: true,
1314 all_threads: false,
1315 }
1316 );
1317
1318 assert!(matches!(
1320 Ruleset::from(ABI::Unsupported)
1321 .handle_access(AccessFs::from_all(ABI::Unsupported))
1323 .unwrap_err(),
1324 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1325 CompatError::Access(AccessError::Empty)
1326 )))
1327 ));
1328
1329 assert!(matches!(
1330 Ruleset::from(ABI::Unsupported)
1331 .create()
1333 .unwrap_err(),
1334 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1335 ));
1336
1337 assert!(matches!(
1339 Ruleset::from(ABI::V1)
1340 .handle_access(AccessFs::from_all(ABI::Unsupported))
1342 .unwrap_err(),
1343 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1344 CompatError::Access(AccessError::Empty)
1345 )))
1346 ));
1347
1348 assert!(matches!(
1350 Ruleset::from(ABI::Unsupported)
1351 .scope(Scope::from_all(ABI::Unsupported))
1352 .unwrap_err(),
1353 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1354 ));
1355
1356 assert!(matches!(
1358 Ruleset::from(ABI::V1)
1359 .scope(Scope::from_all(ABI::Unsupported))
1360 .unwrap_err(),
1361 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1362 ));
1363
1364 let ruleset = Ruleset::from(ABI::V1)
1366 .handle_access(AccessFs::Execute)
1367 .unwrap()
1368 .set_compatibility(CompatLevel::SoftRequirement)
1369 .scope(Scope::Signal)
1370 .unwrap();
1371 assert_eq!(ruleset.requested_scoped, BitFlags::from(Scope::Signal));
1372 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
1373
1374 assert_eq!(
1376 Ruleset::from(ABI::Unsupported)
1377 .handle_access(AccessFs::Execute)
1378 .unwrap()
1379 .create()
1380 .unwrap()
1381 .log_same_exec(false)
1382 .unwrap()
1383 .restrict_self()
1384 .unwrap(),
1385 RestrictionStatus {
1386 ruleset: RulesetStatus::NotEnforced,
1387 landlock: LandlockStatus::NotImplemented,
1388 no_new_privs: true,
1389 log_same_exec: true,
1390 log_new_exec: false,
1391 log_subdomains: true,
1392 all_threads: false,
1393 }
1394 );
1395
1396 assert!(matches!(
1398 Ruleset::from(ABI::Unsupported)
1399 .handle_access(AccessFs::Execute)
1400 .unwrap()
1401 .create()
1402 .unwrap()
1403 .set_compatibility(CompatLevel::HardRequirement)
1404 .log_new_exec(true)
1405 .unwrap_err(),
1406 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
1407 flag: RestrictSelfFlag::LogNewExec,
1408 set: true,
1409 })
1410 ));
1411
1412 for handled_access in &[
1414 make_bitflags!(AccessFs::{Execute | WriteFile}),
1415 AccessFs::Execute.into(),
1416 ] {
1417 let ruleset = Ruleset::from(ABI::V1)
1418 .handle_access(*handled_access)
1419 .unwrap();
1420 let ruleset_created = RulesetCreated::new(ruleset, None);
1423 assert!(matches!(
1424 ruleset_created
1425 .add_rule(PathBeneath::new(
1426 PathFd::new("/").unwrap(),
1427 AccessFs::ReadFile
1428 ))
1429 .unwrap_err(),
1430 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
1431 ));
1432 }
1433}
1434
1435#[test]
1436fn ignore_abi_v2_with_abi_v1() {
1437 assert_eq!(
1440 Ruleset::from(ABI::V1)
1441 .set_compatibility(CompatLevel::HardRequirement)
1442 .handle_access(AccessFs::from_all(ABI::V1))
1443 .unwrap()
1444 .set_compatibility(CompatLevel::SoftRequirement)
1445 .handle_access(AccessFs::Refer)
1447 .unwrap()
1448 .create()
1449 .unwrap()
1450 .add_rule(PathBeneath::new(
1451 PathFd::new("/tmp").unwrap(),
1452 AccessFs::from_all(ABI::V2)
1453 ))
1454 .unwrap()
1455 .add_rule(PathBeneath::new(
1456 PathFd::new("/usr").unwrap(),
1457 make_bitflags!(AccessFs::{ReadFile | ReadDir})
1458 ))
1459 .unwrap()
1460 .restrict_self()
1461 .unwrap(),
1462 RestrictionStatus {
1463 ruleset: RulesetStatus::NotEnforced,
1464 landlock: LandlockStatus::Available {
1465 effective_abi: ABI::V1,
1466 kernel_abi: None,
1467 },
1468 no_new_privs: true,
1469 log_same_exec: true,
1470 log_new_exec: false,
1471 log_subdomains: true,
1472 all_threads: false,
1473 }
1474 );
1475}
1476
1477#[test]
1478fn unsupported_handled_access() {
1479 assert!(matches!(
1480 Ruleset::from(ABI::V3)
1481 .handle_access(AccessNet::from_all(ABI::V3))
1482 .unwrap_err(),
1483 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
1484 CompatError::Access(AccessError::Empty)
1485 )))
1486 ));
1487}
1488
1489#[test]
1490fn unsupported_handled_access_errno() {
1491 assert_eq!(
1492 Errno::from(
1493 Ruleset::from(ABI::V3)
1494 .handle_access(AccessNet::from_all(ABI::V3))
1495 .unwrap_err()
1496 ),
1497 Errno::new(libc::EINVAL)
1498 );
1499}