landlock/
access.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use crate::{
4    private, AccessError, AddRuleError, AddRulesError, BitFlags, CompatError, CompatResult,
5    HandleAccessError, HandleAccessesError, Ruleset, TailoredCompatLevel, TryCompat, ABI,
6};
7use enumflags2::BitFlag;
8
9#[cfg(test)]
10use crate::{make_bitflags, AccessFs, CompatLevel, CompatState, Compatibility};
11
12pub 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.
18pub trait HandledAccess: Access {}
19
20pub 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.
38fn full_negation<T>(flags: BitFlags<T>) -> BitFlags<T>
39where
40    T: Access,
41{
42    unsafe { BitFlags::<T>::from_bits_unchecked(!flags.bits()) }
43}
44
45#[test]
46fn 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
54impl<A> TailoredCompatLevel for BitFlags<A> where A: Access {}
55
56impl<A> TryCompat<A> for BitFlags<A>
57where
58    A: Access,
59{
60    fn try_compat_inner(&mut self, abi: ABI) -> Result<CompatResult<A>, CompatError<A>> {
61        if self.is_empty() {
62            // Empty access-rights would result to a runtime error.
63            Err(AccessError::Empty.into())
64        } 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            Err(AccessError::Unknown {
68                access: *self,
69                unknown: *self & full_negation(Self::all()),
70            }
71            .into())
72        } else {
73            let compat = *self & A::from_all(abi);
74            let ret = if compat.is_empty() {
75                Ok(CompatResult::No(
76                    AccessError::Incompatible { access: *self }.into(),
77                ))
78            } else if compat != *self {
79                let error = AccessError::PartiallyCompatible {
80                    access: *self,
81                    incompatible: *self & full_negation(compat),
82                }
83                .into();
84                Ok(CompatResult::Partial(error))
85            } else {
86                Ok(CompatResult::Full)
87            };
88            *self = compat;
89            ret
90        }
91    }
92}
93
94#[test]
95fn 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}