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}
87
88#[derive(Debug)]
171pub struct Ruleset {
172 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
173 pub(crate) requested_handled_net: BitFlags<AccessNet>,
174 pub(crate) requested_scoped: BitFlags<Scope>,
175 pub(crate) actual_handled_fs: BitFlags<AccessFs>,
176 pub(crate) actual_handled_net: BitFlags<AccessNet>,
177 pub(crate) actual_scoped: BitFlags<Scope>,
178 pub(crate) compat: Compatibility,
179}
180
181impl From<Compatibility> for Ruleset {
182 fn from(compat: Compatibility) -> Self {
183 Ruleset {
184 requested_handled_fs: Default::default(),
186 requested_handled_net: Default::default(),
187 requested_scoped: Default::default(),
188 actual_handled_fs: Default::default(),
189 actual_handled_net: Default::default(),
190 actual_scoped: Default::default(),
191 compat,
192 }
193 }
194}
195
196#[cfg(test)]
197impl From<ABI> for Ruleset {
198 fn from(abi: ABI) -> Self {
199 Ruleset::from(Compatibility::from(abi))
200 }
201}
202
203#[test]
204fn ruleset_add_rule_iter() {
205 assert!(matches!(
206 Ruleset::from(ABI::Unsupported)
207 .handle_access(AccessFs::Execute)
208 .unwrap()
209 .create()
210 .unwrap()
211 .add_rule(PathBeneath::new(
212 PathFd::new("/").unwrap(),
213 AccessFs::ReadFile
214 ))
215 .unwrap_err(),
216 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
217 ));
218}
219
220impl Default for Ruleset {
221 fn default() -> Self {
228 Compatibility::new().into()
233 }
234}
235
236impl Ruleset {
237 #[allow(clippy::new_without_default)]
238 #[deprecated(note = "Use Ruleset::default() instead")]
239 pub fn new() -> Self {
240 Ruleset::default()
241 }
242
243 pub fn create(mut self) -> Result<RulesetCreated, RulesetError> {
248 let body = || -> Result<RulesetCreated, CreateRulesetError> {
249 match self.compat.state {
250 CompatState::Init => {
251 Err(CreateRulesetError::MissingHandledAccess)
254 }
255 CompatState::No | CompatState::Dummy => {
256 #[cfg(test)]
258 assert!(
259 !self.requested_handled_fs.is_empty()
260 || !self.requested_handled_net.is_empty()
261 || !self.requested_scoped.is_empty()
262 );
263
264 self.compat.update(CompatState::Dummy);
267 match self.compat.level.into() {
268 CompatLevel::HardRequirement => {
269 Err(CreateRulesetError::MissingHandledAccess)
270 }
271 _ => Ok(RulesetCreated::new(self, None)),
272 }
273 }
274 CompatState::Full | CompatState::Partial => {
275 #[cfg(test)]
277 assert!(
278 !self.actual_handled_fs.is_empty()
279 || !self.actual_handled_net.is_empty()
280 || !self.actual_scoped.is_empty()
281 );
282
283 let attr = uapi::landlock_ruleset_attr {
284 handled_access_fs: self.actual_handled_fs.bits(),
285 handled_access_net: self.actual_handled_net.bits(),
286 scoped: self.actual_scoped.bits(),
287 };
288 match unsafe { uapi::landlock_create_ruleset(&attr, size_of_val(&attr), 0) } {
289 fd if fd >= 0 => Ok(RulesetCreated::new(
290 self,
291 Some(unsafe { OwnedFd::from_raw_fd(fd) }),
292 )),
293 _ => Err(CreateRulesetError::CreateRulesetCall {
294 source: Error::last_os_error(),
295 }),
296 }
297 }
298 }
299 };
300 Ok(body()?)
301 }
302}
303
304impl OptionCompatLevelMut for Ruleset {
305 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
306 &mut self.compat.level
307 }
308}
309
310impl OptionCompatLevelMut for &mut Ruleset {
311 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
312 &mut self.compat.level
313 }
314}
315
316impl Compatible for Ruleset {}
317
318impl Compatible for &mut Ruleset {}
319
320impl AsMut<Ruleset> for Ruleset {
321 fn as_mut(&mut self) -> &mut Ruleset {
322 self
323 }
324}
325
326#[test]
328fn ruleset_as_mut() {
329 let mut ruleset = Ruleset::from(ABI::Unsupported);
330 let _ = ruleset.as_mut();
331
332 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
333 .handle_access(AccessFs::Execute)
334 .unwrap()
335 .create()
336 .unwrap();
337 let _ = ruleset_created.as_mut();
338}
339
340pub trait RulesetAttr: Sized + AsMut<Ruleset> + Compatible {
341 fn handle_access<T, U>(mut self, access: T) -> Result<Self, RulesetError>
349 where
350 T: Into<BitFlags<U>>,
351 U: HandledAccess + PrivateHandledAccess,
352 {
353 U::ruleset_handle_access(self.as_mut(), access.into())?;
354 Ok(self)
355 }
356
357 fn scope<T>(mut self, scope: T) -> Result<Self, RulesetError>
364 where
365 T: Into<BitFlags<Scope>>,
366 {
367 let scope = scope.into();
368 let ruleset = self.as_mut();
369 ruleset.requested_scoped |= scope;
370 if let Some(a) = scope
371 .try_compat(
372 ruleset.compat.abi(),
373 ruleset.compat.level,
374 &mut ruleset.compat.state,
375 )
376 .map_err(ScopeError::Compat)?
377 {
378 ruleset.actual_scoped |= a;
379 }
380 Ok(self)
381 }
382}
383
384impl RulesetAttr for Ruleset {}
385
386impl RulesetAttr for &mut Ruleset {}
387
388#[test]
389fn ruleset_attr() {
390 let mut ruleset = Ruleset::from(ABI::Unsupported);
391 let ruleset_ref = &mut ruleset;
392
393 ruleset_ref
395 .set_compatibility(CompatLevel::BestEffort)
396 .handle_access(AccessFs::Execute)
397 .unwrap()
398 .handle_access(AccessFs::ReadFile)
399 .unwrap();
400
401 ruleset
403 .set_compatibility(CompatLevel::BestEffort)
404 .handle_access(AccessFs::Execute)
405 .unwrap()
406 .handle_access(AccessFs::WriteFile)
407 .unwrap()
408 .create()
409 .unwrap();
410}
411
412#[test]
413fn ruleset_created_handle_access_fs() {
414 let access = make_bitflags!(AccessFs::{Execute | ReadDir});
415
416 let ruleset = Ruleset::from(ABI::V1).handle_access(access).unwrap();
418 assert_eq!(ruleset.requested_handled_fs, access);
419 assert_eq!(ruleset.actual_handled_fs, access);
420
421 let ruleset = Ruleset::from(ABI::V1)
423 .handle_access(AccessFs::Execute)
424 .unwrap()
425 .handle_access(AccessFs::ReadDir)
426 .unwrap()
427 .handle_access(AccessFs::Execute)
428 .unwrap();
429 assert_eq!(ruleset.requested_handled_fs, access);
430 assert_eq!(ruleset.actual_handled_fs, access);
431
432 assert!(matches!(Ruleset::from(ABI::Unsupported)
435 .handle_access(AccessFs::Execute)
436 .unwrap()
437 .set_compatibility(CompatLevel::HardRequirement)
438 .handle_access(AccessFs::ReadDir)
439 .unwrap_err(),
440 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
441 CompatError::Access(AccessError::Incompatible { access })
442 ))) if access == AccessFs::ReadDir
443 ));
444}
445
446#[test]
447fn ruleset_created_handle_access_net_tcp() {
448 let access = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
449
450 let ruleset = Ruleset::from(ABI::V3).handle_access(access).unwrap();
452 assert_eq!(ruleset.requested_handled_net, access);
453 assert_eq!(ruleset.actual_handled_net, BitFlags::<AccessNet>::EMPTY);
454
455 let ruleset = Ruleset::from(ABI::V4).handle_access(access).unwrap();
457 assert_eq!(ruleset.requested_handled_net, access);
458 assert_eq!(ruleset.actual_handled_net, access);
459
460 let ruleset = Ruleset::from(ABI::V4)
462 .handle_access(AccessNet::BindTcp)
463 .unwrap()
464 .handle_access(AccessNet::ConnectTcp)
465 .unwrap()
466 .handle_access(AccessNet::BindTcp)
467 .unwrap();
468 assert_eq!(ruleset.requested_handled_net, access);
469 assert_eq!(ruleset.actual_handled_net, access);
470
471 assert!(matches!(Ruleset::from(ABI::Unsupported)
474 .handle_access(AccessNet::BindTcp)
475 .unwrap()
476 .set_compatibility(CompatLevel::HardRequirement)
477 .handle_access(AccessNet::ConnectTcp)
478 .unwrap_err(),
479 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
480 CompatError::Access(AccessError::Incompatible { access })
481 ))) if access == AccessNet::ConnectTcp
482 ));
483}
484
485#[test]
486fn ruleset_created_scope() {
487 let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
488
489 let ruleset = Ruleset::from(ABI::V5).scope(scopes).unwrap();
491 assert_eq!(ruleset.requested_scoped, scopes);
492 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
493
494 let ruleset = Ruleset::from(ABI::V6).scope(scopes).unwrap();
496 assert_eq!(ruleset.requested_scoped, scopes);
497 assert_eq!(ruleset.actual_scoped, scopes);
498
499 let ruleset = Ruleset::from(ABI::V6)
501 .scope(Scope::AbstractUnixSocket)
502 .unwrap()
503 .scope(Scope::Signal)
504 .unwrap()
505 .scope(Scope::AbstractUnixSocket)
506 .unwrap();
507 assert_eq!(ruleset.requested_scoped, scopes);
508 assert_eq!(ruleset.actual_scoped, scopes);
509
510 assert!(matches!(Ruleset::from(ABI::Unsupported)
513 .scope(Scope::AbstractUnixSocket)
514 .unwrap()
515 .set_compatibility(CompatLevel::HardRequirement)
516 .scope(Scope::Signal)
517 .unwrap_err(),
518 RulesetError::Scope(ScopeError::Compat(
519 CompatError::Access(AccessError::Incompatible { access })
520 )) if access == Scope::Signal
521 ));
522}
523
524#[test]
525fn ruleset_created_fs_net_scope() {
526 let access_fs = make_bitflags!(AccessFs::{Execute | ReadDir});
527 let access_net = make_bitflags!(AccessNet::{BindTcp | ConnectTcp});
528 let scopes = make_bitflags!(Scope::{AbstractUnixSocket | Signal});
529
530 let ruleset = Ruleset::from(ABI::V5)
532 .handle_access(access_fs)
533 .unwrap()
534 .scope(scopes)
535 .unwrap()
536 .handle_access(access_net)
537 .unwrap();
538 assert_eq!(ruleset.requested_handled_fs, access_fs);
539 assert_eq!(ruleset.actual_handled_fs, access_fs);
540 assert_eq!(ruleset.requested_handled_net, access_net);
541 assert_eq!(ruleset.actual_handled_net, access_net);
542 assert_eq!(ruleset.requested_scoped, scopes);
543 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
544
545 let ruleset = Ruleset::from(ABI::V6)
547 .handle_access(access_fs)
548 .unwrap()
549 .scope(scopes)
550 .unwrap()
551 .handle_access(access_net)
552 .unwrap();
553 assert_eq!(ruleset.requested_handled_fs, access_fs);
554 assert_eq!(ruleset.actual_handled_fs, access_fs);
555 assert_eq!(ruleset.requested_handled_net, access_net);
556 assert_eq!(ruleset.actual_handled_net, access_net);
557 assert_eq!(ruleset.requested_scoped, scopes);
558 assert_eq!(ruleset.actual_scoped, scopes);
559}
560
561#[test]
562fn ruleset_created_log_flags() {
563 let all_raw = uapi::LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
564 | uapi::LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
565 | uapi::LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF;
566
567 let ruleset_created = Ruleset::from(ABI::Unsupported)
569 .handle_access(AccessFs::Execute)
570 .unwrap()
571 .create()
572 .unwrap()
573 .log_same_exec(false)
574 .unwrap()
575 .log_new_exec(true)
576 .unwrap()
577 .log_subdomains(false)
578 .unwrap();
579 assert_eq!(ruleset_created.requested_restrict_self_flags, all_raw);
580 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
581
582 let ruleset_created = Ruleset::from(ABI::Unsupported)
584 .handle_access(AccessFs::Execute)
585 .unwrap()
586 .create()
587 .unwrap()
588 .log_same_exec(true)
589 .unwrap()
590 .log_new_exec(false)
591 .unwrap()
592 .log_subdomains(true)
593 .unwrap();
594 assert_eq!(ruleset_created.requested_restrict_self_flags, 0);
595 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
596
597 let ruleset_created = Ruleset::from(ABI::Unsupported)
599 .handle_access(AccessFs::Execute)
600 .unwrap()
601 .create()
602 .unwrap()
603 .set_compatibility(CompatLevel::SoftRequirement)
604 .log_same_exec(false)
605 .unwrap();
606 assert_eq!(
607 ruleset_created.requested_restrict_self_flags,
608 uapi::LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF
609 );
610 assert_eq!(ruleset_created.actual_restrict_self_flags, 0);
611
612 Ruleset::from(ABI::Unsupported)
614 .handle_access(AccessFs::Execute)
615 .unwrap()
616 .create()
617 .unwrap()
618 .set_compatibility(CompatLevel::HardRequirement)
619 .log_same_exec(true)
620 .unwrap()
621 .log_new_exec(false)
622 .unwrap()
623 .log_subdomains(true)
624 .unwrap();
625
626 assert!(matches!(
628 Ruleset::from(ABI::Unsupported)
629 .handle_access(AccessFs::Execute)
630 .unwrap()
631 .create()
632 .unwrap()
633 .set_compatibility(CompatLevel::HardRequirement)
634 .log_same_exec(false)
635 .unwrap_err(),
636 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
637 flag: RestrictSelfFlag::LogSameExec,
638 set: false,
639 })
640 ));
641}
642
643impl OptionCompatLevelMut for RulesetCreated {
644 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
645 &mut self.compat.level
646 }
647}
648
649impl OptionCompatLevelMut for &mut RulesetCreated {
650 fn as_option_compat_level_mut(&mut self) -> &mut Option<CompatLevel> {
651 &mut self.compat.level
652 }
653}
654
655impl Compatible for RulesetCreated {}
656
657impl Compatible for &mut RulesetCreated {}
658
659impl RestrictSelfFlagsState for RulesetCreated {
660 fn try_set_flag(&mut self, flag: RestrictSelfFlag, set: bool) -> Result<(), RulesetError> {
661 let raw_bit = flag.raw_bit();
662 if set == flag.default_value() {
672 self.requested_restrict_self_flags &= !raw_bit;
673 } else {
674 self.requested_restrict_self_flags |= raw_bit;
675 }
676 if flag.try_compat(set, &mut self.compat)? {
677 self.actual_restrict_self_flags |= raw_bit;
678 } else {
679 self.actual_restrict_self_flags &= !raw_bit;
680 }
681 Ok(())
682 }
683}
684
685impl RestrictSelfFlagsState for &mut RulesetCreated {
686 fn try_set_flag(&mut self, flag: RestrictSelfFlag, set: bool) -> Result<(), RulesetError> {
687 (**self).try_set_flag(flag, set)
688 }
689}
690
691impl RestrictSelfAttr for RulesetCreated {}
692impl RestrictSelfAttr for &mut RulesetCreated {}
693
694pub trait RulesetCreatedAttr:
695 Sized + AsMut<RulesetCreated> + Compatible + RestrictSelfAttr
696{
697 fn add_rule<T, U>(mut self, rule: T) -> Result<Self, RulesetError>
701 where
702 T: Rule<U>,
703 U: HandledAccess + PrivateHandledAccess,
704 {
705 let body = || -> Result<Self, AddRulesError> {
706 let self_ref = self.as_mut();
707 rule.check_consistency(self_ref)?;
708 let mut compat_rule = match rule
709 .try_compat(
710 self_ref.compat.abi(),
711 self_ref.compat.level,
712 &mut self_ref.compat.state,
713 )
714 .map_err(AddRuleError::Compat)?
715 {
716 Some(r) => r,
717 None => return Ok(self),
718 };
719 match self_ref.compat.state {
720 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(self),
721 CompatState::Full | CompatState::Partial => {
722 #[cfg(test)]
723 assert!(self_ref.fd.is_some());
724 let fd = self_ref.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
725 match unsafe {
726 uapi::landlock_add_rule(fd, T::TYPE_ID, compat_rule.as_ptr(), 0)
727 } {
728 0 => Ok(self),
729 _ => Err(AddRuleError::<U>::AddRuleCall {
730 source: Error::last_os_error(),
731 }
732 .into()),
733 }
734 }
735 }
736 };
737 Ok(body()?)
738 }
739
740 fn add_rules<I, T, U, E>(mut self, rules: I) -> Result<Self, E>
815 where
816 I: IntoIterator<Item = Result<T, E>>,
817 T: Rule<U>,
818 U: HandledAccess + PrivateHandledAccess,
819 E: From<RulesetError>,
820 {
821 for rule in rules {
822 self = self.add_rule(rule?)?;
823 }
824 Ok(self)
825 }
826
827 fn no_new_privs(mut self, yes: bool) -> Self {
833 <Self as AsMut<RulesetCreated>>::as_mut(&mut self).no_new_privs = yes;
834 self
835 }
836
837 #[deprecated(note = "Use no_new_privs() instead.")]
839 fn set_no_new_privs(self, yes: bool) -> Self {
840 self.no_new_privs(yes)
841 }
842
843 fn log_same_exec(mut self, set: bool) -> Result<Self, RulesetError> {
856 self.try_set_flag(RestrictSelfFlag::LogSameExec, set)?;
857 Ok(self)
858 }
859
860 fn log_new_exec(mut self, set: bool) -> Result<Self, RulesetError> {
872 self.try_set_flag(RestrictSelfFlag::LogNewExec, set)?;
873 Ok(self)
874 }
875}
876
877#[derive(Debug)]
879pub struct RulesetCreated {
880 fd: Option<OwnedFd>,
881 no_new_privs: bool,
882 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
883 pub(crate) requested_handled_net: BitFlags<AccessNet>,
884 requested_restrict_self_flags: u32,
885 actual_restrict_self_flags: u32,
886 compat: Compatibility,
887}
888
889impl RulesetCreated {
890 pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
891 #[cfg(test)]
893 assert!(!matches!(ruleset.compat.state, CompatState::Init));
894
895 RulesetCreated {
896 fd,
897 no_new_privs: true,
898 requested_handled_fs: ruleset.requested_handled_fs,
899 requested_handled_net: ruleset.requested_handled_net,
900 requested_restrict_self_flags: 0,
901 actual_restrict_self_flags: 0,
902 compat: ruleset.compat,
903 }
904 }
905
906 pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
914 let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
915 let enforced_nnp = if self.no_new_privs {
919 try_set_no_new_privs(&mut self.compat)?
920 } else {
921 false
922 };
923
924 let raw = self.actual_restrict_self_flags;
925 let log_same_exec = RestrictSelfFlag::LogSameExec.is_set(raw);
926 let log_new_exec = RestrictSelfFlag::LogNewExec.is_set(raw);
927 let log_subdomains = RestrictSelfFlag::LogSubdomains.is_set(raw);
928
929 match self.compat.state {
930 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
931 ruleset: self.compat.state.into(),
932 landlock: self.compat.status(),
933 no_new_privs: enforced_nnp,
934 log_same_exec,
935 log_new_exec,
936 log_subdomains,
937 }),
938 CompatState::Full | CompatState::Partial => {
939 #[cfg(test)]
940 assert!(self.fd.is_some());
941 let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
943 match unsafe {
944 uapi::landlock_restrict_self(fd, self.actual_restrict_self_flags)
945 } {
946 0 => {
947 self.compat.update(CompatState::Full);
948 Ok(RestrictionStatus {
949 ruleset: self.compat.state.into(),
950 landlock: self.compat.status(),
951 no_new_privs: enforced_nnp,
952 log_same_exec,
953 log_new_exec,
954 log_subdomains,
955 })
956 }
957 _ => Err(RestrictSelfError::RestrictSelfCall {
959 source: Error::last_os_error(),
960 }),
961 }
962 }
963 }
964 };
965 Ok(body()?)
966 }
967
968 pub fn try_clone(&self) -> std::io::Result<Self> {
973 Ok(RulesetCreated {
974 fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
975 no_new_privs: self.no_new_privs,
976 requested_handled_fs: self.requested_handled_fs,
977 requested_handled_net: self.requested_handled_net,
978 requested_restrict_self_flags: self.requested_restrict_self_flags,
979 actual_restrict_self_flags: self.actual_restrict_self_flags,
980 compat: self.compat,
981 })
982 }
983}
984
985impl From<RulesetCreated> for Option<OwnedFd> {
986 fn from(ruleset: RulesetCreated) -> Self {
987 ruleset.fd
988 }
989}
990
991#[test]
992fn ruleset_created_ownedfd_none() {
993 let ruleset = Ruleset::from(ABI::Unsupported)
994 .handle_access(AccessFs::Execute)
995 .unwrap()
996 .create()
997 .unwrap();
998 let fd: Option<OwnedFd> = ruleset.into();
999 assert!(fd.is_none());
1000}
1001
1002impl AsMut<RulesetCreated> for RulesetCreated {
1003 fn as_mut(&mut self) -> &mut RulesetCreated {
1004 self
1005 }
1006}
1007
1008impl RulesetCreatedAttr for RulesetCreated {}
1009
1010impl RulesetCreatedAttr for &mut RulesetCreated {}
1011
1012#[test]
1013fn ruleset_created_attr() {
1014 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
1015 .handle_access(AccessFs::Execute)
1016 .unwrap()
1017 .create()
1018 .unwrap();
1019 let ruleset_created_ref = &mut ruleset_created;
1020
1021 ruleset_created_ref
1023 .set_compatibility(CompatLevel::BestEffort)
1024 .add_rule(PathBeneath::new(
1025 PathFd::new("/usr").unwrap(),
1026 AccessFs::Execute,
1027 ))
1028 .unwrap()
1029 .add_rule(PathBeneath::new(
1030 PathFd::new("/etc").unwrap(),
1031 AccessFs::Execute,
1032 ))
1033 .unwrap();
1034
1035 assert_eq!(
1037 ruleset_created
1038 .set_compatibility(CompatLevel::BestEffort)
1039 .add_rule(PathBeneath::new(
1040 PathFd::new("/tmp").unwrap(),
1041 AccessFs::Execute,
1042 ))
1043 .unwrap()
1044 .add_rule(PathBeneath::new(
1045 PathFd::new("/var").unwrap(),
1046 AccessFs::Execute,
1047 ))
1048 .unwrap()
1049 .restrict_self()
1050 .unwrap(),
1051 RestrictionStatus {
1052 ruleset: RulesetStatus::NotEnforced,
1053 landlock: LandlockStatus::NotImplemented,
1054 no_new_privs: true,
1055 log_same_exec: true,
1056 log_new_exec: false,
1057 log_subdomains: true,
1058 }
1059 );
1060}
1061
1062#[test]
1063fn ruleset_compat_dummy() {
1064 for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
1065 println!("level: {:?}", level);
1066
1067 let ruleset = Ruleset::from(ABI::Unsupported);
1069 assert_eq!(ruleset.compat.state, CompatState::Init);
1070
1071 let ruleset = ruleset.set_compatibility(level);
1072 assert_eq!(ruleset.compat.state, CompatState::Init);
1073
1074 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1075 assert_eq!(
1076 ruleset.compat.state,
1077 match level {
1078 CompatLevel::BestEffort => CompatState::No,
1079 CompatLevel::SoftRequirement => CompatState::Dummy,
1080 _ => unreachable!(),
1081 }
1082 );
1083
1084 let ruleset_created = ruleset.create().unwrap();
1085 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1088
1089 let ruleset_created = ruleset_created
1090 .add_rule(PathBeneath::new(
1091 PathFd::new("/usr").unwrap(),
1092 AccessFs::Execute,
1093 ))
1094 .unwrap();
1095 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1096 }
1097}
1098
1099#[test]
1100fn ruleset_compat_partial() {
1101 let ruleset = Ruleset::from(ABI::V1);
1103 assert_eq!(ruleset.compat.state, CompatState::Init);
1104
1105 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1107 assert_eq!(ruleset.compat.state, CompatState::No);
1108
1109 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1110 assert_eq!(ruleset.compat.state, CompatState::Partial);
1111
1112 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1114 assert_eq!(ruleset.compat.state, CompatState::Partial);
1115}
1116
1117#[test]
1118fn ruleset_unsupported() {
1119 assert_eq!(
1120 Ruleset::from(ABI::Unsupported)
1121 .handle_access(AccessFs::Execute)
1123 .unwrap()
1124 .create()
1125 .unwrap()
1126 .restrict_self()
1127 .unwrap(),
1128 RestrictionStatus {
1129 ruleset: RulesetStatus::NotEnforced,
1130 landlock: LandlockStatus::NotImplemented,
1131 no_new_privs: true,
1133 log_same_exec: true,
1134 log_new_exec: false,
1135 log_subdomains: true,
1136 }
1137 );
1138
1139 assert_eq!(
1140 Ruleset::from(ABI::Unsupported)
1141 .set_compatibility(CompatLevel::SoftRequirement)
1143 .handle_access(AccessFs::Execute)
1144 .unwrap()
1145 .create()
1146 .unwrap()
1147 .restrict_self()
1148 .unwrap(),
1149 RestrictionStatus {
1150 ruleset: RulesetStatus::NotEnforced,
1151 landlock: LandlockStatus::NotImplemented,
1152 no_new_privs: true,
1154 log_same_exec: true,
1155 log_new_exec: false,
1156 log_subdomains: true,
1157 }
1158 );
1159
1160 assert!(matches!(
1162 Ruleset::from(ABI::Unsupported)
1163 .set_compatibility(CompatLevel::HardRequirement)
1165 .handle_access(AccessFs::Execute)
1166 .unwrap_err(),
1167 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1168 CompatError::Access(AccessError::Incompatible { .. })
1169 )))
1170 ));
1171
1172 assert!(matches!(
1174 Ruleset::from(ABI::Unsupported)
1175 .set_compatibility(CompatLevel::HardRequirement)
1177 .scope(Scope::Signal)
1178 .unwrap_err(),
1179 RulesetError::Scope(ScopeError::Compat(CompatError::Access(
1180 AccessError::Incompatible { .. }
1181 )))
1182 ));
1183
1184 assert_eq!(
1185 Ruleset::from(ABI::Unsupported)
1186 .handle_access(AccessFs::Execute)
1187 .unwrap()
1188 .create()
1189 .unwrap()
1190 .set_compatibility(CompatLevel::SoftRequirement)
1192 .restrict_self()
1193 .unwrap(),
1194 RestrictionStatus {
1195 ruleset: RulesetStatus::NotEnforced,
1196 landlock: LandlockStatus::NotImplemented,
1197 no_new_privs: true,
1199 log_same_exec: true,
1200 log_new_exec: false,
1201 log_subdomains: true,
1202 }
1203 );
1204
1205 if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
1207 assert_eq!(
1208 Ruleset::from(ABI::V1)
1209 .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
1210 .unwrap()
1211 .create()
1212 .unwrap()
1213 .set_compatibility(CompatLevel::SoftRequirement)
1215 .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
1216 .unwrap()
1217 .restrict_self()
1218 .unwrap(),
1219 RestrictionStatus {
1220 ruleset: RulesetStatus::NotEnforced,
1221 landlock: LandlockStatus::Available {
1222 effective_abi: ABI::V1,
1223 kernel_abi: None,
1224 },
1225 no_new_privs: true,
1228 log_same_exec: true,
1229 log_new_exec: false,
1230 log_subdomains: true,
1231 }
1232 );
1233 }
1234
1235 assert_eq!(
1236 Ruleset::from(ABI::Unsupported)
1237 .handle_access(AccessFs::Execute)
1238 .unwrap()
1239 .create()
1240 .unwrap()
1241 .no_new_privs(false)
1242 .restrict_self()
1243 .unwrap(),
1244 RestrictionStatus {
1245 ruleset: RulesetStatus::NotEnforced,
1246 landlock: LandlockStatus::NotImplemented,
1247 no_new_privs: false,
1248 log_same_exec: true,
1249 log_new_exec: false,
1250 log_subdomains: true,
1251 }
1252 );
1253
1254 assert!(matches!(
1256 Ruleset::from(ABI::Unsupported)
1257 .handle_access(AccessFs::from_all(ABI::Unsupported))
1259 .unwrap_err(),
1260 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1261 CompatError::Access(AccessError::Empty)
1262 )))
1263 ));
1264
1265 assert!(matches!(
1266 Ruleset::from(ABI::Unsupported)
1267 .create()
1269 .unwrap_err(),
1270 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1271 ));
1272
1273 assert!(matches!(
1275 Ruleset::from(ABI::V1)
1276 .handle_access(AccessFs::from_all(ABI::Unsupported))
1278 .unwrap_err(),
1279 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1280 CompatError::Access(AccessError::Empty)
1281 )))
1282 ));
1283
1284 assert!(matches!(
1286 Ruleset::from(ABI::Unsupported)
1287 .scope(Scope::from_all(ABI::Unsupported))
1288 .unwrap_err(),
1289 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1290 ));
1291
1292 assert!(matches!(
1294 Ruleset::from(ABI::V1)
1295 .scope(Scope::from_all(ABI::Unsupported))
1296 .unwrap_err(),
1297 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1298 ));
1299
1300 let ruleset = Ruleset::from(ABI::V1)
1302 .handle_access(AccessFs::Execute)
1303 .unwrap()
1304 .set_compatibility(CompatLevel::SoftRequirement)
1305 .scope(Scope::Signal)
1306 .unwrap();
1307 assert_eq!(ruleset.requested_scoped, BitFlags::from(Scope::Signal));
1308 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
1309
1310 assert_eq!(
1312 Ruleset::from(ABI::Unsupported)
1313 .handle_access(AccessFs::Execute)
1314 .unwrap()
1315 .create()
1316 .unwrap()
1317 .log_same_exec(false)
1318 .unwrap()
1319 .restrict_self()
1320 .unwrap(),
1321 RestrictionStatus {
1322 ruleset: RulesetStatus::NotEnforced,
1323 landlock: LandlockStatus::NotImplemented,
1324 no_new_privs: true,
1325 log_same_exec: true,
1326 log_new_exec: false,
1327 log_subdomains: true,
1328 }
1329 );
1330
1331 assert!(matches!(
1333 Ruleset::from(ABI::Unsupported)
1334 .handle_access(AccessFs::Execute)
1335 .unwrap()
1336 .create()
1337 .unwrap()
1338 .set_compatibility(CompatLevel::HardRequirement)
1339 .log_new_exec(true)
1340 .unwrap_err(),
1341 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
1342 flag: RestrictSelfFlag::LogNewExec,
1343 set: true,
1344 })
1345 ));
1346
1347 for handled_access in &[
1349 make_bitflags!(AccessFs::{Execute | WriteFile}),
1350 AccessFs::Execute.into(),
1351 ] {
1352 let ruleset = Ruleset::from(ABI::V1)
1353 .handle_access(*handled_access)
1354 .unwrap();
1355 let ruleset_created = RulesetCreated::new(ruleset, None);
1358 assert!(matches!(
1359 ruleset_created
1360 .add_rule(PathBeneath::new(
1361 PathFd::new("/").unwrap(),
1362 AccessFs::ReadFile
1363 ))
1364 .unwrap_err(),
1365 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
1366 ));
1367 }
1368}
1369
1370#[test]
1371fn ignore_abi_v2_with_abi_v1() {
1372 assert_eq!(
1375 Ruleset::from(ABI::V1)
1376 .set_compatibility(CompatLevel::HardRequirement)
1377 .handle_access(AccessFs::from_all(ABI::V1))
1378 .unwrap()
1379 .set_compatibility(CompatLevel::SoftRequirement)
1380 .handle_access(AccessFs::Refer)
1382 .unwrap()
1383 .create()
1384 .unwrap()
1385 .add_rule(PathBeneath::new(
1386 PathFd::new("/tmp").unwrap(),
1387 AccessFs::from_all(ABI::V2)
1388 ))
1389 .unwrap()
1390 .add_rule(PathBeneath::new(
1391 PathFd::new("/usr").unwrap(),
1392 make_bitflags!(AccessFs::{ReadFile | ReadDir})
1393 ))
1394 .unwrap()
1395 .restrict_self()
1396 .unwrap(),
1397 RestrictionStatus {
1398 ruleset: RulesetStatus::NotEnforced,
1399 landlock: LandlockStatus::Available {
1400 effective_abi: ABI::V1,
1401 kernel_abi: None,
1402 },
1403 no_new_privs: true,
1404 log_same_exec: true,
1405 log_new_exec: false,
1406 log_subdomains: true,
1407 }
1408 );
1409}
1410
1411#[test]
1412fn unsupported_handled_access() {
1413 assert!(matches!(
1414 Ruleset::from(ABI::V3)
1415 .handle_access(AccessNet::from_all(ABI::V3))
1416 .unwrap_err(),
1417 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
1418 CompatError::Access(AccessError::Empty)
1419 )))
1420 ));
1421}
1422
1423#[test]
1424fn unsupported_handled_access_errno() {
1425 assert_eq!(
1426 Errno::from(
1427 Ruleset::from(ABI::V3)
1428 .handle_access(AccessNet::from_all(ABI::V3))
1429 .unwrap_err()
1430 ),
1431 Errno::new(libc::EINVAL)
1432 );
1433}