landlock/
errors.rs

1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3use crate::{Access, AccessFs, AccessNet, BitFlags, HandledAccess, PrivateHandledAccess, Scope};
4use libc::c_int;
5use std::io;
6use std::path::PathBuf;
7use thiserror::Error;
8
9/// Maps to all errors that can be returned by a ruleset action.
10#[derive(Debug, Error)]
11#[non_exhaustive]
12pub enum RulesetError {
13    #[error(transparent)]
14    HandleAccesses(#[from] HandleAccessesError),
15    #[error(transparent)]
16    CreateRuleset(#[from] CreateRulesetError),
17    #[error(transparent)]
18    AddRules(#[from] AddRulesError),
19    #[error(transparent)]
20    RestrictSelf(#[from] RestrictSelfError),
21    #[error(transparent)]
22    Scope(#[from] ScopeError),
23}
24
25#[test]
26fn ruleset_error_breaking_change() {
27    use crate::*;
28
29    // Generics are part of the API and modifying them can lead to a breaking change.
30    let _: RulesetError = RulesetError::HandleAccesses(HandleAccessesError::Fs(
31        HandleAccessError::Compat(CompatError::Access(AccessError::Empty)),
32    ));
33}
34
35/// Identifies errors when updating the ruleset's handled access-rights.
36#[derive(Debug, Error)]
37#[non_exhaustive]
38pub enum HandleAccessError<T>
39where
40    T: HandledAccess,
41{
42    #[error(transparent)]
43    Compat(#[from] CompatError<T>),
44}
45
46/// Identifies errors when updating the ruleset's scopes.
47#[derive(Debug, Error)]
48#[non_exhaustive]
49pub enum ScopeError {
50    #[error(transparent)]
51    Compat(#[from] CompatError<Scope>),
52}
53
54#[derive(Debug, Error)]
55#[non_exhaustive]
56pub enum HandleAccessesError {
57    #[error(transparent)]
58    Fs(HandleAccessError<AccessFs>),
59    #[error(transparent)]
60    Net(HandleAccessError<AccessNet>),
61}
62
63// Generically implement for all the handled access implementations rather than for the cases
64// listed in HandleAccessesError (with #[from]).
65impl<A> From<HandleAccessError<A>> for HandleAccessesError
66where
67    A: PrivateHandledAccess,
68{
69    fn from(error: HandleAccessError<A>) -> Self {
70        A::into_handle_accesses_error(error)
71    }
72}
73
74/// Identifies errors when creating a ruleset.
75#[derive(Debug, Error)]
76#[non_exhaustive]
77pub enum CreateRulesetError {
78    /// The `landlock_create_ruleset()` system call failed.
79    #[error("failed to create a ruleset: {source}")]
80    #[non_exhaustive]
81    CreateRulesetCall { source: io::Error },
82    /// Missing call to [`RulesetAttr::handle_access()`](crate::RulesetAttr::handle_access)
83    /// or [`RulesetAttr::scope()`](crate::RulesetAttr::scope).
84    #[error("missing access")]
85    MissingHandledAccess,
86}
87
88/// Identifies errors when adding a rule to a ruleset.
89#[derive(Debug, Error)]
90#[non_exhaustive]
91pub enum AddRuleError<T>
92where
93    T: HandledAccess,
94{
95    /// The `landlock_add_rule()` system call failed.
96    #[error("failed to add a rule: {source}")]
97    #[non_exhaustive]
98    AddRuleCall { source: io::Error },
99    /// The rule's access-rights are not all handled by the (requested) ruleset access-rights.
100    #[error("access-rights not handled by the ruleset: {incompatible:?}")]
101    UnhandledAccess {
102        access: BitFlags<T>,
103        incompatible: BitFlags<T>,
104    },
105    #[error(transparent)]
106    Compat(#[from] CompatError<T>),
107}
108
109// Generically implement for all the handled access implementations rather than for the cases listed
110// in AddRulesError (with #[from]).
111impl<A> From<AddRuleError<A>> for AddRulesError
112where
113    A: PrivateHandledAccess,
114{
115    fn from(error: AddRuleError<A>) -> Self {
116        A::into_add_rules_error(error)
117    }
118}
119
120/// Identifies errors when adding rules to a ruleset thanks to an iterator returning
121/// Result<Rule, E> items.
122#[derive(Debug, Error)]
123#[non_exhaustive]
124pub enum AddRulesError {
125    #[error(transparent)]
126    Fs(AddRuleError<AccessFs>),
127    #[error(transparent)]
128    Net(AddRuleError<AccessNet>),
129}
130
131#[derive(Debug, Error)]
132#[non_exhaustive]
133pub enum CompatError<T>
134where
135    T: Access,
136{
137    #[error(transparent)]
138    PathBeneath(#[from] PathBeneathError),
139    #[error(transparent)]
140    Access(#[from] AccessError<T>),
141}
142
143#[derive(Debug, Error)]
144#[non_exhaustive]
145pub enum PathBeneathError {
146    /// To check that access-rights are consistent with a file descriptor, a call to
147    /// [`RulesetCreatedAttr::add_rule()`](crate::RulesetCreatedAttr::add_rule)
148    /// looks at the file type with an `fstat()` system call.
149    #[error("failed to check file descriptor type: {source}")]
150    #[non_exhaustive]
151    StatCall { source: io::Error },
152    /// This error is returned by
153    /// [`RulesetCreatedAttr::add_rule()`](crate::RulesetCreatedAttr::add_rule)
154    /// if the related PathBeneath object is not set to best-effort,
155    /// and if its allowed access-rights contain directory-only ones
156    /// whereas the file descriptor doesn't point to a directory.
157    #[error("incompatible directory-only access-rights: {incompatible:?}")]
158    DirectoryAccess {
159        access: BitFlags<AccessFs>,
160        incompatible: BitFlags<AccessFs>,
161    },
162}
163
164#[derive(Debug, Error)]
165// Exhaustive enum
166pub enum AccessError<T>
167where
168    T: Access,
169{
170    /// The access-rights set is empty, which doesn't make sense and would be rejected by the
171    /// kernel.
172    #[error("empty access-right")]
173    Empty,
174    /// The access-rights set was forged with the unsafe `BitFlags::from_bits_unchecked()` and it
175    /// contains unknown bits.
176    #[error("unknown access-rights (at build time): {unknown:?}")]
177    Unknown {
178        access: BitFlags<T>,
179        unknown: BitFlags<T>,
180    },
181    /// The best-effort approach was (deliberately) disabled and the requested access-rights are
182    /// fully incompatible with the running kernel.
183    #[error("fully incompatible access-rights: {access:?}")]
184    Incompatible { access: BitFlags<T> },
185    /// The best-effort approach was (deliberately) disabled and the requested access-rights are
186    /// partially incompatible with the running kernel.
187    #[error("partially incompatible access-rights: {incompatible:?}")]
188    PartiallyCompatible {
189        access: BitFlags<T>,
190        incompatible: BitFlags<T>,
191    },
192}
193
194#[derive(Debug, Error)]
195#[non_exhaustive]
196pub enum RestrictSelfError {
197    /// The `prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)` system call failed.
198    #[error("failed to set no_new_privs: {source}")]
199    #[non_exhaustive]
200    SetNoNewPrivsCall { source: io::Error },
201    /// The `landlock_restrict_self() `system call failed.
202    #[error("failed to restrict the calling thread: {source}")]
203    #[non_exhaustive]
204    RestrictSelfCall { source: io::Error },
205}
206
207#[derive(Debug, Error)]
208#[non_exhaustive]
209pub enum PathFdError {
210    /// The `open()` system call failed.
211    #[error("failed to open \"{path}\": {source}")]
212    #[non_exhaustive]
213    OpenCall { source: io::Error, path: PathBuf },
214}
215
216#[cfg(test)]
217#[derive(Debug, Error)]
218pub(crate) enum TestRulesetError {
219    #[error(transparent)]
220    Ruleset(#[from] RulesetError),
221    #[error(transparent)]
222    PathFd(#[from] PathFdError),
223    #[error(transparent)]
224    File(#[from] std::io::Error),
225}
226
227/// Get the underlying errno value.
228///
229/// This helper is useful for FFI to easily translate a Landlock error into an
230/// errno value.
231#[derive(Debug, PartialEq, Eq)]
232pub struct Errno(c_int);
233
234impl Errno {
235    pub fn new(value: c_int) -> Self {
236        Self(value)
237    }
238}
239
240impl<T> From<T> for Errno
241where
242    T: std::error::Error,
243{
244    fn from(error: T) -> Self {
245        let default = libc::EINVAL;
246        if let Some(e) = error.source() {
247            if let Some(e) = e.downcast_ref::<std::io::Error>() {
248                return Errno(e.raw_os_error().unwrap_or(default));
249            }
250        }
251        Errno(default)
252    }
253}
254
255impl std::ops::Deref for Errno {
256    type Target = c_int;
257
258    fn deref(&self) -> &Self::Target {
259        &self.0
260    }
261}
262
263#[cfg(test)]
264fn _test_ruleset_errno(expected_errno: c_int) {
265    use std::io::Error;
266
267    let handle_access_err = RulesetError::HandleAccesses(HandleAccessesError::Fs(
268        HandleAccessError::Compat(CompatError::Access(AccessError::Empty)),
269    ));
270    assert_eq!(*Errno::from(handle_access_err), libc::EINVAL);
271
272    let create_ruleset_err = RulesetError::CreateRuleset(CreateRulesetError::CreateRulesetCall {
273        source: Error::from_raw_os_error(expected_errno),
274    });
275    assert_eq!(*Errno::from(create_ruleset_err), expected_errno);
276
277    let add_rules_fs_err = RulesetError::AddRules(AddRulesError::Fs(AddRuleError::AddRuleCall {
278        source: Error::from_raw_os_error(expected_errno),
279    }));
280    assert_eq!(*Errno::from(add_rules_fs_err), expected_errno);
281
282    let add_rules_net_err = RulesetError::AddRules(AddRulesError::Net(AddRuleError::AddRuleCall {
283        source: Error::from_raw_os_error(expected_errno),
284    }));
285    assert_eq!(*Errno::from(add_rules_net_err), expected_errno);
286
287    let add_rules_other_err =
288        RulesetError::AddRules(AddRulesError::Fs(AddRuleError::UnhandledAccess {
289            access: AccessFs::Execute.into(),
290            incompatible: BitFlags::<AccessFs>::EMPTY,
291        }));
292    assert_eq!(*Errno::from(add_rules_other_err), libc::EINVAL);
293
294    let restrict_self_err = RulesetError::RestrictSelf(RestrictSelfError::RestrictSelfCall {
295        source: Error::from_raw_os_error(expected_errno),
296    });
297    assert_eq!(*Errno::from(restrict_self_err), expected_errno);
298
299    let set_no_new_privs_err = RulesetError::RestrictSelf(RestrictSelfError::SetNoNewPrivsCall {
300        source: Error::from_raw_os_error(expected_errno),
301    });
302    assert_eq!(*Errno::from(set_no_new_privs_err), expected_errno);
303
304    let create_ruleset_missing_err =
305        RulesetError::CreateRuleset(CreateRulesetError::MissingHandledAccess);
306    assert_eq!(*Errno::from(create_ruleset_missing_err), libc::EINVAL);
307}
308
309#[test]
310fn test_ruleset_errno() {
311    _test_ruleset_errno(libc::EACCES);
312    _test_ruleset_errno(libc::EIO);
313}