landlock/
errors.rs

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