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> {
858 self.try_set_flag(RestrictSelfFlag::LogSameExec, set)?;
859 Ok(self)
860 }
861
862 fn log_new_exec(mut self, set: bool) -> Result<Self, RulesetError> {
876 self.try_set_flag(RestrictSelfFlag::LogNewExec, set)?;
877 Ok(self)
878 }
879}
880
881#[derive(Debug)]
883pub struct RulesetCreated {
884 fd: Option<OwnedFd>,
885 no_new_privs: bool,
886 pub(crate) requested_handled_fs: BitFlags<AccessFs>,
887 pub(crate) requested_handled_net: BitFlags<AccessNet>,
888 requested_restrict_self_flags: u32,
889 actual_restrict_self_flags: u32,
890 compat: Compatibility,
891}
892
893impl RulesetCreated {
894 pub(crate) fn new(ruleset: Ruleset, fd: Option<OwnedFd>) -> Self {
895 #[cfg(test)]
897 assert!(!matches!(ruleset.compat.state, CompatState::Init));
898
899 RulesetCreated {
900 fd,
901 no_new_privs: true,
902 requested_handled_fs: ruleset.requested_handled_fs,
903 requested_handled_net: ruleset.requested_handled_net,
904 requested_restrict_self_flags: 0,
905 actual_restrict_self_flags: 0,
906 compat: ruleset.compat,
907 }
908 }
909
910 pub fn restrict_self(mut self) -> Result<RestrictionStatus, RulesetError> {
918 let mut body = || -> Result<RestrictionStatus, RestrictSelfError> {
919 let enforced_nnp = if self.no_new_privs {
923 try_set_no_new_privs(&mut self.compat)?
924 } else {
925 false
926 };
927
928 let raw = self.actual_restrict_self_flags;
929 let log_same_exec = RestrictSelfFlag::LogSameExec.is_set(raw);
930 let log_new_exec = RestrictSelfFlag::LogNewExec.is_set(raw);
931 let log_subdomains = RestrictSelfFlag::LogSubdomains.is_set(raw);
932
933 match self.compat.state {
934 CompatState::Init | CompatState::No | CompatState::Dummy => Ok(RestrictionStatus {
935 ruleset: self.compat.state.into(),
936 landlock: self.compat.status(),
937 no_new_privs: enforced_nnp,
938 log_same_exec,
939 log_new_exec,
940 log_subdomains,
941 }),
942 CompatState::Full | CompatState::Partial => {
943 #[cfg(test)]
944 assert!(self.fd.is_some());
945 let fd = self.fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
947 match unsafe {
948 uapi::landlock_restrict_self(fd, self.actual_restrict_self_flags)
949 } {
950 0 => {
951 self.compat.update(CompatState::Full);
952 Ok(RestrictionStatus {
953 ruleset: self.compat.state.into(),
954 landlock: self.compat.status(),
955 no_new_privs: enforced_nnp,
956 log_same_exec,
957 log_new_exec,
958 log_subdomains,
959 })
960 }
961 _ => Err(RestrictSelfError::RestrictSelfCall {
963 source: Error::last_os_error(),
964 }),
965 }
966 }
967 }
968 };
969 Ok(body()?)
970 }
971
972 pub fn try_clone(&self) -> std::io::Result<Self> {
977 Ok(RulesetCreated {
978 fd: self.fd.as_ref().map(|f| f.try_clone()).transpose()?,
979 no_new_privs: self.no_new_privs,
980 requested_handled_fs: self.requested_handled_fs,
981 requested_handled_net: self.requested_handled_net,
982 requested_restrict_self_flags: self.requested_restrict_self_flags,
983 actual_restrict_self_flags: self.actual_restrict_self_flags,
984 compat: self.compat,
985 })
986 }
987}
988
989impl From<RulesetCreated> for Option<OwnedFd> {
990 fn from(ruleset: RulesetCreated) -> Self {
991 ruleset.fd
992 }
993}
994
995#[test]
996fn ruleset_created_ownedfd_none() {
997 let ruleset = Ruleset::from(ABI::Unsupported)
998 .handle_access(AccessFs::Execute)
999 .unwrap()
1000 .create()
1001 .unwrap();
1002 let fd: Option<OwnedFd> = ruleset.into();
1003 assert!(fd.is_none());
1004}
1005
1006impl AsMut<RulesetCreated> for RulesetCreated {
1007 fn as_mut(&mut self) -> &mut RulesetCreated {
1008 self
1009 }
1010}
1011
1012impl RulesetCreatedAttr for RulesetCreated {}
1013
1014impl RulesetCreatedAttr for &mut RulesetCreated {}
1015
1016#[test]
1017fn ruleset_created_attr() {
1018 let mut ruleset_created = Ruleset::from(ABI::Unsupported)
1019 .handle_access(AccessFs::Execute)
1020 .unwrap()
1021 .create()
1022 .unwrap();
1023 let ruleset_created_ref = &mut ruleset_created;
1024
1025 ruleset_created_ref
1027 .set_compatibility(CompatLevel::BestEffort)
1028 .add_rule(PathBeneath::new(
1029 PathFd::new("/usr").unwrap(),
1030 AccessFs::Execute,
1031 ))
1032 .unwrap()
1033 .add_rule(PathBeneath::new(
1034 PathFd::new("/etc").unwrap(),
1035 AccessFs::Execute,
1036 ))
1037 .unwrap();
1038
1039 assert_eq!(
1041 ruleset_created
1042 .set_compatibility(CompatLevel::BestEffort)
1043 .add_rule(PathBeneath::new(
1044 PathFd::new("/tmp").unwrap(),
1045 AccessFs::Execute,
1046 ))
1047 .unwrap()
1048 .add_rule(PathBeneath::new(
1049 PathFd::new("/var").unwrap(),
1050 AccessFs::Execute,
1051 ))
1052 .unwrap()
1053 .restrict_self()
1054 .unwrap(),
1055 RestrictionStatus {
1056 ruleset: RulesetStatus::NotEnforced,
1057 landlock: LandlockStatus::NotImplemented,
1058 no_new_privs: true,
1059 log_same_exec: true,
1060 log_new_exec: false,
1061 log_subdomains: true,
1062 }
1063 );
1064}
1065
1066#[test]
1067fn ruleset_compat_dummy() {
1068 for level in [CompatLevel::BestEffort, CompatLevel::SoftRequirement] {
1069 println!("level: {:?}", level);
1070
1071 let ruleset = Ruleset::from(ABI::Unsupported);
1073 assert_eq!(ruleset.compat.state, CompatState::Init);
1074
1075 let ruleset = ruleset.set_compatibility(level);
1076 assert_eq!(ruleset.compat.state, CompatState::Init);
1077
1078 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1079 assert_eq!(
1080 ruleset.compat.state,
1081 match level {
1082 CompatLevel::BestEffort => CompatState::No,
1083 CompatLevel::SoftRequirement => CompatState::Dummy,
1084 _ => unreachable!(),
1085 }
1086 );
1087
1088 let ruleset_created = ruleset.create().unwrap();
1089 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1092
1093 let ruleset_created = ruleset_created
1094 .add_rule(PathBeneath::new(
1095 PathFd::new("/usr").unwrap(),
1096 AccessFs::Execute,
1097 ))
1098 .unwrap();
1099 assert_eq!(ruleset_created.compat.state, CompatState::Dummy);
1100 }
1101}
1102
1103#[test]
1104fn ruleset_compat_partial() {
1105 let ruleset = Ruleset::from(ABI::V1);
1107 assert_eq!(ruleset.compat.state, CompatState::Init);
1108
1109 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1111 assert_eq!(ruleset.compat.state, CompatState::No);
1112
1113 let ruleset = ruleset.handle_access(AccessFs::Execute).unwrap();
1114 assert_eq!(ruleset.compat.state, CompatState::Partial);
1115
1116 let ruleset = ruleset.handle_access(AccessFs::Refer).unwrap();
1118 assert_eq!(ruleset.compat.state, CompatState::Partial);
1119}
1120
1121#[test]
1122fn ruleset_unsupported() {
1123 assert_eq!(
1124 Ruleset::from(ABI::Unsupported)
1125 .handle_access(AccessFs::Execute)
1127 .unwrap()
1128 .create()
1129 .unwrap()
1130 .restrict_self()
1131 .unwrap(),
1132 RestrictionStatus {
1133 ruleset: RulesetStatus::NotEnforced,
1134 landlock: LandlockStatus::NotImplemented,
1135 no_new_privs: true,
1137 log_same_exec: true,
1138 log_new_exec: false,
1139 log_subdomains: true,
1140 }
1141 );
1142
1143 assert_eq!(
1144 Ruleset::from(ABI::Unsupported)
1145 .set_compatibility(CompatLevel::SoftRequirement)
1147 .handle_access(AccessFs::Execute)
1148 .unwrap()
1149 .create()
1150 .unwrap()
1151 .restrict_self()
1152 .unwrap(),
1153 RestrictionStatus {
1154 ruleset: RulesetStatus::NotEnforced,
1155 landlock: LandlockStatus::NotImplemented,
1156 no_new_privs: true,
1158 log_same_exec: true,
1159 log_new_exec: false,
1160 log_subdomains: true,
1161 }
1162 );
1163
1164 assert!(matches!(
1166 Ruleset::from(ABI::Unsupported)
1167 .set_compatibility(CompatLevel::HardRequirement)
1169 .handle_access(AccessFs::Execute)
1170 .unwrap_err(),
1171 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1172 CompatError::Access(AccessError::Incompatible { .. })
1173 )))
1174 ));
1175
1176 assert!(matches!(
1178 Ruleset::from(ABI::Unsupported)
1179 .set_compatibility(CompatLevel::HardRequirement)
1181 .scope(Scope::Signal)
1182 .unwrap_err(),
1183 RulesetError::Scope(ScopeError::Compat(CompatError::Access(
1184 AccessError::Incompatible { .. }
1185 )))
1186 ));
1187
1188 assert_eq!(
1189 Ruleset::from(ABI::Unsupported)
1190 .handle_access(AccessFs::Execute)
1191 .unwrap()
1192 .create()
1193 .unwrap()
1194 .set_compatibility(CompatLevel::SoftRequirement)
1196 .restrict_self()
1197 .unwrap(),
1198 RestrictionStatus {
1199 ruleset: RulesetStatus::NotEnforced,
1200 landlock: LandlockStatus::NotImplemented,
1201 no_new_privs: true,
1203 log_same_exec: true,
1204 log_new_exec: false,
1205 log_subdomains: true,
1206 }
1207 );
1208
1209 if compat::can_emulate(ABI::V1, ABI::V1, Some(ABI::V2)) {
1211 assert_eq!(
1212 Ruleset::from(ABI::V1)
1213 .handle_access(make_bitflags!(AccessFs::{Execute | Refer}))
1214 .unwrap()
1215 .create()
1216 .unwrap()
1217 .set_compatibility(CompatLevel::SoftRequirement)
1219 .add_rule(PathBeneath::new(PathFd::new("/").unwrap(), AccessFs::Refer))
1220 .unwrap()
1221 .restrict_self()
1222 .unwrap(),
1223 RestrictionStatus {
1224 ruleset: RulesetStatus::NotEnforced,
1225 landlock: LandlockStatus::Available {
1226 effective_abi: ABI::V1,
1227 kernel_abi: None,
1228 },
1229 no_new_privs: true,
1232 log_same_exec: true,
1233 log_new_exec: false,
1234 log_subdomains: true,
1235 }
1236 );
1237 }
1238
1239 assert_eq!(
1240 Ruleset::from(ABI::Unsupported)
1241 .handle_access(AccessFs::Execute)
1242 .unwrap()
1243 .create()
1244 .unwrap()
1245 .no_new_privs(false)
1246 .restrict_self()
1247 .unwrap(),
1248 RestrictionStatus {
1249 ruleset: RulesetStatus::NotEnforced,
1250 landlock: LandlockStatus::NotImplemented,
1251 no_new_privs: false,
1252 log_same_exec: true,
1253 log_new_exec: false,
1254 log_subdomains: true,
1255 }
1256 );
1257
1258 assert!(matches!(
1260 Ruleset::from(ABI::Unsupported)
1261 .handle_access(AccessFs::from_all(ABI::Unsupported))
1263 .unwrap_err(),
1264 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1265 CompatError::Access(AccessError::Empty)
1266 )))
1267 ));
1268
1269 assert!(matches!(
1270 Ruleset::from(ABI::Unsupported)
1271 .create()
1273 .unwrap_err(),
1274 RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess)
1275 ));
1276
1277 assert!(matches!(
1279 Ruleset::from(ABI::V1)
1280 .handle_access(AccessFs::from_all(ABI::Unsupported))
1282 .unwrap_err(),
1283 RulesetError::HandleAccesses(HandleAccessesError::Fs(HandleAccessError::Compat(
1284 CompatError::Access(AccessError::Empty)
1285 )))
1286 ));
1287
1288 assert!(matches!(
1290 Ruleset::from(ABI::Unsupported)
1291 .scope(Scope::from_all(ABI::Unsupported))
1292 .unwrap_err(),
1293 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1294 ));
1295
1296 assert!(matches!(
1298 Ruleset::from(ABI::V1)
1299 .scope(Scope::from_all(ABI::Unsupported))
1300 .unwrap_err(),
1301 RulesetError::Scope(ScopeError::Compat(CompatError::Access(AccessError::Empty)))
1302 ));
1303
1304 let ruleset = Ruleset::from(ABI::V1)
1306 .handle_access(AccessFs::Execute)
1307 .unwrap()
1308 .set_compatibility(CompatLevel::SoftRequirement)
1309 .scope(Scope::Signal)
1310 .unwrap();
1311 assert_eq!(ruleset.requested_scoped, BitFlags::from(Scope::Signal));
1312 assert_eq!(ruleset.actual_scoped, BitFlags::<Scope>::EMPTY);
1313
1314 assert_eq!(
1316 Ruleset::from(ABI::Unsupported)
1317 .handle_access(AccessFs::Execute)
1318 .unwrap()
1319 .create()
1320 .unwrap()
1321 .log_same_exec(false)
1322 .unwrap()
1323 .restrict_self()
1324 .unwrap(),
1325 RestrictionStatus {
1326 ruleset: RulesetStatus::NotEnforced,
1327 landlock: LandlockStatus::NotImplemented,
1328 no_new_privs: true,
1329 log_same_exec: true,
1330 log_new_exec: false,
1331 log_subdomains: true,
1332 }
1333 );
1334
1335 assert!(matches!(
1337 Ruleset::from(ABI::Unsupported)
1338 .handle_access(AccessFs::Execute)
1339 .unwrap()
1340 .create()
1341 .unwrap()
1342 .set_compatibility(CompatLevel::HardRequirement)
1343 .log_new_exec(true)
1344 .unwrap_err(),
1345 RulesetError::RestrictSelfFlags(SyscallFlagError::NotSupported {
1346 flag: RestrictSelfFlag::LogNewExec,
1347 set: true,
1348 })
1349 ));
1350
1351 for handled_access in &[
1353 make_bitflags!(AccessFs::{Execute | WriteFile}),
1354 AccessFs::Execute.into(),
1355 ] {
1356 let ruleset = Ruleset::from(ABI::V1)
1357 .handle_access(*handled_access)
1358 .unwrap();
1359 let ruleset_created = RulesetCreated::new(ruleset, None);
1362 assert!(matches!(
1363 ruleset_created
1364 .add_rule(PathBeneath::new(
1365 PathFd::new("/").unwrap(),
1366 AccessFs::ReadFile
1367 ))
1368 .unwrap_err(),
1369 RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess { .. }))
1370 ));
1371 }
1372}
1373
1374#[test]
1375fn ignore_abi_v2_with_abi_v1() {
1376 assert_eq!(
1379 Ruleset::from(ABI::V1)
1380 .set_compatibility(CompatLevel::HardRequirement)
1381 .handle_access(AccessFs::from_all(ABI::V1))
1382 .unwrap()
1383 .set_compatibility(CompatLevel::SoftRequirement)
1384 .handle_access(AccessFs::Refer)
1386 .unwrap()
1387 .create()
1388 .unwrap()
1389 .add_rule(PathBeneath::new(
1390 PathFd::new("/tmp").unwrap(),
1391 AccessFs::from_all(ABI::V2)
1392 ))
1393 .unwrap()
1394 .add_rule(PathBeneath::new(
1395 PathFd::new("/usr").unwrap(),
1396 make_bitflags!(AccessFs::{ReadFile | ReadDir})
1397 ))
1398 .unwrap()
1399 .restrict_self()
1400 .unwrap(),
1401 RestrictionStatus {
1402 ruleset: RulesetStatus::NotEnforced,
1403 landlock: LandlockStatus::Available {
1404 effective_abi: ABI::V1,
1405 kernel_abi: None,
1406 },
1407 no_new_privs: true,
1408 log_same_exec: true,
1409 log_new_exec: false,
1410 log_subdomains: true,
1411 }
1412 );
1413}
1414
1415#[test]
1416fn unsupported_handled_access() {
1417 assert!(matches!(
1418 Ruleset::from(ABI::V3)
1419 .handle_access(AccessNet::from_all(ABI::V3))
1420 .unwrap_err(),
1421 RulesetError::HandleAccesses(HandleAccessesError::Net(HandleAccessError::Compat(
1422 CompatError::Access(AccessError::Empty)
1423 )))
1424 ));
1425}
1426
1427#[test]
1428fn unsupported_handled_access_errno() {
1429 assert_eq!(
1430 Errno::from(
1431 Ruleset::from(ABI::V3)
1432 .handle_access(AccessNet::from_all(ABI::V3))
1433 .unwrap_err()
1434 ),
1435 Errno::new(libc::EINVAL)
1436 );
1437}