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 = permission.class();
208
209    let permission_decision =
210        query.compute_access_decision(source_sid, target_sid, target_class.into());
211
212    let [xperms_postfix, xperms_prefix] = xperm.to_le_bytes();
213    let xperms_decision = query.compute_xperms_access_decision(
214        xperms_kind,
215        source_sid,
216        target_sid,
217        target_class.into(),
218        xperms_prefix,
219    );
220
221    let mut result = if let Some(permission_access_vector) =
222        access_vector_computer.access_vector_from_permissions(&[permission])
223    {
224        let permit = (permission_access_vector & permission_decision.allow
225            == permission_access_vector)
226            && xperms_decision.allow.contains(xperms_postfix);
227        let audit = if permit {
228            (permission_access_vector & permission_decision.auditallow == permission_access_vector)
229                && xperms_decision.auditallow.contains(xperms_postfix)
230        } else {
231            (permission_access_vector & permission_decision.auditdeny == permission_access_vector)
232                && xperms_decision.auditdeny.contains(xperms_postfix)
233        };
234        PermissionCheckResult { permit, audit, todo_bug: None }
235    } else {
236        PermissionCheckResult { permit: false, audit: true, todo_bug: None }
237    };
238
239    if !result.permit {
240        if !is_enforcing {
241            result.permit = true;
242        } else if permission_decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
243            result.permit = true;
244        } else if permission_decision.todo_bug.is_some() {
245            // Currently we can make an exception for the base permission but not for specific
246            // extended permissions.
247            result.permit = true;
248            result.todo_bug = permission_decision.todo_bug;
249        }
250    }
251
252    result
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    use crate::policy::testing::{ACCESS_VECTOR_0001, ACCESS_VECTOR_0010};
259    use crate::policy::{AccessDecision, AccessVector, XpermsAccessDecision};
260    use crate::{
261        CommonFilePermission, CommonFsNodePermission, FileClass, FilePermission, ForClass,
262        ObjectClass, ProcessPermission,
263    };
264
265    use std::num::NonZeroU32;
266    use std::sync::LazyLock;
267    use std::sync::atomic::{AtomicU32, Ordering};
268
269    /// SID to use where any value will do.
270    static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
271
272    /// Returns a new `SecurityId` with unique id.
273    fn unique_sid() -> SecurityId {
274        static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
275        SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
276    }
277
278    fn access_vector_from_permission<P: ClassPermission + Into<KernelPermission> + 'static>(
279        permission: P,
280    ) -> AccessVector {
281        match permission.into() {
282            // Process class permissions
283            KernelPermission::Process(ProcessPermission::Fork) => ACCESS_VECTOR_0001,
284            KernelPermission::Process(ProcessPermission::Transition) => ACCESS_VECTOR_0010,
285            // File class permissions
286            KernelPermission::File(FilePermission::Common(CommonFilePermission::Common(
287                CommonFsNodePermission::Ioctl,
288            ))) => ACCESS_VECTOR_0001,
289            _ => AccessVector::NONE,
290        }
291    }
292
293    fn access_vector_from_permissions<
294        'a,
295        P: ClassPermission + Into<KernelPermission> + Clone + 'static,
296    >(
297        permissions: &[P],
298    ) -> AccessVector {
299        let mut access_vector = AccessVector::NONE;
300        for permission in permissions {
301            access_vector |= access_vector_from_permission(permission.clone());
302        }
303        access_vector
304    }
305
306    #[derive(Default)]
307    pub struct DenyAllPermissions;
308
309    impl Query for DenyAllPermissions {
310        fn compute_access_decision(
311            &self,
312            _source_sid: SecurityId,
313            _target_sid: SecurityId,
314            _target_class: ObjectClass,
315        ) -> AccessDecision {
316            AccessDecision::allow(AccessVector::NONE)
317        }
318
319        fn compute_new_fs_node_sid(
320            &self,
321            _source_sid: SecurityId,
322            _target_sid: SecurityId,
323            _fs_node_class: FsNodeClass,
324        ) -> Result<SecurityId, anyhow::Error> {
325            unreachable!();
326        }
327
328        fn compute_new_fs_node_sid_with_name(
329            &self,
330            _source_sid: SecurityId,
331            _target_sid: SecurityId,
332            _fs_node_class: FsNodeClass,
333            _fs_node_name: NullessByteStr<'_>,
334        ) -> Option<SecurityId> {
335            unreachable!();
336        }
337
338        fn compute_xperms_access_decision(
339            &self,
340            _xperms_kind: XpermsKind,
341            _source_sid: SecurityId,
342            _target_sid: SecurityId,
343            _target_class: ObjectClass,
344            _xperms_prefix: u8,
345        ) -> XpermsAccessDecision {
346            XpermsAccessDecision::DENY_ALL
347        }
348    }
349
350    impl AccessVectorComputer for DenyAllPermissions {
351        fn access_vector_from_permissions<
352            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
353        >(
354            &self,
355            permissions: &[P],
356        ) -> Option<AccessVector> {
357            Some(access_vector_from_permissions(permissions))
358        }
359    }
360
361    /// A [`Query`] that permits all [`AccessVector`].
362    #[derive(Default)]
363    struct AllowAllPermissions;
364
365    impl Query for AllowAllPermissions {
366        fn compute_access_decision(
367            &self,
368            _source_sid: SecurityId,
369            _target_sid: SecurityId,
370            _target_class: ObjectClass,
371        ) -> AccessDecision {
372            AccessDecision::allow(AccessVector::ALL)
373        }
374
375        fn compute_new_fs_node_sid(
376            &self,
377            _source_sid: SecurityId,
378            _target_sid: SecurityId,
379            _fs_node_class: FsNodeClass,
380        ) -> Result<SecurityId, anyhow::Error> {
381            unreachable!();
382        }
383
384        fn compute_new_fs_node_sid_with_name(
385            &self,
386            _source_sid: SecurityId,
387            _target_sid: SecurityId,
388            _fs_node_class: FsNodeClass,
389            _fs_node_name: NullessByteStr<'_>,
390        ) -> Option<SecurityId> {
391            unreachable!();
392        }
393
394        fn compute_xperms_access_decision(
395            &self,
396            _xperms_kind: XpermsKind,
397            _source_sid: SecurityId,
398            _target_sid: SecurityId,
399            _target_class: ObjectClass,
400            _xperms_prefix: u8,
401        ) -> XpermsAccessDecision {
402            XpermsAccessDecision::ALLOW_ALL
403        }
404    }
405
406    impl AccessVectorComputer for AllowAllPermissions {
407        fn access_vector_from_permissions<
408            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
409        >(
410            &self,
411            permissions: &[P],
412        ) -> Option<AccessVector> {
413            Some(access_vector_from_permissions(permissions))
414        }
415    }
416
417    /// A [`Query`] that denies all [`AccessVectors`] and allows all extended permissions.
418    #[derive(Default)]
419    struct DenyPermissionsAllowXperms;
420
421    impl Query for DenyPermissionsAllowXperms {
422        fn compute_access_decision(
423            &self,
424            _source_sid: SecurityId,
425            _target_sid: SecurityId,
426            _target_class: ObjectClass,
427        ) -> AccessDecision {
428            AccessDecision::allow(AccessVector::NONE)
429        }
430
431        fn compute_new_fs_node_sid(
432            &self,
433            _source_sid: SecurityId,
434            _target_sid: SecurityId,
435            _fs_node_class: FsNodeClass,
436        ) -> Result<SecurityId, anyhow::Error> {
437            unreachable!();
438        }
439
440        fn compute_new_fs_node_sid_with_name(
441            &self,
442            _source_sid: SecurityId,
443            _target_sid: SecurityId,
444            _fs_node_class: FsNodeClass,
445            _fs_node_name: NullessByteStr<'_>,
446        ) -> Option<SecurityId> {
447            unreachable!();
448        }
449
450        fn compute_xperms_access_decision(
451            &self,
452            _xperms_kind: XpermsKind,
453            _source_sid: SecurityId,
454            _target_sid: SecurityId,
455            _target_class: ObjectClass,
456            _xperms_prefix: u8,
457        ) -> XpermsAccessDecision {
458            XpermsAccessDecision::ALLOW_ALL
459        }
460    }
461
462    impl AccessVectorComputer for DenyPermissionsAllowXperms {
463        fn access_vector_from_permissions<
464            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
465        >(
466            &self,
467            permissions: &[P],
468        ) -> Option<AccessVector> {
469            Some(access_vector_from_permissions(permissions))
470        }
471    }
472
473    /// A [`Query`] that allows all [`AccessVectors`] and denies all extended permissions.
474    #[derive(Default)]
475    struct AllowPermissionsDenyXperms;
476
477    impl Query for AllowPermissionsDenyXperms {
478        fn compute_access_decision(
479            &self,
480            _source_sid: SecurityId,
481            _target_sid: SecurityId,
482            _target_class: ObjectClass,
483        ) -> AccessDecision {
484            AccessDecision::allow(AccessVector::ALL)
485        }
486
487        fn compute_new_fs_node_sid(
488            &self,
489            _source_sid: SecurityId,
490            _target_sid: SecurityId,
491            _fs_node_class: FsNodeClass,
492        ) -> Result<SecurityId, anyhow::Error> {
493            unreachable!();
494        }
495
496        fn compute_new_fs_node_sid_with_name(
497            &self,
498            _source_sid: SecurityId,
499            _target_sid: SecurityId,
500            _fs_node_class: FsNodeClass,
501            _fs_node_name: NullessByteStr<'_>,
502        ) -> Option<SecurityId> {
503            unreachable!();
504        }
505
506        fn compute_xperms_access_decision(
507            &self,
508            _xperms_kind: XpermsKind,
509            _source_sid: SecurityId,
510            _target_sid: SecurityId,
511            _target_class: ObjectClass,
512            _xperms_prefix: u8,
513        ) -> XpermsAccessDecision {
514            XpermsAccessDecision::DENY_ALL
515        }
516    }
517
518    impl AccessVectorComputer for AllowPermissionsDenyXperms {
519        fn access_vector_from_permissions<
520            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
521        >(
522            &self,
523            permissions: &[P],
524        ) -> Option<AccessVector> {
525            Some(access_vector_from_permissions(permissions))
526        }
527    }
528
529    #[test]
530    fn has_permission_both() {
531        let deny_all: DenyAllPermissions = Default::default();
532        let allow_all: AllowAllPermissions = Default::default();
533
534        // Use permissions that are mapped to access vector bits in
535        // `access_vector_from_permission`.
536        let permissions = [ProcessPermission::Fork, ProcessPermission::Transition];
537        for permission in &permissions {
538            // DenyAllPermissions denies.
539            assert_eq!(
540                PermissionCheckResult { permit: false, audit: true, todo_bug: None },
541                has_permission(
542                    /*is_enforcing=*/ true,
543                    &deny_all,
544                    &deny_all,
545                    *A_TEST_SID,
546                    *A_TEST_SID,
547                    permission.clone()
548                )
549            );
550            // AllowAllPermissions allows.
551            assert_eq!(
552                PermissionCheckResult { permit: true, audit: false, todo_bug: None },
553                has_permission(
554                    /*is_enforcing=*/ true,
555                    &allow_all,
556                    &allow_all,
557                    *A_TEST_SID,
558                    *A_TEST_SID,
559                    permission.clone()
560                )
561            );
562        }
563    }
564
565    #[test]
566    fn has_ioctl_permission_enforcing() {
567        let deny_all: DenyAllPermissions = Default::default();
568        let allow_all: AllowAllPermissions = Default::default();
569        let deny_perms_allow_xperms: DenyPermissionsAllowXperms = Default::default();
570        let allow_perms_deny_xperms: AllowPermissionsDenyXperms = Default::default();
571        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
572
573        // DenyAllPermissions denies.
574        assert_eq!(
575            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
576            has_extended_permission(
577                /*is_enforcing=*/ true,
578                &deny_all,
579                &deny_all,
580                XpermsKind::Ioctl,
581                *A_TEST_SID,
582                *A_TEST_SID,
583                permission.clone(),
584                0xabcd
585            )
586        );
587        // AllowAllPermissions allows.
588        assert_eq!(
589            PermissionCheckResult { permit: true, audit: false, todo_bug: None },
590            has_extended_permission(
591                /*is_enforcing=*/ true,
592                &allow_all,
593                &allow_all,
594                XpermsKind::Ioctl,
595                *A_TEST_SID,
596                *A_TEST_SID,
597                permission.clone(),
598                0xabcd
599            )
600        );
601        // DenyPermissionsAllowXperms denies.
602        assert_eq!(
603            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
604            has_extended_permission(
605                /*is_enforcing=*/ true,
606                &deny_perms_allow_xperms,
607                &deny_perms_allow_xperms,
608                XpermsKind::Ioctl,
609                *A_TEST_SID,
610                *A_TEST_SID,
611                permission.clone(),
612                0xabcd
613            )
614        );
615        // AllowPermissionsDenyXperms denies.
616        assert_eq!(
617            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
618            has_extended_permission(
619                /*is_enforcing=*/ true,
620                &allow_perms_deny_xperms,
621                &allow_perms_deny_xperms,
622                XpermsKind::Ioctl,
623                *A_TEST_SID,
624                *A_TEST_SID,
625                permission,
626                0xabcd
627            )
628        );
629    }
630
631    #[test]
632    fn has_ioctl_permission_not_enforcing() {
633        let deny_all: DenyAllPermissions = Default::default();
634        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
635
636        // DenyAllPermissions denies, but the permission is allowed when the security server
637        // is not in enforcing mode. The decision should still be audited.
638        assert_eq!(
639            PermissionCheckResult { permit: true, audit: true, todo_bug: None },
640            has_extended_permission(
641                /*is_enforcing=*/ false,
642                &deny_all,
643                &deny_all,
644                XpermsKind::Ioctl,
645                *A_TEST_SID,
646                *A_TEST_SID,
647                permission,
648                0xabcd
649            )
650        );
651    }
652}