/rust/registry/src/index.crates.io-1949cf8c6b5b557f/landlock-0.4.7/src/access.rs
Line | Count | Source |
1 | | // SPDX-License-Identifier: Apache-2.0 OR MIT |
2 | | |
3 | | use crate::{ |
4 | | private, AccessError, AddRuleError, AddRulesError, BitFlags, CompatError, CompatResult, |
5 | | HandleAccessError, HandleAccessesError, Ruleset, TailoredCompatLevel, TryCompat, ABI, |
6 | | }; |
7 | | use enumflags2::BitFlag; |
8 | | |
9 | | #[cfg(test)] |
10 | | use crate::{make_bitflags, AccessFs, CompatLevel, CompatState, Compatibility}; |
11 | | |
12 | | pub trait Access: BitFlag + private::Sealed { |
13 | | /// Gets the access rights defined by a specific [`ABI`]. |
14 | | fn from_all(abi: ABI) -> BitFlags<Self>; |
15 | | } |
16 | | |
17 | | // This HandledAccess trait is useful to document the API. |
18 | | pub trait HandledAccess: Access {} |
19 | | |
20 | | pub trait PrivateHandledAccess: HandledAccess { |
21 | | fn ruleset_handle_access( |
22 | | ruleset: &mut Ruleset, |
23 | | access: BitFlags<Self>, |
24 | | ) -> Result<(), HandleAccessesError> |
25 | | where |
26 | | Self: Access; |
27 | | |
28 | | fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError |
29 | | where |
30 | | Self: Access; |
31 | | |
32 | | fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError |
33 | | where |
34 | | Self: Access; |
35 | | } |
36 | | |
37 | | // Creates an illegal/overflowed BitFlags<T> with all its bits toggled, including undefined ones. |
38 | 0 | fn full_negation<T>(flags: BitFlags<T>) -> BitFlags<T> |
39 | 0 | where |
40 | 0 | T: Access, |
41 | | { |
42 | 0 | unsafe { BitFlags::<T>::from_bits_unchecked(!flags.bits()) } |
43 | 0 | } Unexecuted instantiation: landlock::access::full_negation::<landlock::fs::AccessFs> Unexecuted instantiation: landlock::access::full_negation::<landlock::net::AccessNet> |
44 | | |
45 | | #[test] |
46 | | fn bit_flags_full_negation() { |
47 | | let scoped_negation = !BitFlags::<AccessFs>::all(); |
48 | | assert_eq!(scoped_negation, BitFlags::<AccessFs>::empty()); |
49 | | // !BitFlags::<AccessFs>::all() could be equal to full_negation(BitFlags::<AccessFs>::all())) |
50 | | // if all the 64-bits would be used, which is not currently the case. |
51 | | assert_ne!(scoped_negation, full_negation(BitFlags::<AccessFs>::all())); |
52 | | } |
53 | | |
54 | | impl<A> TailoredCompatLevel for BitFlags<A> where A: Access {} |
55 | | |
56 | | impl<A> TryCompat<A> for BitFlags<A> |
57 | | where |
58 | | A: Access, |
59 | | { |
60 | 0 | fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>> { |
61 | 0 | if self.is_empty() { |
62 | | // Empty access-rights would result to a runtime error. |
63 | 0 | Err(AccessError::Empty.into()) |
64 | 0 | } else if !Self::all().contains(*self) { |
65 | | // Unknown access-rights (at build time) would result to a runtime error. |
66 | | // This can only be reached by using the unsafe BitFlags::from_bits_unchecked(). |
67 | 0 | Err(AccessError::Unknown { |
68 | 0 | access: *self, |
69 | 0 | unknown: *self & full_negation(Self::all()), |
70 | 0 | } |
71 | 0 | .into()) |
72 | | } else { |
73 | 0 | let compat = *self & A::from_all(abi); |
74 | 0 | let ret = if compat.is_empty() { |
75 | 0 | Ok(CompatResult::No( |
76 | 0 | AccessError::Incompatible { access: *self }.into(), |
77 | 0 | )) |
78 | 0 | } else if compat != *self { |
79 | 0 | let error = AccessError::PartiallyCompatible { |
80 | 0 | access: *self, |
81 | 0 | incompatible: *self & full_negation(compat), |
82 | 0 | } |
83 | 0 | .into(); |
84 | 0 | Ok(CompatResult::Partial(error)) |
85 | | } else { |
86 | 0 | Ok(CompatResult::Full) |
87 | | }; |
88 | 0 | *self = compat; |
89 | 0 | ret |
90 | | } |
91 | 0 | } Unexecuted instantiation: <enumflags2::BitFlags<landlock::fs::AccessFs, u64> as landlock::compat::TryCompat<landlock::fs::AccessFs>>::try_compat_inner Unexecuted instantiation: <enumflags2::BitFlags<landlock::net::AccessNet, u64> as landlock::compat::TryCompat<landlock::net::AccessNet>>::try_compat_inner |
92 | | } |
93 | | |
94 | | #[test] |
95 | | fn compat_bit_flags() { |
96 | | use crate::ABI; |
97 | | |
98 | | let mut compat: Compatibility = ABI::V1.into(); |
99 | | assert!(compat.state == CompatState::Init); |
100 | | |
101 | | let ro_access = make_bitflags!(AccessFs::{Execute | ReadFile | ReadDir}); |
102 | | assert_eq!( |
103 | | ro_access, |
104 | | ro_access |
105 | | .try_compat(compat.abi(), compat.level, &mut compat.state) |
106 | | .unwrap() |
107 | | .unwrap() |
108 | | ); |
109 | | assert!(compat.state == CompatState::Full); |
110 | | |
111 | | let empty_access = BitFlags::<AccessFs>::empty(); |
112 | | assert!(matches!( |
113 | | empty_access |
114 | | .try_compat(compat.abi(), compat.level, &mut compat.state) |
115 | | .unwrap_err(), |
116 | | CompatError::Access(AccessError::Empty) |
117 | | )); |
118 | | |
119 | | let all_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked(1 << 63) }; |
120 | | assert!(matches!( |
121 | | all_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(), |
122 | | CompatError::Access(AccessError::Unknown { access, unknown }) if access == all_unknown_access && unknown == all_unknown_access |
123 | | )); |
124 | | // An error makes the state final. |
125 | | assert!(compat.state == CompatState::Dummy); |
126 | | |
127 | | let some_unknown_access = unsafe { BitFlags::<AccessFs>::from_bits_unchecked((1 << 63) | 1) }; |
128 | | assert!(matches!( |
129 | | some_unknown_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(), |
130 | | CompatError::Access(AccessError::Unknown { access, unknown }) if access == some_unknown_access && unknown == all_unknown_access |
131 | | )); |
132 | | assert!(compat.state == CompatState::Dummy); |
133 | | |
134 | | compat = ABI::Unsupported.into(); |
135 | | |
136 | | // Tests that the ruleset is marked as unsupported. |
137 | | assert!(compat.state == CompatState::Init); |
138 | | |
139 | | // Access-rights are valid (but ignored) when they are not required for the current ABI. |
140 | | assert_eq!( |
141 | | None, |
142 | | ro_access |
143 | | .try_compat(compat.abi(), compat.level, &mut compat.state) |
144 | | .unwrap() |
145 | | ); |
146 | | |
147 | | assert!(compat.state == CompatState::No); |
148 | | |
149 | | // Access-rights are not valid when they are required for the current ABI. |
150 | | compat.level = Some(CompatLevel::HardRequirement); |
151 | | assert!(matches!( |
152 | | ro_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(), |
153 | | CompatError::Access(AccessError::Incompatible { access }) if access == ro_access |
154 | | )); |
155 | | |
156 | | compat = ABI::V1.into(); |
157 | | |
158 | | // Tests that the ruleset is marked as the unknown compatibility state. |
159 | | assert!(compat.state == CompatState::Init); |
160 | | |
161 | | // Access-rights are valid (but ignored) when they are not required for the current ABI. |
162 | | assert_eq!( |
163 | | ro_access, |
164 | | ro_access |
165 | | .try_compat(compat.abi(), compat.level, &mut compat.state) |
166 | | .unwrap() |
167 | | .unwrap() |
168 | | ); |
169 | | |
170 | | // Tests that the ruleset is in an unsupported state, which is important to be able to still |
171 | | // enforce no_new_privs. |
172 | | assert!(compat.state == CompatState::Full); |
173 | | |
174 | | let v2_access = ro_access | AccessFs::Refer; |
175 | | |
176 | | // Access-rights are not valid when they are required for the current ABI. |
177 | | compat.level = Some(CompatLevel::HardRequirement); |
178 | | assert!(matches!( |
179 | | v2_access.try_compat(compat.abi(), compat.level, &mut compat.state).unwrap_err(), |
180 | | CompatError::Access(AccessError::PartiallyCompatible { access, incompatible }) |
181 | | if access == v2_access && incompatible == AccessFs::Refer |
182 | | )); |
183 | | } |