selinux/
permission_check.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::access_vector_cache::{FifoQueryCache, Locked, Query};
6use crate::policy::{AccessVector, AccessVectorComputer, SELINUX_AVD_FLAGS_PERMISSIVE};
7use crate::security_server::SecurityServer;
8use crate::{ClassPermission, FsNodeClass, NullessByteStr, Permission, SecurityId};
9
10#[cfg(target_os = "fuchsia")]
11use fuchsia_inspect_contrib::profile_duration;
12
13use std::num::NonZeroU64;
14use std::sync::Weak;
15
16/// Describes the result of a permission lookup between two Security Contexts.
17#[derive(Clone, Debug, PartialEq)]
18pub struct PermissionCheckResult {
19    /// True if the specified permissions should be permitted.
20    pub permit: bool,
21
22    /// True if details of the check should be audit logged. Audit logs are by default only output
23    /// when the policy defines that the permissions should be denied (whether or not the check is
24    /// "permissive"), but may be suppressed for some denials ("dontaudit"), or for some allowed
25    /// permissions ("auditallow").
26    pub audit: bool,
27
28    /// If the `AccessDecision` indicates that permission denials should not be enforced then `permit`
29    /// will be true, and this field will hold the Id of the bug to reference in audit logging.
30    pub todo_bug: Option<NonZeroU64>,
31}
32
33/// Implements the `has_permission()` API, based on supplied `Query` and `AccessVectorComputer`
34/// implementations.
35// TODO: https://fxbug.dev/362699811 - Revise the traits to avoid direct dependencies on `SecurityServer`.
36pub struct PermissionCheck<'a> {
37    security_server: &'a SecurityServer,
38    access_vector_cache: &'a Locked<FifoQueryCache<Weak<SecurityServer>>>,
39}
40
41impl<'a> PermissionCheck<'a> {
42    pub(crate) fn new(
43        security_server: &'a SecurityServer,
44        access_vector_cache: &'a Locked<FifoQueryCache<Weak<SecurityServer>>>,
45    ) -> Self {
46        Self { security_server, access_vector_cache }
47    }
48
49    /// Returns whether the `source_sid` has the specified `permission` on `target_sid`.
50    /// The result indicates both whether `permission` is `permit`ted, and whether the caller
51    /// should `audit` log the query.
52    pub fn has_permission<P: ClassPermission + Into<Permission> + Clone + 'static>(
53        &self,
54        source_sid: SecurityId,
55        target_sid: SecurityId,
56        permission: P,
57    ) -> PermissionCheckResult {
58        has_permission(
59            self.security_server.is_enforcing(),
60            self.access_vector_cache,
61            self.security_server,
62            source_sid,
63            target_sid,
64            permission,
65        )
66    }
67
68    // TODO: https://fxbug.dev/362699811 - Remove this once `SecurityServer` APIs such as `sid_to_security_context()`
69    // are exposed via a trait rather than directly by that implementation.
70    pub fn security_server(&self) -> &SecurityServer {
71        self.security_server
72    }
73
74    /// Returns the SID with which to label a new `file_class` instance created by `subject_sid`, with `target_sid`
75    /// as its parent, taking into account role & type transition rules, and filename-transition rules.
76    /// If a filename-transition rule matches the `fs_node_name` then that will be used, otherwise the
77    /// filename-independent computation will be applied.
78    pub fn compute_new_fs_node_sid(
79        &self,
80        source_sid: SecurityId,
81        target_sid: SecurityId,
82        fs_node_class: FsNodeClass,
83        fs_node_name: NullessByteStr<'_>,
84    ) -> Result<SecurityId, anyhow::Error> {
85        // TODO: https://fxbug.dev/385075470 - Stop skipping empty name lookups once by-name lookup is better optimized.
86        if !fs_node_name.as_bytes().is_empty() {
87            if let Some(sid) = self.access_vector_cache.compute_new_fs_node_sid_with_name(
88                source_sid,
89                target_sid,
90                fs_node_class,
91                fs_node_name,
92            ) {
93                return Ok(sid);
94            }
95        }
96        self.access_vector_cache.compute_new_fs_node_sid(source_sid, target_sid, fs_node_class)
97    }
98}
99
100/// Internal implementation of the `has_permission()` API, in terms of the `Query` and `AccessVectorComputer` traits.
101fn has_permission<P: ClassPermission + Into<Permission> + Clone + 'static>(
102    is_enforcing: bool,
103    query: &impl Query,
104    access_vector_computer: &impl AccessVectorComputer,
105    source_sid: SecurityId,
106    target_sid: SecurityId,
107    permission: P,
108) -> PermissionCheckResult {
109    #[cfg(target_os = "fuchsia")]
110    profile_duration!("libselinux.check_permission");
111    let target_class = permission.class();
112
113    let decision = query.compute_access_decision(source_sid, target_sid, target_class.into());
114
115    let mut result = if let Some(permission_access_vector) =
116        access_vector_computer.access_vector_from_permissions(&[permission])
117    {
118        let permit = permission_access_vector & decision.allow == permission_access_vector;
119
120        let audit = if permit {
121            permission_access_vector & decision.auditallow != AccessVector::NONE
122        } else {
123            permission_access_vector & decision.auditdeny != AccessVector::NONE
124        };
125        PermissionCheckResult { permit, audit, todo_bug: None }
126    } else {
127        PermissionCheckResult { permit: false, audit: true, todo_bug: None }
128    };
129
130    if !result.permit {
131        if !is_enforcing {
132            // If the security server is not currently enforcing then permit all access.
133            result.permit = true;
134        } else if decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
135            // If the access decision indicates that the source domain is permissive then permit
136            // all access.
137            result.permit = true;
138        } else if decision.todo_bug.is_some() {
139            // If the access decision includes a `todo_bug` then permit the access and return the
140            // bug Id to the caller, for audit logging.
141            result.permit = true;
142            result.todo_bug = decision.todo_bug;
143        }
144    }
145
146    result
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use crate::access_vector_cache::DenyAll;
153    use crate::policy::testing::{ACCESS_VECTOR_0001, ACCESS_VECTOR_0010};
154    use crate::policy::{AccessDecision, AccessVector, IoctlAccessDecision};
155    use crate::{AbstractObjectClass, ProcessPermission};
156
157    use std::any::Any;
158    use std::num::NonZeroU32;
159    use std::sync::atomic::{AtomicU32, Ordering};
160    use std::sync::LazyLock;
161
162    /// SID to use where any value will do.
163    static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
164
165    /// Returns a new `SecurityId` with unique id.
166    fn unique_sid() -> SecurityId {
167        static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
168        SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
169    }
170
171    fn access_vector_from_permission<P: ClassPermission + Into<Permission> + 'static>(
172        permission: P,
173    ) -> AccessVector {
174        let any = &permission as &dyn Any;
175        let permission_ref = match any.downcast_ref::<ProcessPermission>() {
176            Some(permission_ref) => permission_ref,
177            None => return AccessVector::NONE,
178        };
179
180        match permission_ref {
181            ProcessPermission::Fork => ACCESS_VECTOR_0001,
182            ProcessPermission::Transition => ACCESS_VECTOR_0010,
183            _ => AccessVector::NONE,
184        }
185    }
186
187    fn access_vector_from_permissions<
188        'a,
189        P: ClassPermission + Into<Permission> + Clone + 'static,
190    >(
191        permissions: &[P],
192    ) -> AccessVector {
193        let mut access_vector = AccessVector::NONE;
194        for permission in permissions {
195            access_vector |= access_vector_from_permission(permission.clone());
196        }
197        access_vector
198    }
199
200    #[derive(Default)]
201    pub struct DenyAllPermissions(DenyAll);
202
203    impl Query for DenyAllPermissions {
204        fn compute_access_decision(
205            &self,
206            source_sid: SecurityId,
207            target_sid: SecurityId,
208            target_class: AbstractObjectClass,
209        ) -> AccessDecision {
210            self.0.compute_access_decision(source_sid, target_sid, target_class)
211        }
212
213        fn compute_new_fs_node_sid(
214            &self,
215            _source_sid: SecurityId,
216            _target_sid: SecurityId,
217            _fs_node_class: FsNodeClass,
218        ) -> Result<SecurityId, anyhow::Error> {
219            unreachable!();
220        }
221
222        fn compute_new_fs_node_sid_with_name(
223            &self,
224            _source_sid: SecurityId,
225            _target_sid: SecurityId,
226            _fs_node_class: FsNodeClass,
227            _fs_node_name: NullessByteStr<'_>,
228        ) -> Option<SecurityId> {
229            unreachable!();
230        }
231
232        fn compute_ioctl_access_decision(
233            &self,
234            _source_sid: SecurityId,
235            _target_sid: SecurityId,
236            _target_class: AbstractObjectClass,
237            _ioctl_prefix: u8,
238        ) -> IoctlAccessDecision {
239            // ioctl access decisions are only checked if the `ioctl` permission is allowed, which is
240            // never the case for `DenyAllPermissions`.
241            unreachable!()
242        }
243    }
244
245    impl AccessVectorComputer for DenyAllPermissions {
246        fn access_vector_from_permissions<
247            P: ClassPermission + Into<Permission> + Clone + 'static,
248        >(
249            &self,
250            permissions: &[P],
251        ) -> Option<AccessVector> {
252            Some(access_vector_from_permissions(permissions))
253        }
254    }
255
256    /// A [`Query`] that permits all [`AccessVector`].
257    #[derive(Default)]
258    struct AllowAllPermissions;
259
260    impl Query for AllowAllPermissions {
261        fn compute_access_decision(
262            &self,
263            _source_sid: SecurityId,
264            _target_sid: SecurityId,
265            _target_class: AbstractObjectClass,
266        ) -> AccessDecision {
267            AccessDecision::allow(AccessVector::ALL)
268        }
269
270        fn compute_new_fs_node_sid(
271            &self,
272            _source_sid: SecurityId,
273            _target_sid: SecurityId,
274            _fs_node_class: FsNodeClass,
275        ) -> Result<SecurityId, anyhow::Error> {
276            unreachable!();
277        }
278
279        fn compute_new_fs_node_sid_with_name(
280            &self,
281            _source_sid: SecurityId,
282            _target_sid: SecurityId,
283            _fs_node_class: FsNodeClass,
284            _fs_node_name: NullessByteStr<'_>,
285        ) -> Option<SecurityId> {
286            unreachable!();
287        }
288
289        fn compute_ioctl_access_decision(
290            &self,
291            _source_sid: SecurityId,
292            _target_sid: SecurityId,
293            _target_class: AbstractObjectClass,
294            _ioctl_prefix: u8,
295        ) -> IoctlAccessDecision {
296            IoctlAccessDecision::ALLOW_ALL
297        }
298    }
299
300    impl AccessVectorComputer for AllowAllPermissions {
301        fn access_vector_from_permissions<
302            P: ClassPermission + Into<Permission> + Clone + 'static,
303        >(
304            &self,
305            permissions: &[P],
306        ) -> Option<AccessVector> {
307            Some(access_vector_from_permissions(permissions))
308        }
309    }
310
311    #[test]
312    fn has_permission_both() {
313        let deny_all: DenyAllPermissions = Default::default();
314        let allow_all: AllowAllPermissions = Default::default();
315
316        // Use permissions that are mapped to access vector bits in
317        // `access_vector_from_permission`.
318        let permissions = [ProcessPermission::Fork, ProcessPermission::Transition];
319        for permission in &permissions {
320            // DenyAllPermissions denies.
321            assert_eq!(
322                PermissionCheckResult { permit: false, audit: true, todo_bug: None },
323                has_permission(
324                    /*is_enforcing=*/ true,
325                    &deny_all,
326                    &deny_all,
327                    *A_TEST_SID,
328                    *A_TEST_SID,
329                    permission.clone()
330                )
331            );
332            // AllowAllPermissions allows.
333            assert_eq!(
334                PermissionCheckResult { permit: true, audit: false, todo_bug: None },
335                has_permission(
336                    /*is_enforcing=*/ true,
337                    &allow_all,
338                    &allow_all,
339                    *A_TEST_SID,
340                    *A_TEST_SID,
341                    permission.clone()
342                )
343            );
344        }
345    }
346}