Skip to main content

landlock/
errors.rs

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