1use crate::compat::private::OptionCompatLevelMut;
4use crate::{
5 uapi, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel, CompatState,
6 Compatibility, Compatible, CreateRulesetError, HandledAccess, LandlockStatus,
7 PrivateHandledAccess, RestrictSelfError, RulesetError, Scope, ScopeError, TryCompat,
8};
9use std::io::Error;
10use std::mem::size_of_val;
11use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
12
13#[cfg(test)]
14use crate::*;
15
16pub trait Rule<T>: PrivateRule<T>
18where
19 T: HandledAccess,
20{
21}
22
23pub trait PrivateRule<T>
25where
26 Self: TryCompat<T> + Compatible,
27 T: HandledAccess,
28{
29 const TYPE_ID: uapi::landlock_rule_type;
30
31 fn as_ptr(&mut self) -> *const libc::c_void;
36
37 fn check_consistency(&self, ruleset: &RulesetCreated) -> Result<(), AddRulesError>;
38}
39
40#[derive(Debug, PartialEq, Eq)]
42pub enum RulesetStatus {
43 FullyEnforced,
45 PartiallyEnforced,
48 NotEnforced,
51}
52
53impl From<CompatState> for RulesetStatus {
54 fn from(state: CompatState) -> Self {
55 match state {
56 CompatState::Init | CompatState::No | CompatState::Dummy => RulesetStatus::NotEnforced,
57 CompatState::Full => RulesetStatus::FullyEnforced,
58 CompatState::Partial => RulesetStatus::PartiallyEnforced,
59 }
60 }
61}
62
63#[derive(Debug, PartialEq, Eq)]
68#[non_exhaustive]
69pub struct RestrictionStatus {
70 pub ruleset: RulesetStatus,
72 pub no_new_privs: bool,
74 pub landlock: LandlockStatus,
76}
77
78fn prctl_set_no_new_privs() -> Result<(), Error> {
79 match unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } {
80 0 => Ok(()),
81 _ => Err(Error::last_os_error()),
82 }
83}
84
85fn support_no_new_privs() -> bool {
86 matches!(
88 unsafe { libc::prctl(libc::PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) },
89 0 | 1
90 )
91}
92
93#[derive(Debug)]
176pub struct Ruleset {
177 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
178 pub(crate) requested_handled_net: BitFlags<AccessNet>,
179 pub(crate) requested_scoped: BitFlags<Scope>,
180 pub(crate) actual_handled_fs: BitFlags<AccessFs>,
181 pub(crate) actual_handled_net: BitFlags<AccessNet>,
182 pub(crate) actual_scoped: BitFlags<Scope>,
183 pub(crate) compat: Compatibility,
184}
185
186impl From<Compatibility> for Ruleset {
187 fn from(compat: Compatibility) -> Self {
188 Ruleset {
189 requested_handled_fs: Default::default(),
191 requested_handled_net: Default::default(),
192 requested_scoped: Default::default(),
193 actual_handled_fs: Default::default(),
194 actual_handled_net: Default::default(),
195 actual_scoped: Default::default(),
196 compat,
197 }
198 }
199}
200
201#[cfg(test)]
202impl From<ABI> for Ruleset {
203 fn from(abi: ABI) -> Self {
204 Ruleset::from(Compatibility::from(abi))
205 }
206}
207
208#[test]
209fn ruleset_add_rule_iter() {
210 assert!(matches!(
211 Ruleset::from(ABI::Unsupported)
212 .handle_access(AccessFs::Execute)
213 .unwrap()
214 .create()
215 .unwrap()
216 .add_rule(PathBeneath::new(
217 PathFd::new("/").unwrap(),
218 AccessFs::ReadFile
219 ))
220 .unwrap_err(),
221 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
222 ));
223}
224
225impl Default for Ruleset {
226 fn default() -> Self {
233 Compatibility::new().into()
238 }
239}
240
241impl Ruleset {
242 #[allow(clippy::new_without_default)]
243 #[deprecated(note = "Use Ruleset::default() instead")]
244 pub fn new() -> Self {
245 Ruleset::default()
246 }
247
248 pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
253 let body = || -> Result<RulesetCreated, CreateRulesetError> {
254 match self.compat.state {
255 CompatState::Init => {
256 Err(CreateRulesetError::MissingHandledAccess)
259 }
260 CompatState::No | CompatState::Dummy => {
261 #[cfg(test)]
263 assert!(
264 !self.requested_handled_fs.is_empty()
265 || !self.requested_handled_net.is_empty()
266 || !self.requested_scoped.is_empty()
267 );
268
269 self.compat.update(CompatState::Dummy);
272 match self.compat.level.into() {
273 CompatLevel::HardRequirement => {
274 Err(CreateRulesetError::MissingHandledAccess)
275 }
276 _ => Ok(RulesetCreated::new(self, None)),
277 }
278 }
279 CompatState::Full | CompatState::Partial => {
280 #[cfg(test)]
282 assert!(
283 !self.actual_handled_fs.is_empty()
284 || !self.actual_handled_net.is_empty()
285 || !self.actual_scoped.is_empty()
286 );
287
288 let attr = uapi::landlock_ruleset_attr {
289 handled_access_fs: self.actual_handled_fs.bits(),
290 handled_access_net: self.actual_handled_net.bits(),
291 scoped: self.actual_scoped.bits(),
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
566impl OptionCompatLevelMut for RulesetCreated {
567 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
568 &mut self.compat.level
569 }
570}
571
572impl OptionCompatLevelMut for &mut RulesetCreated {
573 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
574 &mut self.compat.level
575 }
576}
577
578impl Compatible for RulesetCreated {}
579
580impl Compatible for &mut RulesetCreated {}
581
582pub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {
583 fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
587 where
588 T: Rule<U>,
589 U: HandledAccess + PrivateHandledAccess,
590 {
591 let body = || -> Result<Self, AddRulesError> {
592 let self_ref = self.as_mut();
593 rule.check_consistency(self_ref)?;
594 let mut compat_rule = match rule
595 .try_compat(
596 self_ref.compat.abi(),
597 self_ref.compat.level,
598 &mut self_ref.compat.state,
599 )
600 .map_err(AddRuleError::Compat)?
601 {
602 Some(r) => r,
603 None => return Ok(self),
604 };
605 match self_ref.compat.state {
606 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
607 CompatState::Full | CompatState::Partial => {
608 #[cfg(test)]
609 assert!(self_ref.fd.is_some());
610 let fd = self_ref.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
611 match unsafe {
612 uapi::landlock_add_rule(fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
613 } {
614 0 => Ok(self),
615 _ => Err(AddRuleError::<U>::AddRuleCall {
616 source: Error::last_os_error(),
617 }
618 .into()),
619 }
620 }
621 }
622 };
623 Ok(body()?)
624 }
625
626 fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
701 where
702 I: IntoIterator<Item = Result<T, E>>,
703 T: Rule<U>,
704 U: HandledAccess + PrivateHandledAccess,
705 E: From<RulesetError>,
706 {
707 for rule in rules {
708 self = self.add_rule(rule?)?;
709 }
710 Ok(self)
711 }
712
713 fn set_no_new_privs(mut self, no_new_privs: bool) -> Self {
719 <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = no_new_privs;
720 self
721 }
722}
723
724#[derive(Debug)]
726pub struct RulesetCreated {
727 fd: Option<OwnedFd>,
728 no_new_privs: bool,
729 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
730 pub(crate) requested_handled_net: BitFlags<AccessNet>,
731 compat: Compatibility,
732}
733
734impl RulesetCreated {
735 pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
736 #[cfg(test)]
738 assert!(!matches!(ruleset.compat.state, CompatState::Init));
739
740 RulesetCreated {
741 fd,
742 no_new_privs: true,
743 requested_handled_fs: ruleset.requested_handled_fs,
744 requested_handled_net: ruleset.requested_handled_net,
745 compat: ruleset.compat,
746 }
747 }
748
749 pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
757 let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
758 let enforced_nnp = if self.no_new_privs {
762 if let Err(e) = prctl_set_no_new_privs() {
763 match self.compat.level.into() {
764 CompatLevel::BestEffort => {}
765 CompatLevel::SoftRequirement => {
766 self.compat.update(CompatState::Dummy);
767 }
768 CompatLevel::HardRequirement => {
769 return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
770 }
771 }
772 let support_nnp = support_no_new_privs();
775 match self.compat.state {
776 CompatState::Init | CompatState::No | CompatState::Dummy => {
779 if support_nnp {
780 return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
783 }
784 }
785 CompatState::Full | CompatState::Partial => {
788 return Err(RestrictSelfError::SetNoNewPrivsCall { source: e })
789 }
790 }
791 false
792 } else {
793 true
794 }
795 } else {
796 false
797 };
798
799 match self.compat.state {
800 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
801 ruleset: self.compat.state.into(),
802 landlock: self.compat.status(),
803 no_new_privs: enforced_nnp,
804 }),
805 CompatState::Full | CompatState::Partial => {
806 #[cfg(test)]
807 assert!(self.fd.is_some());
808 let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
810 match unsafe { uapi::landlock_restrict_self(fd, 0) } {
811 0 => {
812 self.compat.update(CompatState::Full);
813 Ok(RestrictionStatus {
814 ruleset: self.compat.state.into(),
815 landlock: self.compat.status(),
816 no_new_privs: enforced_nnp,
817 })
818 }
819 _ => Err(RestrictSelfError::RestrictSelfCall {
821 source: Error::last_os_error(),
822 }),
823 }
824 }
825 }
826 };
827 Ok(body()?)
828 }
829
830 pub fn try_clone(&self) -> std::io::Result<Self> {
835 Ok(RulesetCreated {
836 fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
837 no_new_privs: self.no_new_privs,
838 requested_handled_fs: self.requested_handled_fs,
839 requested_handled_net: self.requested_handled_net,
840 compat: self.compat,
841 })
842 }
843}
844
845impl From<RulesetCreated> for Option<OwnedFd> {
846 fn from(ruleset: RulesetCreated) -> Self {
847 ruleset.fd
848 }
849}
850
851#[test]
852fn ruleset_created_ownedfd_none() {
853 let ruleset = Ruleset::from(ABI::Unsupported)
854 .handle_access(AccessFs::Execute)
855 .unwrap()
856 .create()
857 .unwrap();
858 let fd: Option<OwnedFd> = ruleset.into();
859 assert!(fd.is_none());
860}
861
862impl AsMut<RulesetCreated> for RulesetCreated {
863 fn as_mut(&mut self) -> &mut RulesetCreated {
864 self
865 }
866}
867
868impl RulesetCreatedAttr for RulesetCreated {}
869
870impl RulesetCreatedAttr for &mut RulesetCreated {}
871
872#[test]
873fn ruleset_created_attr() {
874 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
875 .handle_access(AccessFs::Execute)
876 .unwrap()
877 .create()
878 .unwrap();
879 let ruleset_created_ref = &mut ruleset_created;
880
881 ruleset_created_ref
883 .set_compatibility(CompatLevel::BestEffort)
884 .add_rule(PathBeneath::new(
885 PathFd::new("/usr").unwrap(),
886 AccessFs::Execute,
887 ))
888 .unwrap()
889 .add_rule(PathBeneath::new(
890 PathFd::new("/etc").unwrap(),
891 AccessFs::Execute,
892 ))
893 .unwrap();
894
895 assert_eq!(
897 ruleset_created
898 .set_compatibility(CompatLevel::BestEffort)
899 .add_rule(PathBeneath::new(
900 PathFd::new("/tmp").unwrap(),
901 AccessFs::Execute,
902 ))
903 .unwrap()
904 .add_rule(PathBeneath::new(
905 PathFd::new("/var").unwrap(),
906 AccessFs::Execute,
907 ))
908 .unwrap()
909 .restrict_self()
910 .unwrap(),
911 RestrictionStatus {
912 ruleset: RulesetStatus::NotEnforced,
913 landlock: LandlockStatus::NotImplemented,
914 no_new_privs: true,
915 }
916 );
917}
918
919#[test]
920fn ruleset_compat_dummy() {
921 for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
922 println!("level: {:?}", level);
923
924 let ruleset = Ruleset::from(ABI::Unsupported);
926 assert_eq!(ruleset.compat.state, CompatState::Init);
927
928 let ruleset = ruleset.set_compatibility(level);
929 assert_eq!(ruleset.compat.state, CompatState::Init);
930
931 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
932 assert_eq!(
933 ruleset.compat.state,
934 match level {
935 CompatLevel::BestEffort => CompatState::No,
936 CompatLevel::SoftRequirement => CompatState::Dummy,
937 _ => unreachable!(),
938 }
939 );
940
941 let ruleset_created = ruleset.create().unwrap();
942 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
945
946 let ruleset_created = ruleset_created
947 .add_rule(PathBeneath::new(
948 PathFd::new("/usr").unwrap(),
949 AccessFs::Execute,
950 ))
951 .unwrap();
952 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
953 }
954}
955
956#[test]
957fn ruleset_compat_partial() {
958 let ruleset = Ruleset::from(ABI::V1);
960 assert_eq!(ruleset.compat.state, CompatState::Init);
961
962 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
964 assert_eq!(ruleset.compat.state, CompatState::No);
965
966 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
967 assert_eq!(ruleset.compat.state, CompatState::Partial);
968
969 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
971 assert_eq!(ruleset.compat.state, CompatState::Partial);
972}
973
974#[test]
975fn ruleset_unsupported() {
976 assert_eq!(
977 Ruleset::from(ABI::Unsupported)
978 .handle_access(AccessFs::Execute)
980 .unwrap()
981 .create()
982 .unwrap()
983 .restrict_self()
984 .unwrap(),
985 RestrictionStatus {
986 ruleset: RulesetStatus::NotEnforced,
987 landlock: LandlockStatus::NotImplemented,
988 no_new_privs: true,
990 }
991 );
992
993 assert_eq!(
994 Ruleset::from(ABI::Unsupported)
995 .set_compatibility(CompatLevel::SoftRequirement)
997 .handle_access(AccessFs::Execute)
998 .unwrap()
999 .create()
1000 .unwrap()
1001 .restrict_self()
1002 .unwrap(),
1003 RestrictionStatus {
1004 ruleset: RulesetStatus::NotEnforced,
1005 landlock: LandlockStatus::NotImplemented,
1006 no_new_privs: true,
1008 }
1009 );
1010
1011 matches!(
1013 Ruleset::from(ABI::Unsupported)
1014 .set_compatibility(CompatLevel::HardRequirement)
1016 .handle_access(AccessFs::Execute)
1017 .unwrap_err(),
1018 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1019 );
1020
1021 matches!(
1023 Ruleset::from(ABI::Unsupported)
1024 .set_compatibility(CompatLevel::HardRequirement)
1026 .scope(Scope::Signal)
1027 .unwrap_err(),
1028 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1029 );
1030
1031 assert_eq!(
1032 Ruleset::from(ABI::Unsupported)
1033 .handle_access(AccessFs::Execute)
1034 .unwrap()
1035 .create()
1036 .unwrap()
1037 .set_compatibility(CompatLevel::SoftRequirement)
1039 .restrict_self()
1040 .unwrap(),
1041 RestrictionStatus {
1042 ruleset: RulesetStatus::NotEnforced,
1043 landlock: LandlockStatus::NotImplemented,
1044 no_new_privs: true,
1046 }
1047 );
1048
1049 if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
1051 assert_eq!(
1052 Ruleset::from(ABI::V1)
1053 .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
1054 .unwrap()
1055 .create()
1056 .unwrap()
1057 .set_compatibility(CompatLevel::SoftRequirement)
1059 .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
1060 .unwrap()
1061 .restrict_self()
1062 .unwrap(),
1063 RestrictionStatus {
1064 ruleset: RulesetStatus::NotEnforced,
1065 landlock: LandlockStatus::Available(ABI::V1, None),
1066 no_new_privs: true,
1069 }
1070 );
1071 }
1072
1073 assert_eq!(
1074 Ruleset::from(ABI::Unsupported)
1075 .handle_access(AccessFs::Execute)
1076 .unwrap()
1077 .create()
1078 .unwrap()
1079 .set_no_new_privs(false)
1080 .restrict_self()
1081 .unwrap(),
1082 RestrictionStatus {
1083 ruleset: RulesetStatus::NotEnforced,
1084 landlock: LandlockStatus::NotImplemented,
1085 no_new_privs: false,
1086 }
1087 );
1088
1089 assert!(matches!(
1091 Ruleset::from(ABI::Unsupported)
1092 .handle_access(AccessFs::from_all(ABI::Unsupported))
1094 .unwrap_err(),
1095 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1096 CompatError::Access(AccessError::Empty)
1097 )))
1098 ));
1099
1100 assert!(matches!(
1101 Ruleset::from(ABI::Unsupported)
1102 .create()
1104 .unwrap_err(),
1105 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1106 ));
1107
1108 assert!(matches!(
1110 Ruleset::from(ABI::V1)
1111 .handle_access(AccessFs::from_all(ABI::Unsupported))
1113 .unwrap_err(),
1114 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1115 CompatError::Access(AccessError::Empty)
1116 )))
1117 ));
1118
1119 assert!(matches!(
1121 Ruleset::from(ABI::Unsupported)
1122 .scope(Scope::from_all(ABI::Unsupported))
1123 .unwrap_err(),
1124 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1125 ));
1126
1127 assert!(matches!(
1129 Ruleset::from(ABI::V1)
1130 .scope(Scope::from_all(ABI::Unsupported))
1131 .unwrap_err(),
1132 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1133 ));
1134
1135 for handled_access in &[
1137 make_bitflags!(AccessFs::{Execute | WriteFile}),
1138 AccessFs::Execute.into(),
1139 ] {
1140 let ruleset = Ruleset::from(ABI::V1)
1141 .handle_access(*handled_access)
1142 .unwrap();
1143 let ruleset_created = RulesetCreated::new(ruleset, None);
1146 assert!(matches!(
1147 ruleset_created
1148 .add_rule(PathBeneath::new(
1149 PathFd::new("/").unwrap(),
1150 AccessFs::ReadFile
1151 ))
1152 .unwrap_err(),
1153 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
1154 ));
1155 }
1156}
1157
1158#[test]
1159fn ignore_abi_v2_with_abi_v1() {
1160 assert_eq!(
1163 Ruleset::from(ABI::V1)
1164 .set_compatibility(CompatLevel::HardRequirement)
1165 .handle_access(AccessFs::from_all(ABI::V1))
1166 .unwrap()
1167 .set_compatibility(CompatLevel::SoftRequirement)
1168 .handle_access(AccessFs::Refer)
1170 .unwrap()
1171 .create()
1172 .unwrap()
1173 .add_rule(PathBeneath::new(
1174 PathFd::new("/tmp").unwrap(),
1175 AccessFs::from_all(ABI::V2)
1176 ))
1177 .unwrap()
1178 .add_rule(PathBeneath::new(
1179 PathFd::new("/usr").unwrap(),
1180 make_bitflags!(AccessFs::{ReadFile | ReadDir})
1181 ))
1182 .unwrap()
1183 .restrict_self()
1184 .unwrap(),
1185 RestrictionStatus {
1186 ruleset: RulesetStatus::NotEnforced,
1187 landlock: LandlockStatus::Available(ABI::V1, None),
1188 no_new_privs: true,
1189 }
1190 );
1191}
1192
1193#[test]
1194fn unsupported_handled_access() {
1195 matches!(
1196 Ruleset::from(ABI::V3)
1197 .handle_access(AccessNet::from_all(ABI::V3))
1198 .unwrap_err(),
1199 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
1200 CompatError::Access(AccessError::Empty)
1201 )))
1202 );
1203}
1204
1205#[test]
1206fn unsupported_handled_access_errno() {
1207 assert_eq!(
1208 Errno::from(
1209 Ruleset::from(ABI::V3)
1210 .handle_access(AccessNet::from_all(ABI::V3))
1211 .unwrap_err()
1212 ),
1213 Errno::new(libc::EINVAL)
1214 );
1215}