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