Coverage Report

Created: 2026-08-05 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}