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 quiet_access_fs: 0,
290 quiet_access_net: 0,
291 quiet_scoped: 0,
292 };
293 match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {
294 fd if fd >= 0 => Ok(RulesetCreated::new(
295 self,
296 Some(unsafe { OwnedFd::from_raw_fd(fd) }),
297 )),
298 _ => Err(CreateRulesetError::CreateRulesetCall {
299 source: Error::last_os_error(),
300 }),
301 }
302 }
303 }
304 };
305 Ok(body()?)
306 }
307}
308
309impl OptionCompatLevelMut for Ruleset {
310 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
311 &mut self.compat.level
312 }
313}
314
315impl OptionCompatLevelMut for &mut Ruleset {
316 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
317 &mut self.compat.level
318 }
319}
320
321impl Compatible for Ruleset {}
322
323impl Compatible for &mut Ruleset {}
324
325impl AsMut<Ruleset> for Ruleset {
326 fn as_mut(&mut self) -> &mut Ruleset {
327 self
328 }
329}
330
331#[test]
333fn ruleset_as_mut() {
334 let mut ruleset = Ruleset::from(ABI::Unsupported);
335 let _ = ruleset.as_mut();
336
337 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
338 .handle_access(AccessFs::Execute)
339 .unwrap()
340 .create()
341 .unwrap();
342 let _ = ruleset_created.as_mut();
343}
344
345pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
346 fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
354 where
355 T: Into<BitFlags<U>>,
356 U: HandledAccess + PrivateHandledAccess,
357 {
358 U::ruleset_handle_access(self.as_mut(), access.into())?;
359 Ok(self)
360 }
361
362 fn scope<T>(mut self, scope: T) -> Result<Self, RulesetError>
369 where
370 T: Into<BitFlags<Scope>>,
371 {
372 let scope = scope.into();
373 let ruleset = self.as_mut();
374 ruleset.requested_scoped |= scope;
375 if let Some(a) = scope
376 .try_compat(
377 ruleset.compat.abi(),
378 ruleset.compat.level,
379 &mut ruleset.compat.state,
380 )
381 .map_err(ScopeError::Compat)?
382 {
383 ruleset.actual_scoped |= a;
384 }
385 Ok(self)
386 }
387}
388
389impl RulesetAttr for Ruleset {}
390
391impl RulesetAttr for &mut Ruleset {}
392
393#[test]
394fn ruleset_attr() {
395 let mut ruleset = Ruleset::from(ABI::Unsupported);
396 let ruleset_ref = &mut ruleset;
397
398 ruleset_ref
400 .set_compatibility(CompatLevel::BestEffort)
401 .handle_access(AccessFs::Execute)
402 .unwrap()
403 .handle_access(AccessFs::ReadFile)
404 .unwrap();
405
406 ruleset
408 .set_compatibility(CompatLevel::BestEffort)
409 .handle_access(AccessFs::Execute)
410 .unwrap()
411 .handle_access(AccessFs::WriteFile)
412 .unwrap()
413 .create()
414 .unwrap();
415}
416
417#[test]
418fn ruleset_created_handle_access_fs() {
419 let access = make_bitflags!(AccessFs::{Execute | ReadDir});
420
421 let ruleset = Ruleset::from(ABI::V1).handle_access(access).unwrap();
423 assert_eq!(ruleset.requested_handled_fs, access);
424 assert_eq!(ruleset.actual_handled_fs, access);
425
426 let ruleset = Ruleset::from(ABI::V1)
428 .handle_access(AccessFs::Execute)
429 .unwrap()
430 .handle_access(AccessFs::ReadDir)
431 .unwrap()
432 .handle_access(AccessFs::Execute)
433 .unwrap();
434 assert_eq!(ruleset.requested_handled_fs, access);
435 assert_eq!(ruleset.actual_handled_fs, access);
436
437 assert!(matches!(Ruleset::from(ABI::Unsupported)
440 .handle_access(AccessFs::Execute)
441 .unwrap()
442 .set_compatibility(CompatLevel::HardRequirement)
443 .handle_access(AccessFs::ReadDir)
444 .unwrap_err(),
445 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
446 CompatError::Access(AccessError::Incompatible { access })
447 ))) if access == AccessFs::ReadDir
448 ));
449}
450
451#[test]
452fn ruleset_created_handle_access_net_tcp() {
453 let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
454
455 let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();
457 assert_eq!(ruleset.requested_handled_net, access);
458 assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);
459
460 let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
462 assert_eq!(ruleset.requested_handled_net, access);
463 assert_eq!(ruleset.actual_handled_net, access);
464
465 let ruleset = Ruleset::from(ABI::V4)
467 .handle_access(AccessNet::BindTcp)
468 .unwrap()
469 .handle_access(AccessNet::ConnectTcp)
470 .unwrap()
471 .handle_access(AccessNet::BindTcp)
472 .unwrap();
473 assert_eq!(ruleset.requested_handled_net, access);
474 assert_eq!(ruleset.actual_handled_net, access);
475
476 assert!(matches!(Ruleset::from(ABI::Unsupported)
479 .handle_access(AccessNet::BindTcp)
480 .unwrap()
481 .set_compatibility(CompatLevel::HardRequirement)
482 .handle_access(AccessNet::ConnectTcp)
483 .unwrap_err(),
484 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
485 CompatError::Access(AccessError::Incompatible { access })
486 ))) if access == AccessNet::ConnectTcp
487 ));
488}
489
490#[test]
491fn ruleset_created_scope() {
492 let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
493
494 let ruleset = Ruleset::from(ABI::V5).scope(scopes).unwrap();
496 assert_eq!(ruleset.requested_scoped, scopes);
497 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
498
499 let ruleset = Ruleset::from(ABI::V6).scope(scopes).unwrap();
501 assert_eq!(ruleset.requested_scoped, scopes);
502 assert_eq!(ruleset.actual_scoped, scopes);
503
504 let ruleset = Ruleset::from(ABI::V6)
506 .scope(Scope::AbstractUnixSocket)
507 .unwrap()
508 .scope(Scope::Signal)
509 .unwrap()
510 .scope(Scope::AbstractUnixSocket)
511 .unwrap();
512 assert_eq!(ruleset.requested_scoped, scopes);
513 assert_eq!(ruleset.actual_scoped, scopes);
514
515 assert!(matches!(Ruleset::from(ABI::Unsupported)
518 .scope(Scope::AbstractUnixSocket)
519 .unwrap()
520 .set_compatibility(CompatLevel::HardRequirement)
521 .scope(Scope::Signal)
522 .unwrap_err(),
523 RulesetError::Scope(ScopeError::Compat(
524 CompatError::Access(AccessError::Incompatible { access })
525 )) if access == Scope::Signal
526 ));
527}
528
529#[test]
530fn ruleset_created_fs_net_scope() {
531 let access_fs = make_bitflags!(AccessFs::{Execute | ReadDir});
532 let access_net = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
533 let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
534
535 let ruleset = Ruleset::from(ABI::V5)
537 .handle_access(access_fs)
538 .unwrap()
539 .scope(scopes)
540 .unwrap()
541 .handle_access(access_net)
542 .unwrap();
543 assert_eq!(ruleset.requested_handled_fs, access_fs);
544 assert_eq!(ruleset.actual_handled_fs, access_fs);
545 assert_eq!(ruleset.requested_handled_net, access_net);
546 assert_eq!(ruleset.actual_handled_net, access_net);
547 assert_eq!(ruleset.requested_scoped, scopes);
548 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
549
550 let ruleset = Ruleset::from(ABI::V6)
552 .handle_access(access_fs)
553 .unwrap()
554 .scope(scopes)
555 .unwrap()
556 .handle_access(access_net)
557 .unwrap();
558 assert_eq!(ruleset.requested_handled_fs, access_fs);
559 assert_eq!(ruleset.actual_handled_fs, access_fs);
560 assert_eq!(ruleset.requested_handled_net, access_net);
561 assert_eq!(ruleset.actual_handled_net, access_net);
562 assert_eq!(ruleset.requested_scoped, scopes);
563 assert_eq!(ruleset.actual_scoped, scopes);
564}
565
566#[test]
567fn ruleset_created_log_flags() {
568 let all_raw = uapi::LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
569 | uapi::LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
570 | uapi::LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF;
571
572 let ruleset_created = Ruleset::from(ABI::Unsupported)
574 .handle_access(AccessFs::Execute)
575 .unwrap()
576 .create()
577 .unwrap()
578 .log_same_exec(false)
579 .unwrap()
580 .log_new_exec(true)
581 .unwrap()
582 .log_subdomains(false)
583 .unwrap();
584 assert_eq!(ruleset_created.requested_restrict_self_flags, all_raw);
585 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
586
587 let ruleset_created = Ruleset::from(ABI::Unsupported)
589 .handle_access(AccessFs::Execute)
590 .unwrap()
591 .create()
592 .unwrap()
593 .log_same_exec(true)
594 .unwrap()
595 .log_new_exec(false)
596 .unwrap()
597 .log_subdomains(true)
598 .unwrap();
599 assert_eq!(ruleset_created.requested_restrict_self_flags, 0);
600 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
601
602 let ruleset_created = Ruleset::from(ABI::Unsupported)
604 .handle_access(AccessFs::Execute)
605 .unwrap()
606 .create()
607 .unwrap()
608 .set_compatibility(CompatLevel::SoftRequirement)
609 .log_same_exec(false)
610 .unwrap();
611 assert_eq!(
612 ruleset_created.requested_restrict_self_flags,
613 uapi::LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
614 );
615 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
616
617 Ruleset::from(ABI::Unsupported)
619 .handle_access(AccessFs::Execute)
620 .unwrap()
621 .create()
622 .unwrap()
623 .set_compatibility(CompatLevel::HardRequirement)
624 .log_same_exec(true)
625 .unwrap()
626 .log_new_exec(false)
627 .unwrap()
628 .log_subdomains(true)
629 .unwrap();
630
631 assert!(matches!(
633 Ruleset::from(ABI::Unsupported)
634 .handle_access(AccessFs::Execute)
635 .unwrap()
636 .create()
637 .unwrap()
638 .set_compatibility(CompatLevel::HardRequirement)
639 .log_same_exec(false)
640 .unwrap_err(),
641 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
642 flag: RestrictSelfFlag::LogSameExec,
643 set: false,
644 })
645 ));
646}
647
648#[test]
649fn ruleset_created_all_threads() {
650 let ruleset_created = Ruleset::from(ABI::Unsupported)
658 .handle_access(AccessFs::Execute)
659 .unwrap()
660 .create()
661 .unwrap()
662 .all_threads(true)
663 .unwrap();
664 assert_eq!(
665 ruleset_created.requested_restrict_self_flags,
666 uapi::LANDLOCK_RESTRICT_SELF_TSYNC
667 );
668 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
669
670 let ruleset_created = Ruleset::from(ABI::Unsupported)
674 .handle_access(AccessFs::Execute)
675 .unwrap()
676 .create()
677 .unwrap()
678 .set_compatibility(CompatLevel::HardRequirement)
679 .all_threads(false)
680 .unwrap();
681 assert_eq!(ruleset_created.requested_restrict_self_flags, 0);
682 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
683
684 assert!(matches!(
686 Ruleset::from(ABI::Unsupported)
687 .handle_access(AccessFs::Execute)
688 .unwrap()
689 .create()
690 .unwrap()
691 .set_compatibility(CompatLevel::HardRequirement)
692 .all_threads(true)
693 .unwrap_err(),
694 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
695 flag: RestrictSelfFlag::AllThreads,
696 set: true,
697 })
698 ));
699}
700
701impl OptionCompatLevelMut for RulesetCreated {
702 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
703 &mut self.compat.level
704 }
705}
706
707impl OptionCompatLevelMut for &mut RulesetCreated {
708 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
709 &mut self.compat.level
710 }
711}
712
713impl Compatible for RulesetCreated {}
714
715impl Compatible for &mut RulesetCreated {}
716
717impl RestrictSelfFlagsState for RulesetCreated {
718 fn try_set_flag(&mut self, flag: RestrictSelfFlag, set: bool) -> Result<(), RulesetError> {
719 let raw_bit = flag.raw_bit();
720 if set == flag.default_value() {
730 self.requested_restrict_self_flags &= !raw_bit;
731 } else {
732 self.requested_restrict_self_flags |= raw_bit;
733 }
734 if flag.try_compat(set, &mut self.compat)? {
735 self.actual_restrict_self_flags |= raw_bit;
736 } else {
737 self.actual_restrict_self_flags &= !raw_bit;
738 }
739 Ok(())
740 }
741}
742
743impl RestrictSelfFlagsState for &mut RulesetCreated {
744 fn try_set_flag(&mut self, flag: RestrictSelfFlag, set: bool) -> Result<(), RulesetError> {
745 (**self).try_set_flag(flag, set)
746 }
747}
748
749impl RestrictSelfAttr for RulesetCreated {}
750impl RestrictSelfAttr for &mut RulesetCreated {}
751
752pub trait RulesetCreatedAttr:
753 Sized + AsMut<RulesetCreated> + Compatible + RestrictSelfAttr
754{
755 fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
759 where
760 T: Rule<U>,
761 U: HandledAccess + PrivateHandledAccess,
762 {
763 let body = || -> Result<Self, AddRulesError> {
764 let self_ref = self.as_mut();
765 rule.check_consistency(self_ref)?;
766 let mut compat_rule = match rule
767 .try_compat(
768 self_ref.compat.abi(),
769 self_ref.compat.level,
770 &mut self_ref.compat.state,
771 )
772 .map_err(AddRuleError::Compat)?
773 {
774 Some(r) => r,
775 None => return Ok(self),
776 };
777 match self_ref.compat.state {
778 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
779 CompatState::Full | CompatState::Partial => {
780 #[cfg(test)]
781 assert!(self_ref.fd.is_some());
782 let fd = self_ref.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
783 match unsafe {
784 uapi::landlock_add_rule(fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
785 } {
786 0 => Ok(self),
787 _ => Err(AddRuleError::<U>::AddRuleCall {
788 source: Error::last_os_error(),
789 }
790 .into()),
791 }
792 }
793 }
794 };
795 Ok(body()?)
796 }
797
798 fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
873 where
874 I: IntoIterator<Item = Result<T, E>>,
875 T: Rule<U>,
876 U: HandledAccess + PrivateHandledAccess,
877 E: From<RulesetError>,
878 {
879 for rule in rules {
880 self = self.add_rule(rule?)?;
881 }
882 Ok(self)
883 }
884
885 fn no_new_privs(mut self, yes: bool) -> Self {
891 <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = yes;
892 self
893 }
894
895 #[deprecated(note = "Use no_new_privs() instead.")]
897 fn set_no_new_privs(self, yes: bool) -> Self {
898 self.no_new_privs(yes)
899 }
900
901 fn log_same_exec(mut self, set: bool) -> Result<Self, RulesetError> {
914 self.try_set_flag(RestrictSelfFlag::LogSameExec, set)?;
915 Ok(self)
916 }
917
918 fn log_new_exec(mut self, set: bool) -> Result<Self, RulesetError> {
930 self.try_set_flag(RestrictSelfFlag::LogNewExec, set)?;
931 Ok(self)
932 }
933}
934
935#[derive(Debug)]
937pub struct RulesetCreated {
938 fd: Option<OwnedFd>,
939 no_new_privs: bool,
940 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
941 pub(crate) requested_handled_net: BitFlags<AccessNet>,
942 requested_restrict_self_flags: u32,
943 actual_restrict_self_flags: u32,
944 compat: Compatibility,
945}
946
947impl RulesetCreated {
948 pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
949 #[cfg(test)]
951 assert!(!matches!(ruleset.compat.state, CompatState::Init));
952
953 RulesetCreated {
954 fd,
955 no_new_privs: true,
956 requested_handled_fs: ruleset.requested_handled_fs,
957 requested_handled_net: ruleset.requested_handled_net,
958 requested_restrict_self_flags: 0,
959 actual_restrict_self_flags: 0,
960 compat: ruleset.compat,
961 }
962 }
963
964 pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
972 let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
973 let enforced_nnp = if self.no_new_privs {
977 try_set_no_new_privs(&mut self.compat)?
978 } else {
979 false
980 };
981
982 let raw = self.actual_restrict_self_flags;
983 let log_same_exec = RestrictSelfFlag::LogSameExec.is_set(raw);
984 let log_new_exec = RestrictSelfFlag::LogNewExec.is_set(raw);
985 let log_subdomains = RestrictSelfFlag::LogSubdomains.is_set(raw);
986 let all_threads = RestrictSelfFlag::AllThreads.is_set(raw);
987
988 match self.compat.state {
989 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
990 ruleset: self.compat.state.into(),
991 landlock: self.compat.status(),
992 no_new_privs: enforced_nnp,
993 log_same_exec,
994 log_new_exec,
995 log_subdomains,
996 all_threads,
997 }),
998 CompatState::Full | CompatState::Partial => {
999 #[cfg(test)]
1000 assert!(self.fd.is_some());
1001 let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
1003 match unsafe {
1004 uapi::landlock_restrict_self(fd, self.actual_restrict_self_flags)
1005 } {
1006 0 => {
1007 self.compat.update(CompatState::Full);
1008 Ok(RestrictionStatus {
1009 ruleset: self.compat.state.into(),
1010 landlock: self.compat.status(),
1011 no_new_privs: enforced_nnp,
1012 log_same_exec,
1013 log_new_exec,
1014 log_subdomains,
1015 all_threads,
1016 })
1017 }
1018 _ => Err(RestrictSelfError::RestrictSelfCall {
1020 source: Error::last_os_error(),
1021 }),
1022 }
1023 }
1024 }
1025 };
1026 Ok(body()?)
1027 }
1028
1029 pub fn try_clone(&self) -> std::io::Result<Self> {
1034 Ok(RulesetCreated {
1035 fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
1036 no_new_privs: self.no_new_privs,
1037 requested_handled_fs: self.requested_handled_fs,
1038 requested_handled_net: self.requested_handled_net,
1039 requested_restrict_self_flags: self.requested_restrict_self_flags,
1040 actual_restrict_self_flags: self.actual_restrict_self_flags,
1041 compat: self.compat,
1042 })
1043 }
1044}
1045
1046impl From<RulesetCreated> for Option<OwnedFd> {
1047 fn from(ruleset: RulesetCreated) -> Self {
1048 ruleset.fd
1049 }
1050}
1051
1052#[test]
1053fn ruleset_created_ownedfd_none() {
1054 let ruleset = Ruleset::from(ABI::Unsupported)
1055 .handle_access(AccessFs::Execute)
1056 .unwrap()
1057 .create()
1058 .unwrap();
1059 let fd: Option<OwnedFd> = ruleset.into();
1060 assert!(fd.is_none());
1061}
1062
1063impl AsMut<RulesetCreated> for RulesetCreated {
1064 fn as_mut(&mut self) -> &mut RulesetCreated {
1065 self
1066 }
1067}
1068
1069impl RulesetCreatedAttr for RulesetCreated {}
1070
1071impl RulesetCreatedAttr for &mut RulesetCreated {}
1072
1073#[test]
1074fn ruleset_created_attr() {
1075 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
1076 .handle_access(AccessFs::Execute)
1077 .unwrap()
1078 .create()
1079 .unwrap();
1080 let ruleset_created_ref = &mut ruleset_created;
1081
1082 ruleset_created_ref
1084 .set_compatibility(CompatLevel::BestEffort)
1085 .add_rule(PathBeneath::new(
1086 PathFd::new("/usr").unwrap(),
1087 AccessFs::Execute,
1088 ))
1089 .unwrap()
1090 .add_rule(PathBeneath::new(
1091 PathFd::new("/etc").unwrap(),
1092 AccessFs::Execute,
1093 ))
1094 .unwrap();
1095
1096 assert_eq!(
1098 ruleset_created
1099 .set_compatibility(CompatLevel::BestEffort)
1100 .add_rule(PathBeneath::new(
1101 PathFd::new("/tmp").unwrap(),
1102 AccessFs::Execute,
1103 ))
1104 .unwrap()
1105 .add_rule(PathBeneath::new(
1106 PathFd::new("/var").unwrap(),
1107 AccessFs::Execute,
1108 ))
1109 .unwrap()
1110 .restrict_self()
1111 .unwrap(),
1112 RestrictionStatus {
1113 ruleset: RulesetStatus::NotEnforced,
1114 landlock: LandlockStatus::NotImplemented,
1115 no_new_privs: true,
1116 log_same_exec: true,
1117 log_new_exec: false,
1118 log_subdomains: true,
1119 all_threads: false,
1120 }
1121 );
1122}
1123
1124#[test]
1125fn ruleset_compat_dummy() {
1126 for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
1127 println!("level: {:?}", level);
1128
1129 let ruleset = Ruleset::from(ABI::Unsupported);
1131 assert_eq!(ruleset.compat.state, CompatState::Init);
1132
1133 let ruleset = ruleset.set_compatibility(level);
1134 assert_eq!(ruleset.compat.state, CompatState::Init);
1135
1136 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1137 assert_eq!(
1138 ruleset.compat.state,
1139 match level {
1140 CompatLevel::BestEffort => CompatState::No,
1141 CompatLevel::SoftRequirement => CompatState::Dummy,
1142 _ => unreachable!(),
1143 }
1144 );
1145
1146 let ruleset_created = ruleset.create().unwrap();
1147 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1150
1151 let ruleset_created = ruleset_created
1152 .add_rule(PathBeneath::new(
1153 PathFd::new("/usr").unwrap(),
1154 AccessFs::Execute,
1155 ))
1156 .unwrap();
1157 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1158 }
1159}
1160
1161#[test]
1162fn ruleset_compat_partial() {
1163 let ruleset = Ruleset::from(ABI::V1);
1165 assert_eq!(ruleset.compat.state, CompatState::Init);
1166
1167 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1169 assert_eq!(ruleset.compat.state, CompatState::No);
1170
1171 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1172 assert_eq!(ruleset.compat.state, CompatState::Partial);
1173
1174 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1176 assert_eq!(ruleset.compat.state, CompatState::Partial);
1177}
1178
1179#[test]
1180fn ruleset_unsupported() {
1181 assert_eq!(
1182 Ruleset::from(ABI::Unsupported)
1183 .handle_access(AccessFs::Execute)
1185 .unwrap()
1186 .create()
1187 .unwrap()
1188 .restrict_self()
1189 .unwrap(),
1190 RestrictionStatus {
1191 ruleset: RulesetStatus::NotEnforced,
1192 landlock: LandlockStatus::NotImplemented,
1193 no_new_privs: true,
1195 log_same_exec: true,
1196 log_new_exec: false,
1197 log_subdomains: true,
1198 all_threads: false,
1199 }
1200 );
1201
1202 assert_eq!(
1203 Ruleset::from(ABI::Unsupported)
1204 .set_compatibility(CompatLevel::SoftRequirement)
1206 .handle_access(AccessFs::Execute)
1207 .unwrap()
1208 .create()
1209 .unwrap()
1210 .restrict_self()
1211 .unwrap(),
1212 RestrictionStatus {
1213 ruleset: RulesetStatus::NotEnforced,
1214 landlock: LandlockStatus::NotImplemented,
1215 no_new_privs: true,
1217 log_same_exec: true,
1218 log_new_exec: false,
1219 log_subdomains: true,
1220 all_threads: false,
1221 }
1222 );
1223
1224 assert!(matches!(
1226 Ruleset::from(ABI::Unsupported)
1227 .set_compatibility(CompatLevel::HardRequirement)
1229 .handle_access(AccessFs::Execute)
1230 .unwrap_err(),
1231 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1232 CompatError::Access(AccessError::Incompatible { .. })
1233 )))
1234 ));
1235
1236 assert!(matches!(
1238 Ruleset::from(ABI::Unsupported)
1239 .set_compatibility(CompatLevel::HardRequirement)
1241 .scope(Scope::Signal)
1242 .unwrap_err(),
1243 RulesetError::Scope(ScopeError::Compat(CompatError::Access(
1244 AccessError::Incompatible { .. }
1245 )))
1246 ));
1247
1248 assert_eq!(
1249 Ruleset::from(ABI::Unsupported)
1250 .handle_access(AccessFs::Execute)
1251 .unwrap()
1252 .create()
1253 .unwrap()
1254 .set_compatibility(CompatLevel::SoftRequirement)
1256 .restrict_self()
1257 .unwrap(),
1258 RestrictionStatus {
1259 ruleset: RulesetStatus::NotEnforced,
1260 landlock: LandlockStatus::NotImplemented,
1261 no_new_privs: true,
1263 log_same_exec: true,
1264 log_new_exec: false,
1265 log_subdomains: true,
1266 all_threads: false,
1267 }
1268 );
1269
1270 if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
1272 assert_eq!(
1273 Ruleset::from(ABI::V1)
1274 .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
1275 .unwrap()
1276 .create()
1277 .unwrap()
1278 .set_compatibility(CompatLevel::SoftRequirement)
1280 .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
1281 .unwrap()
1282 .restrict_self()
1283 .unwrap(),
1284 RestrictionStatus {
1285 ruleset: RulesetStatus::NotEnforced,
1286 landlock: LandlockStatus::Available {
1287 effective_abi: ABI::V1,
1288 kernel_abi: None,
1289 },
1290 no_new_privs: true,
1293 log_same_exec: true,
1294 log_new_exec: false,
1295 log_subdomains: true,
1296 all_threads: false,
1297 }
1298 );
1299 }
1300
1301 assert_eq!(
1302 Ruleset::from(ABI::Unsupported)
1303 .handle_access(AccessFs::Execute)
1304 .unwrap()
1305 .create()
1306 .unwrap()
1307 .no_new_privs(false)
1308 .restrict_self()
1309 .unwrap(),
1310 RestrictionStatus {
1311 ruleset: RulesetStatus::NotEnforced,
1312 landlock: LandlockStatus::NotImplemented,
1313 no_new_privs: false,
1314 log_same_exec: true,
1315 log_new_exec: false,
1316 log_subdomains: true,
1317 all_threads: false,
1318 }
1319 );
1320
1321 assert!(matches!(
1323 Ruleset::from(ABI::Unsupported)
1324 .handle_access(AccessFs::from_all(ABI::Unsupported))
1326 .unwrap_err(),
1327 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1328 CompatError::Access(AccessError::Empty)
1329 )))
1330 ));
1331
1332 assert!(matches!(
1333 Ruleset::from(ABI::Unsupported)
1334 .create()
1336 .unwrap_err(),
1337 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1338 ));
1339
1340 assert!(matches!(
1342 Ruleset::from(ABI::V1)
1343 .handle_access(AccessFs::from_all(ABI::Unsupported))
1345 .unwrap_err(),
1346 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1347 CompatError::Access(AccessError::Empty)
1348 )))
1349 ));
1350
1351 assert!(matches!(
1353 Ruleset::from(ABI::Unsupported)
1354 .scope(Scope::from_all(ABI::Unsupported))
1355 .unwrap_err(),
1356 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1357 ));
1358
1359 assert!(matches!(
1361 Ruleset::from(ABI::V1)
1362 .scope(Scope::from_all(ABI::Unsupported))
1363 .unwrap_err(),
1364 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1365 ));
1366
1367 let ruleset = Ruleset::from(ABI::V1)
1369 .handle_access(AccessFs::Execute)
1370 .unwrap()
1371 .set_compatibility(CompatLevel::SoftRequirement)
1372 .scope(Scope::Signal)
1373 .unwrap();
1374 assert_eq!(ruleset.requested_scoped, BitFlags::from(Scope::Signal));
1375 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
1376
1377 assert_eq!(
1379 Ruleset::from(ABI::Unsupported)
1380 .handle_access(AccessFs::Execute)
1381 .unwrap()
1382 .create()
1383 .unwrap()
1384 .log_same_exec(false)
1385 .unwrap()
1386 .restrict_self()
1387 .unwrap(),
1388 RestrictionStatus {
1389 ruleset: RulesetStatus::NotEnforced,
1390 landlock: LandlockStatus::NotImplemented,
1391 no_new_privs: true,
1392 log_same_exec: true,
1393 log_new_exec: false,
1394 log_subdomains: true,
1395 all_threads: false,
1396 }
1397 );
1398
1399 assert!(matches!(
1401 Ruleset::from(ABI::Unsupported)
1402 .handle_access(AccessFs::Execute)
1403 .unwrap()
1404 .create()
1405 .unwrap()
1406 .set_compatibility(CompatLevel::HardRequirement)
1407 .log_new_exec(true)
1408 .unwrap_err(),
1409 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
1410 flag: RestrictSelfFlag::LogNewExec,
1411 set: true,
1412 })
1413 ));
1414
1415 for handled_access in &[
1417 make_bitflags!(AccessFs::{Execute | WriteFile}),
1418 AccessFs::Execute.into(),
1419 ] {
1420 let ruleset = Ruleset::from(ABI::V1)
1421 .handle_access(*handled_access)
1422 .unwrap();
1423 let ruleset_created = RulesetCreated::new(ruleset, None);
1426 assert!(matches!(
1427 ruleset_created
1428 .add_rule(PathBeneath::new(
1429 PathFd::new("/").unwrap(),
1430 AccessFs::ReadFile
1431 ))
1432 .unwrap_err(),
1433 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
1434 ));
1435 }
1436}
1437
1438#[test]
1439fn ignore_abi_v2_with_abi_v1() {
1440 assert_eq!(
1443 Ruleset::from(ABI::V1)
1444 .set_compatibility(CompatLevel::HardRequirement)
1445 .handle_access(AccessFs::from_all(ABI::V1))
1446 .unwrap()
1447 .set_compatibility(CompatLevel::SoftRequirement)
1448 .handle_access(AccessFs::Refer)
1450 .unwrap()
1451 .create()
1452 .unwrap()
1453 .add_rule(PathBeneath::new(
1454 PathFd::new("/tmp").unwrap(),
1455 AccessFs::from_all(ABI::V2)
1456 ))
1457 .unwrap()
1458 .add_rule(PathBeneath::new(
1459 PathFd::new("/usr").unwrap(),
1460 make_bitflags!(AccessFs::{ReadFile | ReadDir})
1461 ))
1462 .unwrap()
1463 .restrict_self()
1464 .unwrap(),
1465 RestrictionStatus {
1466 ruleset: RulesetStatus::NotEnforced,
1467 landlock: LandlockStatus::Available {
1468 effective_abi: ABI::V1,
1469 kernel_abi: None,
1470 },
1471 no_new_privs: true,
1472 log_same_exec: true,
1473 log_new_exec: false,
1474 log_subdomains: true,
1475 all_threads: false,
1476 }
1477 );
1478}
1479
1480#[test]
1481fn unsupported_handled_access() {
1482 assert!(matches!(
1483 Ruleset::from(ABI::V3)
1484 .handle_access(AccessNet::from_all(ABI::V3))
1485 .unwrap_err(),
1486 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
1487 CompatError::Access(AccessError::Empty)
1488 )))
1489 ));
1490}
1491
1492#[test]
1493fn unsupported_handled_access_errno() {
1494 assert_eq!(
1495 Errno::from(
1496 Ruleset::from(ABI::V3)
1497 .handle_access(AccessNet::from_all(ABI::V3))
1498 .unwrap_err()
1499 ),
1500 Errno::new(libc::EINVAL)
1501 );
1502}