1use crate::compat::private::OptionCompatLevelMut;
4use crate::{
5 uapi, AccessFs, AccessNet, AddRuleError, AddRulesError, BitFlags, CompatLevel, CompatState,
6 Compatibility, Compatible, CreateRulesetError, HandledAccess, PrivateHandledAccess,
7 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}
75
76fn prctl_set_no_new_privs() -> Result<(), Error> {
77 match unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } {
78 0 => Ok(()),
79 _ => Err(Error::last_os_error()),
80 }
81}
82
83fn support_no_new_privs() -> bool {
84 matches!(
86 unsafe { libc::prctl(libc::PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) },
87 0 | 1
88 )
89}
90
91#[cfg_attr(test, derive(Debug))]
174pub struct Ruleset {
175 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
176 pub(crate) requested_handled_net: BitFlags<AccessNet>,
177 pub(crate) requested_scoped: BitFlags<Scope>,
178 pub(crate) actual_handled_fs: BitFlags<AccessFs>,
179 pub(crate) actual_handled_net: BitFlags<AccessNet>,
180 pub(crate) actual_scoped: BitFlags<Scope>,
181 pub(crate) compat: Compatibility,
182}
183
184impl From<Compatibility> for Ruleset {
185 fn from(compat: Compatibility) -> Self {
186 Ruleset {
187 requested_handled_fs: Default::default(),
189 requested_handled_net: Default::default(),
190 requested_scoped: Default::default(),
191 actual_handled_fs: Default::default(),
192 actual_handled_net: Default::default(),
193 actual_scoped: Default::default(),
194 compat,
195 }
196 }
197}
198
199#[cfg(test)]
200impl From<ABI> for Ruleset {
201 fn from(abi: ABI) -> Self {
202 Ruleset::from(Compatibility::from(abi))
203 }
204}
205
206#[test]
207fn ruleset_add_rule_iter() {
208 assert!(matches!(
209 Ruleset::from(ABI::Unsupported)
210 .handle_access(AccessFs::Execute)
211 .unwrap()
212 .create()
213 .unwrap()
214 .add_rule(PathBeneath::new(
215 PathFd::new("/").unwrap(),
216 AccessFs::ReadFile
217 ))
218 .unwrap_err(),
219 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
220 ));
221}
222
223impl Default for Ruleset {
224 fn default() -> Self {
231 Compatibility::new().into()
236 }
237}
238
239impl Ruleset {
240 #[allow(clippy::new_without_default)]
241 #[deprecated(note = "Use Ruleset::default() instead")]
242 pub fn new() -> Self {
243 Ruleset::default()
244 }
245
246 pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
251 let body = || -> Result<RulesetCreated, CreateRulesetError> {
252 match self.compat.state {
253 CompatState::Init => {
254 Err(CreateRulesetError::MissingHandledAccess)
257 }
258 CompatState::No | CompatState::Dummy => {
259 #[cfg(test)]
261 assert!(
262 !self.requested_handled_fs.is_empty()
263 || !self.requested_handled_net.is_empty()
264 || !self.requested_scoped.is_empty()
265 );
266
267 self.compat.update(CompatState::Dummy);
270 match self.compat.level.into() {
271 CompatLevel::HardRequirement => {
272 Err(CreateRulesetError::MissingHandledAccess)
273 }
274 _ => Ok(RulesetCreated::new(self, None)),
275 }
276 }
277 CompatState::Full | CompatState::Partial => {
278 #[cfg(test)]
280 assert!(
281 !self.actual_handled_fs.is_empty()
282 || !self.actual_handled_net.is_empty()
283 || !self.actual_scoped.is_empty()
284 );
285
286 let attr = uapi::landlock_ruleset_attr {
287 handled_access_fs: self.actual_handled_fs.bits(),
288 handled_access_net: self.actual_handled_net.bits(),
289 scoped: self.actual_scoped.bits(),
290 };
291 match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {
292 fd if fd >= 0 => Ok(RulesetCreated::new(
293 self,
294 Some(unsafe { OwnedFd::from_raw_fd(fd) }),
295 )),
296 _ => Err(CreateRulesetError::CreateRulesetCall {
297 source: Error::last_os_error(),
298 }),
299 }
300 }
301 }
302 };
303 Ok(body()?)
304 }
305}
306
307impl OptionCompatLevelMut for Ruleset {
308 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
309 &mut self.compat.level
310 }
311}
312
313impl OptionCompatLevelMut for &mut Ruleset {
314 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
315 &mut self.compat.level
316 }
317}
318
319impl Compatible for Ruleset {}
320
321impl Compatible for &mut Ruleset {}
322
323impl AsMut<Ruleset> for Ruleset {
324 fn as_mut(&mut self) -> &mut Ruleset {
325 self
326 }
327}
328
329#[test]
331fn ruleset_as_mut() {
332 let mut ruleset = Ruleset::from(ABI::Unsupported);
333 let _ = ruleset.as_mut();
334
335 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
336 .handle_access(AccessFs::Execute)
337 .unwrap()
338 .create()
339 .unwrap();
340 let _ = ruleset_created.as_mut();
341}
342
343pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
344 fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
352 where
353 T: Into<BitFlags<U>>,
354 U: HandledAccess + PrivateHandledAccess,
355 {
356 U::ruleset_handle_access(self.as_mut(), access.into())?;
357 Ok(self)
358 }
359
360 fn scope<T>(mut self, scope: T) -> Result<Self, RulesetError>
367 where
368 T: Into<BitFlags<Scope>>,
369 {
370 let scope = scope.into();
371 let ruleset = self.as_mut();
372 ruleset.requested_scoped |= scope;
373 if let Some(a) = scope
374 .try_compat(
375 ruleset.compat.abi(),
376 ruleset.compat.level,
377 &mut ruleset.compat.state,
378 )
379 .map_err(ScopeError::Compat)?
380 {
381 ruleset.actual_scoped |= a;
382 }
383 Ok(self)
384 }
385}
386
387impl RulesetAttr for Ruleset {}
388
389impl RulesetAttr for &mut Ruleset {}
390
391#[test]
392fn ruleset_attr() {
393 let mut ruleset = Ruleset::from(ABI::Unsupported);
394 let ruleset_ref = &mut ruleset;
395
396 ruleset_ref
398 .set_compatibility(CompatLevel::BestEffort)
399 .handle_access(AccessFs::Execute)
400 .unwrap()
401 .handle_access(AccessFs::ReadFile)
402 .unwrap();
403
404 ruleset
406 .set_compatibility(CompatLevel::BestEffort)
407 .handle_access(AccessFs::Execute)
408 .unwrap()
409 .handle_access(AccessFs::WriteFile)
410 .unwrap()
411 .create()
412 .unwrap();
413}
414
415#[test]
416fn ruleset_created_handle_access_fs() {
417 let access = make_bitflags!(AccessFs::{Execute | ReadDir});
418
419 let ruleset = Ruleset::from(ABI::V1).handle_access(access).unwrap();
421 assert_eq!(ruleset.requested_handled_fs, access);
422 assert_eq!(ruleset.actual_handled_fs, access);
423
424 let ruleset = Ruleset::from(ABI::V1)
426 .handle_access(AccessFs::Execute)
427 .unwrap()
428 .handle_access(AccessFs::ReadDir)
429 .unwrap()
430 .handle_access(AccessFs::Execute)
431 .unwrap();
432 assert_eq!(ruleset.requested_handled_fs, access);
433 assert_eq!(ruleset.actual_handled_fs, access);
434
435 assert!(matches!(Ruleset::from(ABI::Unsupported)
438 .handle_access(AccessFs::Execute)
439 .unwrap()
440 .set_compatibility(CompatLevel::HardRequirement)
441 .handle_access(AccessFs::ReadDir)
442 .unwrap_err(),
443 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
444 CompatError::Access(AccessError::Incompatible { access })
445 ))) if access == AccessFs::ReadDir
446 ));
447}
448
449#[test]
450fn ruleset_created_handle_access_net_tcp() {
451 let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
452
453 let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();
455 assert_eq!(ruleset.requested_handled_net, access);
456 assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);
457
458 let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
460 assert_eq!(ruleset.requested_handled_net, access);
461 assert_eq!(ruleset.actual_handled_net, access);
462
463 let ruleset = Ruleset::from(ABI::V4)
465 .handle_access(AccessNet::BindTcp)
466 .unwrap()
467 .handle_access(AccessNet::ConnectTcp)
468 .unwrap()
469 .handle_access(AccessNet::BindTcp)
470 .unwrap();
471 assert_eq!(ruleset.requested_handled_net, access);
472 assert_eq!(ruleset.actual_handled_net, access);
473
474 assert!(matches!(Ruleset::from(ABI::Unsupported)
477 .handle_access(AccessNet::BindTcp)
478 .unwrap()
479 .set_compatibility(CompatLevel::HardRequirement)
480 .handle_access(AccessNet::ConnectTcp)
481 .unwrap_err(),
482 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
483 CompatError::Access(AccessError::Incompatible { access })
484 ))) if access == AccessNet::ConnectTcp
485 ));
486}
487
488#[test]
489fn ruleset_created_scope() {
490 let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
491
492 let ruleset = Ruleset::from(ABI::V5).scope(scopes).unwrap();
494 assert_eq!(ruleset.requested_scoped, scopes);
495 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
496
497 let ruleset = Ruleset::from(ABI::V6).scope(scopes).unwrap();
499 assert_eq!(ruleset.requested_scoped, scopes);
500 assert_eq!(ruleset.actual_scoped, scopes);
501
502 let ruleset = Ruleset::from(ABI::V6)
504 .scope(Scope::AbstractUnixSocket)
505 .unwrap()
506 .scope(Scope::Signal)
507 .unwrap()
508 .scope(Scope::AbstractUnixSocket)
509 .unwrap();
510 assert_eq!(ruleset.requested_scoped, scopes);
511 assert_eq!(ruleset.actual_scoped, scopes);
512
513 assert!(matches!(Ruleset::from(ABI::Unsupported)
516 .scope(Scope::AbstractUnixSocket)
517 .unwrap()
518 .set_compatibility(CompatLevel::HardRequirement)
519 .scope(Scope::Signal)
520 .unwrap_err(),
521 RulesetError::Scope(ScopeError::Compat(
522 CompatError::Access(AccessError::Incompatible { access })
523 )) if access == Scope::Signal
524 ));
525}
526
527#[test]
528fn ruleset_created_fs_net_scope() {
529 let access_fs = make_bitflags!(AccessFs::{Execute | ReadDir});
530 let access_net = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
531 let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
532
533 let ruleset = Ruleset::from(ABI::V5)
535 .handle_access(access_fs)
536 .unwrap()
537 .scope(scopes)
538 .unwrap()
539 .handle_access(access_net)
540 .unwrap();
541 assert_eq!(ruleset.requested_handled_fs, access_fs);
542 assert_eq!(ruleset.actual_handled_fs, access_fs);
543 assert_eq!(ruleset.requested_handled_net, access_net);
544 assert_eq!(ruleset.actual_handled_net, access_net);
545 assert_eq!(ruleset.requested_scoped, scopes);
546 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
547
548 let ruleset = Ruleset::from(ABI::V6)
550 .handle_access(access_fs)
551 .unwrap()
552 .scope(scopes)
553 .unwrap()
554 .handle_access(access_net)
555 .unwrap();
556 assert_eq!(ruleset.requested_handled_fs, access_fs);
557 assert_eq!(ruleset.actual_handled_fs, access_fs);
558 assert_eq!(ruleset.requested_handled_net, access_net);
559 assert_eq!(ruleset.actual_handled_net, access_net);
560 assert_eq!(ruleset.requested_scoped, scopes);
561 assert_eq!(ruleset.actual_scoped, scopes);
562}
563
564impl OptionCompatLevelMut for RulesetCreated {
565 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
566 &mut self.compat.level
567 }
568}
569
570impl OptionCompatLevelMut for &mut RulesetCreated {
571 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
572 &mut self.compat.level
573 }
574}
575
576impl Compatible for RulesetCreated {}
577
578impl Compatible for &mut RulesetCreated {}
579
580pub trait RulesetCreatedAttr: Sized + AsMut<RulesetCreated> + Compatible {
581 fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
585 where
586 T: Rule<U>,
587 U: HandledAccess + PrivateHandledAccess,
588 {
589 let body = || -> Result<Self, AddRulesError> {
590 let self_ref = self.as_mut();
591 rule.check_consistency(self_ref)?;
592 let mut compat_rule = match rule
593 .try_compat(
594 self_ref.compat.abi(),
595 self_ref.compat.level,
596 &mut self_ref.compat.state,
597 )
598 .map_err(AddRuleError::Compat)?
599 {
600 Some(r) => r,
601 None => return Ok(self),
602 };
603 match self_ref.compat.state {
604 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
605 CompatState::Full | CompatState::Partial => {
606 #[cfg(test)]
607 assert!(self_ref.fd.is_some());
608 let fd = self_ref.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
609 match unsafe {
610 uapi::landlock_add_rule(fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
611 } {
612 0 => Ok(self),
613 _ => Err(AddRuleError::<U>::AddRuleCall {
614 source: Error::last_os_error(),
615 }
616 .into()),
617 }
618 }
619 }
620 };
621 Ok(body()?)
622 }
623
624 fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
699 where
700 I: IntoIterator<Item = Result<T, E>>,
701 T: Rule<U>,
702 U: HandledAccess + PrivateHandledAccess,
703 E: From<RulesetError>,
704 {
705 for rule in rules {
706 self = self.add_rule(rule?)?;
707 }
708 Ok(self)
709 }
710
711 fn set_no_new_privs(mut self, no_new_privs: bool) -> Self {
717 <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = no_new_privs;
718 self
719 }
720}
721
722#[cfg_attr(test, derive(Debug))]
724pub struct RulesetCreated {
725 fd: Option<OwnedFd>,
726 no_new_privs: bool,
727 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
728 pub(crate) requested_handled_net: BitFlags<AccessNet>,
729 compat: Compatibility,
730}
731
732impl RulesetCreated {
733 pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
734 #[cfg(test)]
736 assert!(!matches!(ruleset.compat.state, CompatState::Init));
737
738 RulesetCreated {
739 fd,
740 no_new_privs: true,
741 requested_handled_fs: ruleset.requested_handled_fs,
742 requested_handled_net: ruleset.requested_handled_net,
743 compat: ruleset.compat,
744 }
745 }
746
747 pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
755 let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
756 let enforced_nnp = if self.no_new_privs {
760 if let Err(e) = prctl_set_no_new_privs() {
761 match self.compat.level.into() {
762 CompatLevel::BestEffort => {}
763 CompatLevel::SoftRequirement => {
764 self.compat.update(CompatState::Dummy);
765 }
766 CompatLevel::HardRequirement => {
767 return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
768 }
769 }
770 let support_nnp = support_no_new_privs();
773 match self.compat.state {
774 CompatState::Init | CompatState::No | CompatState::Dummy => {
777 if support_nnp {
778 return Err(RestrictSelfError::SetNoNewPrivsCall { source: e });
781 }
782 }
783 CompatState::Full | CompatState::Partial => {
786 return Err(RestrictSelfError::SetNoNewPrivsCall { source: e })
787 }
788 }
789 false
790 } else {
791 true
792 }
793 } else {
794 false
795 };
796
797 match self.compat.state {
798 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
799 ruleset: self.compat.state.into(),
800 no_new_privs: enforced_nnp,
801 }),
802 CompatState::Full | CompatState::Partial => {
803 #[cfg(test)]
804 assert!(self.fd.is_some());
805 let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
807 match unsafe { uapi::landlock_restrict_self(fd, 0) } {
808 0 => {
809 self.compat.update(CompatState::Full);
810 Ok(RestrictionStatus {
811 ruleset: self.compat.state.into(),
812 no_new_privs: enforced_nnp,
813 })
814 }
815 _ => Err(RestrictSelfError::RestrictSelfCall {
817 source: Error::last_os_error(),
818 }),
819 }
820 }
821 }
822 };
823 Ok(body()?)
824 }
825
826 pub fn try_clone(&self) -> std::io::Result<Self> {
831 Ok(RulesetCreated {
832 fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
833 no_new_privs: self.no_new_privs,
834 requested_handled_fs: self.requested_handled_fs,
835 requested_handled_net: self.requested_handled_net,
836 compat: self.compat,
837 })
838 }
839}
840
841impl From<RulesetCreated> for Option<OwnedFd> {
842 fn from(ruleset: RulesetCreated) -> Self {
843 ruleset.fd
844 }
845}
846
847#[test]
848fn ruleset_created_ownedfd_none() {
849 let ruleset = Ruleset::from(ABI::Unsupported)
850 .handle_access(AccessFs::Execute)
851 .unwrap()
852 .create()
853 .unwrap();
854 let fd: Option<OwnedFd> = ruleset.into();
855 assert!(fd.is_none());
856}
857
858impl AsMut<RulesetCreated> for RulesetCreated {
859 fn as_mut(&mut self) -> &mut RulesetCreated {
860 self
861 }
862}
863
864impl RulesetCreatedAttr for RulesetCreated {}
865
866impl RulesetCreatedAttr for &mut RulesetCreated {}
867
868#[test]
869fn ruleset_created_attr() {
870 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
871 .handle_access(AccessFs::Execute)
872 .unwrap()
873 .create()
874 .unwrap();
875 let ruleset_created_ref = &mut ruleset_created;
876
877 ruleset_created_ref
879 .set_compatibility(CompatLevel::BestEffort)
880 .add_rule(PathBeneath::new(
881 PathFd::new("/usr").unwrap(),
882 AccessFs::Execute,
883 ))
884 .unwrap()
885 .add_rule(PathBeneath::new(
886 PathFd::new("/etc").unwrap(),
887 AccessFs::Execute,
888 ))
889 .unwrap();
890
891 assert_eq!(
893 ruleset_created
894 .set_compatibility(CompatLevel::BestEffort)
895 .add_rule(PathBeneath::new(
896 PathFd::new("/tmp").unwrap(),
897 AccessFs::Execute,
898 ))
899 .unwrap()
900 .add_rule(PathBeneath::new(
901 PathFd::new("/var").unwrap(),
902 AccessFs::Execute,
903 ))
904 .unwrap()
905 .restrict_self()
906 .unwrap(),
907 RestrictionStatus {
908 ruleset: RulesetStatus::NotEnforced,
909 no_new_privs: true,
910 }
911 );
912}
913
914#[test]
915fn ruleset_compat_dummy() {
916 for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
917 println!("level: {:?}", level);
918
919 let ruleset = Ruleset::from(ABI::Unsupported);
921 assert_eq!(ruleset.compat.state, CompatState::Init);
922
923 let ruleset = ruleset.set_compatibility(level);
924 assert_eq!(ruleset.compat.state, CompatState::Init);
925
926 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
927 assert_eq!(
928 ruleset.compat.state,
929 match level {
930 CompatLevel::BestEffort => CompatState::No,
931 CompatLevel::SoftRequirement => CompatState::Dummy,
932 _ => unreachable!(),
933 }
934 );
935
936 let ruleset_created = ruleset.create().unwrap();
937 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
940
941 let ruleset_created = ruleset_created
942 .add_rule(PathBeneath::new(
943 PathFd::new("/usr").unwrap(),
944 AccessFs::Execute,
945 ))
946 .unwrap();
947 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
948 }
949}
950
951#[test]
952fn ruleset_compat_partial() {
953 let ruleset = Ruleset::from(ABI::V1);
955 assert_eq!(ruleset.compat.state, CompatState::Init);
956
957 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
959 assert_eq!(ruleset.compat.state, CompatState::No);
960
961 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
962 assert_eq!(ruleset.compat.state, CompatState::Partial);
963
964 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
966 assert_eq!(ruleset.compat.state, CompatState::Partial);
967}
968
969#[test]
970fn ruleset_unsupported() {
971 assert_eq!(
972 Ruleset::from(ABI::Unsupported)
973 .handle_access(AccessFs::Execute)
975 .unwrap()
976 .create()
977 .unwrap()
978 .restrict_self()
979 .unwrap(),
980 RestrictionStatus {
981 ruleset: RulesetStatus::NotEnforced,
982 no_new_privs: true,
984 }
985 );
986
987 assert_eq!(
988 Ruleset::from(ABI::Unsupported)
989 .set_compatibility(CompatLevel::SoftRequirement)
991 .handle_access(AccessFs::Execute)
992 .unwrap()
993 .create()
994 .unwrap()
995 .restrict_self()
996 .unwrap(),
997 RestrictionStatus {
998 ruleset: RulesetStatus::NotEnforced,
999 no_new_privs: true,
1001 }
1002 );
1003
1004 matches!(
1006 Ruleset::from(ABI::Unsupported)
1007 .set_compatibility(CompatLevel::HardRequirement)
1009 .handle_access(AccessFs::Execute)
1010 .unwrap_err(),
1011 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1012 );
1013
1014 matches!(
1016 Ruleset::from(ABI::Unsupported)
1017 .set_compatibility(CompatLevel::HardRequirement)
1019 .scope(Scope::Signal)
1020 .unwrap_err(),
1021 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1022 );
1023
1024 assert_eq!(
1025 Ruleset::from(ABI::Unsupported)
1026 .handle_access(AccessFs::Execute)
1027 .unwrap()
1028 .create()
1029 .unwrap()
1030 .set_compatibility(CompatLevel::SoftRequirement)
1032 .restrict_self()
1033 .unwrap(),
1034 RestrictionStatus {
1035 ruleset: RulesetStatus::NotEnforced,
1036 no_new_privs: true,
1038 }
1039 );
1040
1041 if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
1043 assert_eq!(
1044 Ruleset::from(ABI::V1)
1045 .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
1046 .unwrap()
1047 .create()
1048 .unwrap()
1049 .set_compatibility(CompatLevel::SoftRequirement)
1051 .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
1052 .unwrap()
1053 .restrict_self()
1054 .unwrap(),
1055 RestrictionStatus {
1056 ruleset: RulesetStatus::NotEnforced,
1057 no_new_privs: true,
1060 }
1061 );
1062 }
1063
1064 assert_eq!(
1065 Ruleset::from(ABI::Unsupported)
1066 .handle_access(AccessFs::Execute)
1067 .unwrap()
1068 .create()
1069 .unwrap()
1070 .set_no_new_privs(false)
1071 .restrict_self()
1072 .unwrap(),
1073 RestrictionStatus {
1074 ruleset: RulesetStatus::NotEnforced,
1075 no_new_privs: false,
1076 }
1077 );
1078
1079 assert!(matches!(
1081 Ruleset::from(ABI::Unsupported)
1082 .handle_access(AccessFs::from_all(ABI::Unsupported))
1084 .unwrap_err(),
1085 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1086 CompatError::Access(AccessError::Empty)
1087 )))
1088 ));
1089
1090 assert!(matches!(
1091 Ruleset::from(ABI::Unsupported)
1092 .create()
1094 .unwrap_err(),
1095 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1096 ));
1097
1098 assert!(matches!(
1100 Ruleset::from(ABI::V1)
1101 .handle_access(AccessFs::from_all(ABI::Unsupported))
1103 .unwrap_err(),
1104 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1105 CompatError::Access(AccessError::Empty)
1106 )))
1107 ));
1108
1109 assert!(matches!(
1111 Ruleset::from(ABI::Unsupported)
1112 .scope(Scope::from_all(ABI::Unsupported))
1113 .unwrap_err(),
1114 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1115 ));
1116
1117 assert!(matches!(
1119 Ruleset::from(ABI::V1)
1120 .scope(Scope::from_all(ABI::Unsupported))
1121 .unwrap_err(),
1122 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1123 ));
1124
1125 for handled_access in &[
1127 make_bitflags!(AccessFs::{Execute | WriteFile}),
1128 AccessFs::Execute.into(),
1129 ] {
1130 let ruleset = Ruleset::from(ABI::V1)
1131 .handle_access(*handled_access)
1132 .unwrap();
1133 let ruleset_created = RulesetCreated::new(ruleset, None);
1136 assert!(matches!(
1137 ruleset_created
1138 .add_rule(PathBeneath::new(
1139 PathFd::new("/").unwrap(),
1140 AccessFs::ReadFile
1141 ))
1142 .unwrap_err(),
1143 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
1144 ));
1145 }
1146}
1147
1148#[test]
1149fn ignore_abi_v2_with_abi_v1() {
1150 assert_eq!(
1153 Ruleset::from(ABI::V1)
1154 .set_compatibility(CompatLevel::HardRequirement)
1155 .handle_access(AccessFs::from_all(ABI::V1))
1156 .unwrap()
1157 .set_compatibility(CompatLevel::SoftRequirement)
1158 .handle_access(AccessFs::Refer)
1160 .unwrap()
1161 .create()
1162 .unwrap()
1163 .add_rule(PathBeneath::new(
1164 PathFd::new("/tmp").unwrap(),
1165 AccessFs::from_all(ABI::V2)
1166 ))
1167 .unwrap()
1168 .add_rule(PathBeneath::new(
1169 PathFd::new("/usr").unwrap(),
1170 make_bitflags!(AccessFs::{ReadFile | ReadDir})
1171 ))
1172 .unwrap()
1173 .restrict_self()
1174 .unwrap(),
1175 RestrictionStatus {
1176 ruleset: RulesetStatus::NotEnforced,
1177 no_new_privs: true,
1178 }
1179 );
1180}
1181
1182#[test]
1183fn unsupported_handled_access() {
1184 matches!(
1185 Ruleset::from(ABI::V3)
1186 .handle_access(AccessNet::from_all(ABI::V3))
1187 .unwrap_err(),
1188 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
1189 CompatError::Access(AccessError::Empty)
1190 )))
1191 );
1192}
1193
1194#[test]
1195fn unsupported_handled_access_errno() {
1196 assert_eq!(
1197 Errno::from(
1198 Ruleset::from(ABI::V3)
1199 .handle_access(AccessNet::from_all(ABI::V3))
1200 .unwrap_err()
1201 ),
1202 Errno::new(libc::EINVAL)
1203 );
1204}