use crate::access_vector_cache::{Fixed, Locked, Query, DEFAULT_SHARED_SIZE};
use crate::policy::{AccessVectorComputer, SELINUX_AVD_FLAGS_PERMISSIVE};
use crate::security_server::SecurityServer;
use crate::{ClassPermission, FileClass, Permission, SecurityId};
#[cfg(target_os = "fuchsia")]
use fuchsia_inspect_contrib::profile_duration;
use std::sync::Weak;
#[derive(Clone, Debug, PartialEq)]
pub struct PermissionCheckResult {
pub permit: bool,
pub audit: bool,
}
pub struct PermissionCheck<'a> {
security_server: &'a SecurityServer,
access_vector_cache: &'a Locked<Fixed<Weak<SecurityServer>, DEFAULT_SHARED_SIZE>>,
}
impl<'a> PermissionCheck<'a> {
pub(crate) fn new(
security_server: &'a SecurityServer,
access_vector_cache: &'a Locked<Fixed<Weak<SecurityServer>, DEFAULT_SHARED_SIZE>>,
) -> Self {
Self { security_server, access_vector_cache }
}
pub fn has_permission<P: ClassPermission + Into<Permission> + Clone + 'static>(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
permission: P,
) -> PermissionCheckResult {
has_permission(
self.security_server.is_enforcing(),
self.access_vector_cache,
self.security_server,
source_sid,
target_sid,
permission,
)
}
pub fn security_server(&self) -> &SecurityServer {
self.security_server
}
pub fn compute_new_file_sid(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
self.access_vector_cache.compute_new_file_sid(source_sid, target_sid, file_class)
}
}
fn has_permission<P: ClassPermission + Into<Permission> + Clone + 'static>(
is_enforcing: bool,
query: &impl Query,
access_vector_computer: &impl AccessVectorComputer,
source_sid: SecurityId,
target_sid: SecurityId,
permission: P,
) -> PermissionCheckResult {
#[cfg(target_os = "fuchsia")]
profile_duration!("libselinux.check_permission");
let target_class = permission.class();
let decision = query.query(source_sid, target_sid, target_class.into());
let mut result = if let Some(permission_access_vector) =
access_vector_computer.access_vector_from_permissions(&[permission])
{
let permit = permission_access_vector & decision.allow == permission_access_vector;
let audit = if permit {
permission_access_vector & decision.auditallow == permission_access_vector
} else {
permission_access_vector & decision.auditdeny == permission_access_vector
};
PermissionCheckResult { permit, audit }
} else {
PermissionCheckResult { permit: false, audit: true }
};
if !result.permit {
if !is_enforcing {
result.permit = true;
} else if decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
result.permit = true;
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::access_vector_cache::DenyAll;
use crate::policy::testing::{ACCESS_VECTOR_0001, ACCESS_VECTOR_0010};
use crate::policy::{AccessDecision, AccessVector};
use crate::{AbstractObjectClass, ProcessPermission};
use std::any::Any;
use std::num::NonZeroU32;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::LazyLock;
static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
fn unique_sid() -> SecurityId {
static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
}
fn access_vector_from_permission<P: ClassPermission + Into<Permission> + 'static>(
permission: P,
) -> AccessVector {
let any = &permission as &dyn Any;
let permission_ref = match any.downcast_ref::<ProcessPermission>() {
Some(permission_ref) => permission_ref,
None => return AccessVector::NONE,
};
match permission_ref {
ProcessPermission::Fork => ACCESS_VECTOR_0001,
ProcessPermission::Transition => ACCESS_VECTOR_0010,
_ => AccessVector::NONE,
}
}
fn access_vector_from_permissions<
'a,
P: ClassPermission + Into<Permission> + Clone + 'static,
>(
permissions: &[P],
) -> AccessVector {
let mut access_vector = AccessVector::NONE;
for permission in permissions {
access_vector |= access_vector_from_permission(permission.clone());
}
access_vector
}
#[derive(Default)]
pub struct DenyAllPermissions(DenyAll);
impl Query for DenyAllPermissions {
fn query(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
self.0.query(source_sid, target_sid, target_class)
}
fn compute_new_file_sid(
&self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
unreachable!();
}
}
impl AccessVectorComputer for DenyAllPermissions {
fn access_vector_from_permissions<
P: ClassPermission + Into<Permission> + Clone + 'static,
>(
&self,
permissions: &[P],
) -> Option<AccessVector> {
Some(access_vector_from_permissions(permissions))
}
}
#[derive(Default)]
struct AllowAllPermissions;
impl Query for AllowAllPermissions {
fn query(
&self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_target_class: AbstractObjectClass,
) -> AccessDecision {
AccessDecision::allow(AccessVector::ALL)
}
fn compute_new_file_sid(
&self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
unreachable!();
}
}
impl AccessVectorComputer for AllowAllPermissions {
fn access_vector_from_permissions<
P: ClassPermission + Into<Permission> + Clone + 'static,
>(
&self,
permissions: &[P],
) -> Option<AccessVector> {
Some(access_vector_from_permissions(permissions))
}
}
#[test]
fn has_permission_both() {
let deny_all: DenyAllPermissions = Default::default();
let allow_all: AllowAllPermissions = Default::default();
let permissions = [ProcessPermission::Fork, ProcessPermission::Transition];
for permission in &permissions {
assert_eq!(
PermissionCheckResult { permit: false, audit: true },
has_permission(
true,
&deny_all,
&deny_all,
*A_TEST_SID,
*A_TEST_SID,
permission.clone()
)
);
assert_eq!(
PermissionCheckResult { permit: true, audit: false },
has_permission(
true,
&allow_all,
&allow_all,
*A_TEST_SID,
*A_TEST_SID,
permission.clone()
)
);
}
}
}