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::{AccessVectorCache, Query};
6use crate::policy::{
7    AccessDecision, AccessVector, AccessVectorComputer, SELINUX_AVD_FLAGS_PERMISSIVE, XpermsKind,
8};
9use crate::security_server::SecurityServer;
10use crate::{
11    ClassPermission, FsNodeClass, KernelPermission, NullessByteStr, ObjectClass, SecurityId,
12};
13
14use std::num::NonZeroU64;
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 `SecurityServer` and
34/// `AccessVectorCache` 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 AccessVectorCache,
39}
40
41impl<'a> PermissionCheck<'a> {
42    pub(crate) fn new(
43        security_server: &'a SecurityServer,
44        access_vector_cache: &'a AccessVectorCache,
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<KernelPermission> + 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    /// Returns whether the `source_sid` has both a base permission (i.e. `ioctl` or `nlmsg`) and
69    /// the specified extended permission on `target_sid`, and whether the decision should be
70    /// audited.
71    ///
72    /// A request is allowed if the base permission is `allow`ed and either the numeric extended
73    /// permission of this `xperms_kind` is included in an `allowxperm` statement, or extended
74    /// permissions of this kind are not filtered for this domain.
75    ///
76    /// A granted request is audited if the base permission is `auditallow` and the extended
77    /// permission is `auditallowxperm`.
78    ///
79    /// A denied request is audited if the base permission is `dontaudit` or the extended
80    /// permission is `dontauditxperm`.
81    pub fn has_extended_permission<
82        P: ClassPermission + Into<KernelPermission> + Clone + 'static,
83    >(
84        &self,
85        xperms_kind: XpermsKind,
86        source_sid: SecurityId,
87        target_sid: SecurityId,
88        permission: P,
89        xperm: u16,
90    ) -> PermissionCheckResult {
91        has_extended_permission(
92            self.security_server.is_enforcing(),
93            self.access_vector_cache,
94            self.security_server,
95            xperms_kind,
96            source_sid,
97            target_sid,
98            permission,
99            xperm,
100        )
101    }
102
103    // TODO: https://fxbug.dev/362699811 - Remove this once `SecurityServer` APIs such as `sid_to_security_context()`
104    // are exposed via a trait rather than directly by that implementation.
105    pub fn security_server(&self) -> &SecurityServer {
106        self.security_server
107    }
108
109    /// Returns the SID with which to label a new `file_class` instance created by `subject_sid`, with `target_sid`
110    /// as its parent, taking into account role & type transition rules, and filename-transition rules.
111    /// If a filename-transition rule matches the `fs_node_name` then that will be used, otherwise the
112    /// filename-independent computation will be applied.
113    pub fn compute_new_fs_node_sid(
114        &self,
115        source_sid: SecurityId,
116        target_sid: SecurityId,
117        fs_node_class: FsNodeClass,
118        fs_node_name: NullessByteStr<'_>,
119    ) -> Result<SecurityId, anyhow::Error> {
120        // TODO: https://fxbug.dev/385075470 - Stop skipping empty name lookups once by-name lookup is better optimized.
121        if !fs_node_name.as_bytes().is_empty() {
122            if let Some(sid) = self.access_vector_cache.compute_new_fs_node_sid_with_name(
123                source_sid,
124                target_sid,
125                fs_node_class,
126                fs_node_name,
127            ) {
128                return Ok(sid);
129            }
130        }
131        self.access_vector_cache.compute_new_fs_node_sid(source_sid, target_sid, fs_node_class)
132    }
133
134    /// Returns the raw `AccessDecision` for a specified source, target and class.
135    pub fn compute_access_decision(
136        &self,
137        source_sid: SecurityId,
138        target_sid: SecurityId,
139        target_class: ObjectClass,
140    ) -> AccessDecision {
141        self.access_vector_cache.compute_access_decision(source_sid, target_sid, target_class)
142    }
143}
144
145/// Internal implementation of the `has_permission()` API, in terms of the `Query` and `AccessVectorComputer` traits.
146fn has_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
147    is_enforcing: bool,
148    query: &impl Query,
149    access_vector_computer: &impl AccessVectorComputer,
150    source_sid: SecurityId,
151    target_sid: SecurityId,
152    permission: P,
153) -> PermissionCheckResult {
154    let target_class = permission.class();
155
156    let decision = query.compute_access_decision(source_sid, target_sid, target_class.into());
157
158    let mut result = if let Some(permission_access_vector) =
159        access_vector_computer.access_vector_from_permissions(&[permission])
160    {
161        let permit = permission_access_vector & decision.allow == permission_access_vector;
162        let audit = if permit {
163            permission_access_vector & decision.auditallow != AccessVector::NONE
164        } else {
165            permission_access_vector & decision.auditdeny != AccessVector::NONE
166        };
167        PermissionCheckResult { permit, audit, todo_bug: None }
168    } else {
169        PermissionCheckResult { permit: false, audit: true, todo_bug: None }
170    };
171
172    if !result.permit {
173        if decision.todo_bug.is_some() {
174            // If the access decision includes a `todo_bug` then permit the access and return the
175            // bug Id to the caller, for audit logging.
176            //
177            // This is checked before the "permissive" settings because exceptions work-around
178            // issues in our implementation, or policy builds, so permissive treatment can lead
179            // to logspam as well as losing bug-tracking information.
180            result.permit = true;
181            result.todo_bug = decision.todo_bug;
182        } else if !is_enforcing {
183            // If the security server is not currently enforcing then permit all access.
184            result.permit = true;
185        } else if decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
186            // If the access decision indicates that the source domain is permissive then permit
187            // all access.
188            result.permit = true;
189        }
190    }
191
192    result
193}
194
195/// Internal implementation of the `has_extended_permission()` API, in terms of the `Query` and
196/// `AccessVectorComputer` traits.
197fn has_extended_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
198    is_enforcing: bool,
199    query: &impl Query,
200    access_vector_computer: &impl AccessVectorComputer,
201    xperms_kind: XpermsKind,
202    source_sid: SecurityId,
203    target_sid: SecurityId,
204    permission: P,
205    xperm: u16,
206) -> PermissionCheckResult {
207    let target_class = ObjectClass::from(permission.class());
208
209    let permission_decision = query.compute_access_decision(source_sid, target_sid, target_class);
210
211    let [xperms_postfix, xperms_prefix] = xperm.to_le_bytes();
212    let xperms_decision = query.compute_xperms_access_decision(
213        xperms_kind,
214        source_sid,
215        target_sid,
216        target_class,
217        xperms_prefix,
218    );
219
220    let mut result = if let Some(permission_access_vector) =
221        access_vector_computer.access_vector_from_permissions(&[permission])
222    {
223        let permit = (permission_access_vector & permission_decision.allow
224            == permission_access_vector)
225            && xperms_decision.allow.contains(xperms_postfix);
226        let audit = if permit {
227            (permission_access_vector & permission_decision.auditallow == permission_access_vector)
228                && xperms_decision.auditallow.contains(xperms_postfix)
229        } else {
230            (permission_access_vector & permission_decision.auditdeny == permission_access_vector)
231                && xperms_decision.auditdeny.contains(xperms_postfix)
232        };
233        PermissionCheckResult { permit, audit, todo_bug: None }
234    } else {
235        PermissionCheckResult { permit: false, audit: true, todo_bug: None }
236    };
237
238    if !result.permit {
239        if !is_enforcing {
240            result.permit = true;
241        } else if permission_decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
242            result.permit = true;
243        } else if permission_decision.todo_bug.is_some() {
244            // Currently we can make an exception for the base permission but not for specific
245            // extended permissions.
246            result.permit = true;
247            result.todo_bug = permission_decision.todo_bug;
248        }
249    }
250
251    result
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use crate::policy::testing::{ACCESS_VECTOR_0001, ACCESS_VECTOR_0010};
258    use crate::policy::{AccessDecision, AccessVector, XpermsAccessDecision};
259    use crate::{
260        CommonFilePermission, CommonFsNodePermission, FileClass, FilePermission, ForClass,
261        ObjectClass, ProcessPermission,
262    };
263
264    use std::num::NonZeroU32;
265    use std::sync::LazyLock;
266    use std::sync::atomic::{AtomicU32, Ordering};
267
268    /// SID to use where any value will do.
269    static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
270
271    /// Returns a new `SecurityId` with unique id.
272    fn unique_sid() -> SecurityId {
273        static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
274        SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
275    }
276
277    fn access_vector_from_permission<P: ClassPermission + Into<KernelPermission> + 'static>(
278        permission: P,
279    ) -> AccessVector {
280        match permission.into() {
281            // Process class permissions
282            KernelPermission::Process(ProcessPermission::Fork) => ACCESS_VECTOR_0001,
283            KernelPermission::Process(ProcessPermission::Transition) => ACCESS_VECTOR_0010,
284            // File class permissions
285            KernelPermission::File(FilePermission::Common(CommonFilePermission::Common(
286                CommonFsNodePermission::Ioctl,
287            ))) => ACCESS_VECTOR_0001,
288            _ => AccessVector::NONE,
289        }
290    }
291
292    fn access_vector_from_permissions<
293        'a,
294        P: ClassPermission + Into<KernelPermission> + Clone + 'static,
295    >(
296        permissions: &[P],
297    ) -> AccessVector {
298        let mut access_vector = AccessVector::NONE;
299        for permission in permissions {
300            access_vector |= access_vector_from_permission(permission.clone());
301        }
302        access_vector
303    }
304
305    #[derive(Default)]
306    pub struct DenyAllPermissions;
307
308    impl Query for DenyAllPermissions {
309        fn compute_access_decision(
310            &self,
311            _source_sid: SecurityId,
312            _target_sid: SecurityId,
313            _target_class: ObjectClass,
314        ) -> AccessDecision {
315            AccessDecision::allow(AccessVector::NONE)
316        }
317
318        fn compute_new_fs_node_sid(
319            &self,
320            _source_sid: SecurityId,
321            _target_sid: SecurityId,
322            _fs_node_class: FsNodeClass,
323        ) -> Result<SecurityId, anyhow::Error> {
324            unreachable!();
325        }
326
327        fn compute_new_fs_node_sid_with_name(
328            &self,
329            _source_sid: SecurityId,
330            _target_sid: SecurityId,
331            _fs_node_class: FsNodeClass,
332            _fs_node_name: NullessByteStr<'_>,
333        ) -> Option<SecurityId> {
334            unreachable!();
335        }
336
337        fn compute_xperms_access_decision(
338            &self,
339            _xperms_kind: XpermsKind,
340            _source_sid: SecurityId,
341            _target_sid: SecurityId,
342            _target_class: ObjectClass,
343            _xperms_prefix: u8,
344        ) -> XpermsAccessDecision {
345            XpermsAccessDecision::DENY_ALL
346        }
347    }
348
349    impl AccessVectorComputer for DenyAllPermissions {
350        fn access_vector_from_permissions<
351            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
352        >(
353            &self,
354            permissions: &[P],
355        ) -> Option<AccessVector> {
356            Some(access_vector_from_permissions(permissions))
357        }
358    }
359
360    /// A [`Query`] that permits all [`AccessVector`].
361    #[derive(Default)]
362    struct AllowAllPermissions;
363
364    impl Query for AllowAllPermissions {
365        fn compute_access_decision(
366            &self,
367            _source_sid: SecurityId,
368            _target_sid: SecurityId,
369            _target_class: ObjectClass,
370        ) -> AccessDecision {
371            AccessDecision::allow(AccessVector::ALL)
372        }
373
374        fn compute_new_fs_node_sid(
375            &self,
376            _source_sid: SecurityId,
377            _target_sid: SecurityId,
378            _fs_node_class: FsNodeClass,
379        ) -> Result<SecurityId, anyhow::Error> {
380            unreachable!();
381        }
382
383        fn compute_new_fs_node_sid_with_name(
384            &self,
385            _source_sid: SecurityId,
386            _target_sid: SecurityId,
387            _fs_node_class: FsNodeClass,
388            _fs_node_name: NullessByteStr<'_>,
389        ) -> Option<SecurityId> {
390            unreachable!();
391        }
392
393        fn compute_xperms_access_decision(
394            &self,
395            _xperms_kind: XpermsKind,
396            _source_sid: SecurityId,
397            _target_sid: SecurityId,
398            _target_class: ObjectClass,
399            _xperms_prefix: u8,
400        ) -> XpermsAccessDecision {
401            XpermsAccessDecision::ALLOW_ALL
402        }
403    }
404
405    impl AccessVectorComputer for AllowAllPermissions {
406        fn access_vector_from_permissions<
407            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
408        >(
409            &self,
410            permissions: &[P],
411        ) -> Option<AccessVector> {
412            Some(access_vector_from_permissions(permissions))
413        }
414    }
415
416    /// A [`Query`] that denies all [`AccessVectors`] and allows all extended permissions.
417    #[derive(Default)]
418    struct DenyPermissionsAllowXperms;
419
420    impl Query for DenyPermissionsAllowXperms {
421        fn compute_access_decision(
422            &self,
423            _source_sid: SecurityId,
424            _target_sid: SecurityId,
425            _target_class: ObjectClass,
426        ) -> AccessDecision {
427            AccessDecision::allow(AccessVector::NONE)
428        }
429
430        fn compute_new_fs_node_sid(
431            &self,
432            _source_sid: SecurityId,
433            _target_sid: SecurityId,
434            _fs_node_class: FsNodeClass,
435        ) -> Result<SecurityId, anyhow::Error> {
436            unreachable!();
437        }
438
439        fn compute_new_fs_node_sid_with_name(
440            &self,
441            _source_sid: SecurityId,
442            _target_sid: SecurityId,
443            _fs_node_class: FsNodeClass,
444            _fs_node_name: NullessByteStr<'_>,
445        ) -> Option<SecurityId> {
446            unreachable!();
447        }
448
449        fn compute_xperms_access_decision(
450            &self,
451            _xperms_kind: XpermsKind,
452            _source_sid: SecurityId,
453            _target_sid: SecurityId,
454            _target_class: ObjectClass,
455            _xperms_prefix: u8,
456        ) -> XpermsAccessDecision {
457            XpermsAccessDecision::ALLOW_ALL
458        }
459    }
460
461    impl AccessVectorComputer for DenyPermissionsAllowXperms {
462        fn access_vector_from_permissions<
463            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
464        >(
465            &self,
466            permissions: &[P],
467        ) -> Option<AccessVector> {
468            Some(access_vector_from_permissions(permissions))
469        }
470    }
471
472    /// A [`Query`] that allows all [`AccessVectors`] and denies all extended permissions.
473    #[derive(Default)]
474    struct AllowPermissionsDenyXperms;
475
476    impl Query for AllowPermissionsDenyXperms {
477        fn compute_access_decision(
478            &self,
479            _source_sid: SecurityId,
480            _target_sid: SecurityId,
481            _target_class: ObjectClass,
482        ) -> AccessDecision {
483            AccessDecision::allow(AccessVector::ALL)
484        }
485
486        fn compute_new_fs_node_sid(
487            &self,
488            _source_sid: SecurityId,
489            _target_sid: SecurityId,
490            _fs_node_class: FsNodeClass,
491        ) -> Result<SecurityId, anyhow::Error> {
492            unreachable!();
493        }
494
495        fn compute_new_fs_node_sid_with_name(
496            &self,
497            _source_sid: SecurityId,
498            _target_sid: SecurityId,
499            _fs_node_class: FsNodeClass,
500            _fs_node_name: NullessByteStr<'_>,
501        ) -> Option<SecurityId> {
502            unreachable!();
503        }
504
505        fn compute_xperms_access_decision(
506            &self,
507            _xperms_kind: XpermsKind,
508            _source_sid: SecurityId,
509            _target_sid: SecurityId,
510            _target_class: ObjectClass,
511            _xperms_prefix: u8,
512        ) -> XpermsAccessDecision {
513            XpermsAccessDecision::DENY_ALL
514        }
515    }
516
517    impl AccessVectorComputer for AllowPermissionsDenyXperms {
518        fn access_vector_from_permissions<
519            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
520        >(
521            &self,
522            permissions: &[P],
523        ) -> Option<AccessVector> {
524            Some(access_vector_from_permissions(permissions))
525        }
526    }
527
528    #[test]
529    fn has_permission_both() {
530        let deny_all: DenyAllPermissions = Default::default();
531        let allow_all: AllowAllPermissions = Default::default();
532
533        // Use permissions that are mapped to access vector bits in
534        // `access_vector_from_permission`.
535        let permissions = [ProcessPermission::Fork, ProcessPermission::Transition];
536        for permission in &permissions {
537            // DenyAllPermissions denies.
538            assert_eq!(
539                PermissionCheckResult { permit: false, audit: true, todo_bug: None },
540                has_permission(
541                    /*is_enforcing=*/ true,
542                    &deny_all,
543                    &deny_all,
544                    *A_TEST_SID,
545                    *A_TEST_SID,
546                    permission.clone()
547                )
548            );
549            // AllowAllPermissions allows.
550            assert_eq!(
551                PermissionCheckResult { permit: true, audit: false, todo_bug: None },
552                has_permission(
553                    /*is_enforcing=*/ true,
554                    &allow_all,
555                    &allow_all,
556                    *A_TEST_SID,
557                    *A_TEST_SID,
558                    permission.clone()
559                )
560            );
561        }
562    }
563
564    #[test]
565    fn has_ioctl_permission_enforcing() {
566        let deny_all: DenyAllPermissions = Default::default();
567        let allow_all: AllowAllPermissions = Default::default();
568        let deny_perms_allow_xperms: DenyPermissionsAllowXperms = Default::default();
569        let allow_perms_deny_xperms: AllowPermissionsDenyXperms = Default::default();
570        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
571
572        // DenyAllPermissions denies.
573        assert_eq!(
574            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
575            has_extended_permission(
576                /*is_enforcing=*/ true,
577                &deny_all,
578                &deny_all,
579                XpermsKind::Ioctl,
580                *A_TEST_SID,
581                *A_TEST_SID,
582                permission.clone(),
583                0xabcd
584            )
585        );
586        // AllowAllPermissions allows.
587        assert_eq!(
588            PermissionCheckResult { permit: true, audit: false, todo_bug: None },
589            has_extended_permission(
590                /*is_enforcing=*/ true,
591                &allow_all,
592                &allow_all,
593                XpermsKind::Ioctl,
594                *A_TEST_SID,
595                *A_TEST_SID,
596                permission.clone(),
597                0xabcd
598            )
599        );
600        // DenyPermissionsAllowXperms denies.
601        assert_eq!(
602            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
603            has_extended_permission(
604                /*is_enforcing=*/ true,
605                &deny_perms_allow_xperms,
606                &deny_perms_allow_xperms,
607                XpermsKind::Ioctl,
608                *A_TEST_SID,
609                *A_TEST_SID,
610                permission.clone(),
611                0xabcd
612            )
613        );
614        // AllowPermissionsDenyXperms denies.
615        assert_eq!(
616            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
617            has_extended_permission(
618                /*is_enforcing=*/ true,
619                &allow_perms_deny_xperms,
620                &allow_perms_deny_xperms,
621                XpermsKind::Ioctl,
622                *A_TEST_SID,
623                *A_TEST_SID,
624                permission,
625                0xabcd
626            )
627        );
628    }
629
630    #[test]
631    fn has_ioctl_permission_not_enforcing() {
632        let deny_all: DenyAllPermissions = Default::default();
633        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
634
635        // DenyAllPermissions denies, but the permission is allowed when the security server
636        // is not in enforcing mode. The decision should still be audited.
637        assert_eq!(
638            PermissionCheckResult { permit: true, audit: true, todo_bug: None },
639            has_extended_permission(
640                /*is_enforcing=*/ false,
641                &deny_all,
642                &deny_all,
643                XpermsKind::Ioctl,
644                *A_TEST_SID,
645                *A_TEST_SID,
646                permission,
647                0xabcd
648            )
649        );
650    }
651}