syd 3.52.0

rock-solid application kernel
Documentation
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::landlock::{
    compat::TryCompat, uapi, Access, AddRuleError, AddRulesError, HandleAccessError,
    HandleAccessesError, PrivateAccess, Ruleset, TailoredCompatLevel, ABI,
};

crate::landlock::access::bitflags_type! {
    /// Scope flags
    ///
    /// Each variant of `Scope` is an [scope
    /// flag](https://docs.kernel.org/userspace-api/landlock.html#scope-flags).
    ///
    /// # Example
    ///
    /// ```
    /// use syd::landlock::{ABI, Access, Scope};
    ///
    /// let scope_set = Scope::AbstractUnixSocket | Scope::Signal;
    ///
    /// let scope_v6 = Scope::from_all(ABI::V6);
    ///
    /// assert_eq!(scope_set, scope_v6);
    /// ```
    pub struct Scope: u64 {
        /// Restrict a sandboxed process from connecting to an abstract UNIX socket created by a
        /// process outside the related Landlock domain
        const AbstractUnixSocket = uapi::LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET as u64;
        /// Restrict a sandboxed process from sending a signal to another process outside the
        /// domain.
        const Signal = uapi::LANDLOCK_SCOPE_SIGNAL as u64;
    }
}

impl TailoredCompatLevel for Scope {}

/// # Warning
///
/// If `ABI <= ABI::V5`, `Scope::from_all()` returns an empty `Scope`, which
/// makes `Ruleset::handle_access(Scope::from_all(ABI::V5))` return an error.
impl Access for Scope {
    fn from_all(abi: ABI) -> Self {
        match abi {
            ABI::Unsupported | ABI::V1 | ABI::V2 | ABI::V3 | ABI::V4 | ABI::V5 => Scope::EMPTY,
            ABI::V6 | ABI::V7 | ABI::V8 => Scope::AbstractUnixSocket | Scope::Signal,
        }
    }
}

impl PrivateAccess for Scope {
    fn is_empty(self) -> bool {
        Scope::is_empty(&self)
    }

    fn ruleset_handle_access(
        ruleset: &mut Ruleset,
        scope: Self,
    ) -> Result<(), HandleAccessesError> {
        // We need to record the requested scopes for PrivateRule::check_consistency().
        ruleset.requested_scoped |= scope;
        if let Some(a) = scope
            .try_compat(
                ruleset.compat.abi(),
                ruleset.compat.level,
                &mut ruleset.compat.state,
            )
            .map_err(HandleAccessError::Compat)?
        {
            ruleset.actual_scoped |= a;
        }
        Ok(())
    }

    fn into_add_rules_error(error: AddRuleError<Self>) -> AddRulesError {
        AddRulesError::Scope(error)
    }

    fn into_handle_accesses_error(error: HandleAccessError<Self>) -> HandleAccessesError {
        HandleAccessesError::Scope(error)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::landlock::*;

    #[test]
    fn test_scope_bitflags_1() {
        let unix = Scope::AbstractUnixSocket;
        let signal = Scope::Signal;
        let both = unix | signal;
        assert!(both.contains(unix));
        assert!(both.contains(signal));
        assert_ne!(unix, signal);
    }

    #[test]
    fn test_scope_bitflags_2() {
        assert!(!Scope::EMPTY.contains(Scope::AbstractUnixSocket));
        assert!(!Scope::EMPTY.contains(Scope::Signal));
    }

    #[test]
    fn test_scope_from_all_1() {
        assert_eq!(Scope::from_all(ABI::Unsupported), Scope::EMPTY);
    }

    #[test]
    fn test_scope_from_all_2() {
        assert_eq!(Scope::from_all(ABI::V1), Scope::EMPTY);
    }

    #[test]
    fn test_scope_from_all_3() {
        assert_eq!(Scope::from_all(ABI::V2), Scope::EMPTY);
    }

    #[test]
    fn test_scope_from_all_4() {
        assert_eq!(Scope::from_all(ABI::V3), Scope::EMPTY);
    }

    #[test]
    fn test_scope_from_all_5() {
        assert_eq!(Scope::from_all(ABI::V4), Scope::EMPTY);
    }

    #[test]
    fn test_scope_from_all_6() {
        assert_eq!(Scope::from_all(ABI::V5), Scope::EMPTY);
    }

    #[test]
    fn test_scope_from_all_7() {
        let expected = Scope::AbstractUnixSocket | Scope::Signal;
        assert_eq!(Scope::from_all(ABI::V6), expected);
    }

    #[test]
    fn test_scope_from_all_8() {
        let expected = Scope::AbstractUnixSocket | Scope::Signal;
        assert_eq!(Scope::from_all(ABI::V7), expected);
    }

    #[test]
    fn test_is_empty_1() {
        assert!(PrivateAccess::is_empty(Scope::EMPTY));
    }

    #[test]
    fn test_is_empty_2() {
        assert!(!PrivateAccess::is_empty(Scope::Signal));
    }

    #[test]
    fn test_is_empty_3() {
        assert!(!PrivateAccess::is_empty(
            Scope::AbstractUnixSocket | Scope::Signal
        ));
    }

    #[test]
    fn test_into_add_rules_error_1() {
        let err = AddRuleError::UnhandledAccess {
            access: Scope::Signal,
            incompatible: Scope::Signal,
        };
        assert!(matches!(
            Scope::into_add_rules_error(err),
            AddRulesError::Scope(AddRuleError::UnhandledAccess { .. })
        ));
    }

    #[test]
    fn test_into_handle_accesses_error_1() {
        let err = HandleAccessError::Compat(CompatError::Access(AccessError::Empty));
        assert!(matches!(
            Scope::into_handle_accesses_error(err),
            HandleAccessesError::Scope(HandleAccessError::Compat(CompatError::Access(
                AccessError::Empty
            )))
        ));
    }

    #[test]
    fn test_ruleset_handle_access_1() {
        let scope = Scope::AbstractUnixSocket | Scope::Signal;
        let ruleset = Ruleset::from(ABI::V6).handle_access(scope).unwrap();
        assert_eq!(ruleset.requested_scoped, scope);
        assert_eq!(ruleset.actual_scoped, scope);
    }

    #[test]
    fn test_ruleset_handle_access_2() {
        let scope = Scope::AbstractUnixSocket | Scope::Signal;
        let ruleset = Ruleset::from(ABI::V5).handle_access(scope).unwrap();
        assert_eq!(ruleset.requested_scoped, scope);
        assert_eq!(ruleset.actual_scoped, Scope::EMPTY);
    }
}