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::{
7    AccessDecision, AccessVector, AccessVectorComputer, SELINUX_AVD_FLAGS_PERMISSIVE,
8};
9use crate::security_server::SecurityServer;
10use crate::{
11    ClassPermission, FsNodeClass, KernelPermission, NullessByteStr, ObjectClass, SecurityId,
12};
13
14#[cfg(target_os = "fuchsia")]
15use fuchsia_inspect_contrib::profile_duration;
16
17use std::num::NonZeroU64;
18use std::sync::Weak;
19
20/// Describes the result of a permission lookup between two Security Contexts.
21#[derive(Clone, Debug, PartialEq)]
22pub struct PermissionCheckResult {
23    /// True if the specified permissions should be permitted.
24    pub permit: bool,
25
26    /// True if details of the check should be audit logged. Audit logs are by default only output
27    /// when the policy defines that the permissions should be denied (whether or not the check is
28    /// "permissive"), but may be suppressed for some denials ("dontaudit"), or for some allowed
29    /// permissions ("auditallow").
30    pub audit: bool,
31
32    /// If the `AccessDecision` indicates that permission denials should not be enforced then `permit`
33    /// will be true, and this field will hold the Id of the bug to reference in audit logging.
34    pub todo_bug: Option<NonZeroU64>,
35}
36
37/// Implements the `has_permission()` API, based on supplied `Query` and `AccessVectorComputer`
38/// implementations.
39// TODO: https://fxbug.dev/362699811 - Revise the traits to avoid direct dependencies on `SecurityServer`.
40pub struct PermissionCheck<'a> {
41    security_server: &'a SecurityServer,
42    access_vector_cache: &'a Locked<FifoQueryCache<Weak<SecurityServer>>>,
43}
44
45impl<'a> PermissionCheck<'a> {
46    pub(crate) fn new(
47        security_server: &'a SecurityServer,
48        access_vector_cache: &'a Locked<FifoQueryCache<Weak<SecurityServer>>>,
49    ) -> Self {
50        Self { security_server, access_vector_cache }
51    }
52
53    /// Returns whether the `source_sid` has the specified `permission` on `target_sid`.
54    /// The result indicates both whether `permission` is `permit`ted, and whether the caller
55    /// should `audit` log the query.
56    pub fn has_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
57        &self,
58        source_sid: SecurityId,
59        target_sid: SecurityId,
60        permission: P,
61    ) -> PermissionCheckResult {
62        has_permission(
63            self.security_server.is_enforcing(),
64            self.access_vector_cache,
65            self.security_server,
66            source_sid,
67            target_sid,
68            permission,
69        )
70    }
71
72    /// Returns whether the `source_sid` has both the `ioctl` permission and the specified extended
73    /// permission on `target_sid`, and whether the decision should be audited.
74    ///
75    /// A request is allowed if the ioctl permission is `allow`ed and either the numeric ioctl
76    /// extended permission is `allowxperm`, or ioctl extended permissions are not filtered for this
77    /// domain.
78    ///
79    /// A granted request is audited if the ioctl permission is `auditallow` and the numeric ioctl
80    /// extended permission is `auditallowxperm`.
81    ///
82    /// A denied request is audited if the ioctl permission is `dontaudit` or the numeric ioctl
83    /// extended permission is `dontauditxperm`.
84    pub fn has_ioctl_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
85        &self,
86        source_sid: SecurityId,
87        target_sid: SecurityId,
88        permission: P,
89        ioctl: u16,
90    ) -> PermissionCheckResult {
91        has_ioctl_permission(
92            self.security_server.is_enforcing(),
93            self.access_vector_cache,
94            self.security_server,
95            source_sid,
96            target_sid,
97            permission,
98            ioctl,
99        )
100    }
101
102    // TODO: https://fxbug.dev/362699811 - Remove this once `SecurityServer` APIs such as `sid_to_security_context()`
103    // are exposed via a trait rather than directly by that implementation.
104    pub fn security_server(&self) -> &SecurityServer {
105        self.security_server
106    }
107
108    /// Returns the SID with which to label a new `file_class` instance created by `subject_sid`, with `target_sid`
109    /// as its parent, taking into account role & type transition rules, and filename-transition rules.
110    /// If a filename-transition rule matches the `fs_node_name` then that will be used, otherwise the
111    /// filename-independent computation will be applied.
112    pub fn compute_new_fs_node_sid(
113        &self,
114        source_sid: SecurityId,
115        target_sid: SecurityId,
116        fs_node_class: FsNodeClass,
117        fs_node_name: NullessByteStr<'_>,
118    ) -> Result<SecurityId, anyhow::Error> {
119        // TODO: https://fxbug.dev/385075470 - Stop skipping empty name lookups once by-name lookup is better optimized.
120        if !fs_node_name.as_bytes().is_empty() {
121            if let Some(sid) = self.access_vector_cache.compute_new_fs_node_sid_with_name(
122                source_sid,
123                target_sid,
124                fs_node_class,
125                fs_node_name,
126            ) {
127                return Ok(sid);
128            }
129        }
130        self.access_vector_cache.compute_new_fs_node_sid(source_sid, target_sid, fs_node_class)
131    }
132
133    /// Returns the raw `AccessDecision` for a specified source, target and class.
134    pub fn compute_access_decision(
135        &self,
136        source_sid: SecurityId,
137        target_sid: SecurityId,
138        target_class: ObjectClass,
139    ) -> AccessDecision {
140        self.access_vector_cache.compute_access_decision(source_sid, target_sid, target_class)
141    }
142}
143
144/// Internal implementation of the `has_permission()` API, in terms of the `Query` and `AccessVectorComputer` traits.
145fn has_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
146    is_enforcing: bool,
147    query: &impl Query,
148    access_vector_computer: &impl AccessVectorComputer,
149    source_sid: SecurityId,
150    target_sid: SecurityId,
151    permission: P,
152) -> PermissionCheckResult {
153    #[cfg(target_os = "fuchsia")]
154    profile_duration!("libselinux.check_permission");
155    let target_class = permission.class();
156
157    let decision = query.compute_access_decision(source_sid, target_sid, target_class.into());
158
159    let mut result = if let Some(permission_access_vector) =
160        access_vector_computer.access_vector_from_permissions(&[permission])
161    {
162        let permit = permission_access_vector & decision.allow == permission_access_vector;
163        let audit = if permit {
164            permission_access_vector & decision.auditallow != AccessVector::NONE
165        } else {
166            permission_access_vector & decision.auditdeny != AccessVector::NONE
167        };
168        PermissionCheckResult { permit, audit, todo_bug: None }
169    } else {
170        PermissionCheckResult { permit: false, audit: true, todo_bug: None }
171    };
172
173    if !result.permit {
174        if decision.todo_bug.is_some() {
175            // If the access decision includes a `todo_bug` then permit the access and return the
176            // bug Id to the caller, for audit logging.
177            //
178            // This is checked before the "permissive" settings because exceptions work-around
179            // issues in our implementation, or policy builds, so permissive treatment can lead
180            // to logspam as well as losing bug-tracking information.
181            result.permit = true;
182            result.todo_bug = decision.todo_bug;
183        } else if !is_enforcing {
184            // If the security server is not currently enforcing then permit all access.
185            result.permit = true;
186        } else if decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
187            // If the access decision indicates that the source domain is permissive then permit
188            // all access.
189            result.permit = true;
190        }
191    }
192
193    result
194}
195
196/// Internal implementation of the `has_ioctl_permission()` API, in terms of the `Query` and
197/// `AccessVectorComputer` traits.
198fn has_ioctl_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
199    is_enforcing: bool,
200    query: &impl Query,
201    access_vector_computer: &impl AccessVectorComputer,
202    source_sid: SecurityId,
203    target_sid: SecurityId,
204    permission: P,
205    ioctl: 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 [ioctl_postfix, ioctl_prefix] = ioctl.to_le_bytes();
213    let xperm_decision = query.compute_ioctl_access_decision(
214        source_sid,
215        target_sid,
216        target_class.into(),
217        ioctl_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            && xperm_decision.allow.contains(ioctl_postfix);
226        let audit = if permit {
227            (permission_access_vector & permission_decision.auditallow == permission_access_vector)
228                && xperm_decision.auditallow.contains(ioctl_postfix)
229        } else {
230            (permission_access_vector & permission_decision.auditdeny == permission_access_vector)
231                && xperm_decision.auditdeny.contains(ioctl_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 overall `ioctl` permission,
245            // but not for specific ioctl xperms.
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, IoctlAccessDecision};
259    use crate::{
260        CommonFilePermission, CommonFsNodePermission, FileClass, FilePermission, ObjectClass,
261        ProcessPermission,
262    };
263
264    use std::num::NonZeroU32;
265    use std::sync::atomic::{AtomicU32, Ordering};
266    use std::sync::LazyLock;
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_ioctl_access_decision(
338            &self,
339            _source_sid: SecurityId,
340            _target_sid: SecurityId,
341            _target_class: ObjectClass,
342            _ioctl_prefix: u8,
343        ) -> IoctlAccessDecision {
344            IoctlAccessDecision::DENY_ALL
345        }
346    }
347
348    impl AccessVectorComputer for DenyAllPermissions {
349        fn access_vector_from_permissions<
350            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
351        >(
352            &self,
353            permissions: &[P],
354        ) -> Option<AccessVector> {
355            Some(access_vector_from_permissions(permissions))
356        }
357    }
358
359    /// A [`Query`] that permits all [`AccessVector`].
360    #[derive(Default)]
361    struct AllowAllPermissions;
362
363    impl Query for AllowAllPermissions {
364        fn compute_access_decision(
365            &self,
366            _source_sid: SecurityId,
367            _target_sid: SecurityId,
368            _target_class: ObjectClass,
369        ) -> AccessDecision {
370            AccessDecision::allow(AccessVector::ALL)
371        }
372
373        fn compute_new_fs_node_sid(
374            &self,
375            _source_sid: SecurityId,
376            _target_sid: SecurityId,
377            _fs_node_class: FsNodeClass,
378        ) -> Result<SecurityId, anyhow::Error> {
379            unreachable!();
380        }
381
382        fn compute_new_fs_node_sid_with_name(
383            &self,
384            _source_sid: SecurityId,
385            _target_sid: SecurityId,
386            _fs_node_class: FsNodeClass,
387            _fs_node_name: NullessByteStr<'_>,
388        ) -> Option<SecurityId> {
389            unreachable!();
390        }
391
392        fn compute_ioctl_access_decision(
393            &self,
394            _source_sid: SecurityId,
395            _target_sid: SecurityId,
396            _target_class: ObjectClass,
397            _ioctl_prefix: u8,
398        ) -> IoctlAccessDecision {
399            IoctlAccessDecision::ALLOW_ALL
400        }
401    }
402
403    impl AccessVectorComputer for AllowAllPermissions {
404        fn access_vector_from_permissions<
405            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
406        >(
407            &self,
408            permissions: &[P],
409        ) -> Option<AccessVector> {
410            Some(access_vector_from_permissions(permissions))
411        }
412    }
413
414    /// A [`Query`] that denies all [`AccessVectors`] and allows all ioctl extended permissions.
415    #[derive(Default)]
416    struct DenyPermissionsAllowXperms;
417
418    impl Query for DenyPermissionsAllowXperms {
419        fn compute_access_decision(
420            &self,
421            _source_sid: SecurityId,
422            _target_sid: SecurityId,
423            _target_class: ObjectClass,
424        ) -> AccessDecision {
425            AccessDecision::allow(AccessVector::NONE)
426        }
427
428        fn compute_new_fs_node_sid(
429            &self,
430            _source_sid: SecurityId,
431            _target_sid: SecurityId,
432            _fs_node_class: FsNodeClass,
433        ) -> Result<SecurityId, anyhow::Error> {
434            unreachable!();
435        }
436
437        fn compute_new_fs_node_sid_with_name(
438            &self,
439            _source_sid: SecurityId,
440            _target_sid: SecurityId,
441            _fs_node_class: FsNodeClass,
442            _fs_node_name: NullessByteStr<'_>,
443        ) -> Option<SecurityId> {
444            unreachable!();
445        }
446
447        fn compute_ioctl_access_decision(
448            &self,
449            _source_sid: SecurityId,
450            _target_sid: SecurityId,
451            _target_class: ObjectClass,
452            _ioctl_prefix: u8,
453        ) -> IoctlAccessDecision {
454            IoctlAccessDecision::ALLOW_ALL
455        }
456    }
457
458    impl AccessVectorComputer for DenyPermissionsAllowXperms {
459        fn access_vector_from_permissions<
460            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
461        >(
462            &self,
463            permissions: &[P],
464        ) -> Option<AccessVector> {
465            Some(access_vector_from_permissions(permissions))
466        }
467    }
468
469    /// A [`Query`] that allows all [`AccessVectors`] and denies all ioctl extended permissions.
470    #[derive(Default)]
471    struct AllowPermissionsDenyXperms;
472
473    impl Query for AllowPermissionsDenyXperms {
474        fn compute_access_decision(
475            &self,
476            _source_sid: SecurityId,
477            _target_sid: SecurityId,
478            _target_class: ObjectClass,
479        ) -> AccessDecision {
480            AccessDecision::allow(AccessVector::ALL)
481        }
482
483        fn compute_new_fs_node_sid(
484            &self,
485            _source_sid: SecurityId,
486            _target_sid: SecurityId,
487            _fs_node_class: FsNodeClass,
488        ) -> Result<SecurityId, anyhow::Error> {
489            unreachable!();
490        }
491
492        fn compute_new_fs_node_sid_with_name(
493            &self,
494            _source_sid: SecurityId,
495            _target_sid: SecurityId,
496            _fs_node_class: FsNodeClass,
497            _fs_node_name: NullessByteStr<'_>,
498        ) -> Option<SecurityId> {
499            unreachable!();
500        }
501
502        fn compute_ioctl_access_decision(
503            &self,
504            _source_sid: SecurityId,
505            _target_sid: SecurityId,
506            _target_class: ObjectClass,
507            _ioctl_prefix: u8,
508        ) -> IoctlAccessDecision {
509            IoctlAccessDecision::DENY_ALL
510        }
511    }
512
513    impl AccessVectorComputer for AllowPermissionsDenyXperms {
514        fn access_vector_from_permissions<
515            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
516        >(
517            &self,
518            permissions: &[P],
519        ) -> Option<AccessVector> {
520            Some(access_vector_from_permissions(permissions))
521        }
522    }
523
524    #[test]
525    fn has_permission_both() {
526        let deny_all: DenyAllPermissions = Default::default();
527        let allow_all: AllowAllPermissions = Default::default();
528
529        // Use permissions that are mapped to access vector bits in
530        // `access_vector_from_permission`.
531        let permissions = [ProcessPermission::Fork, ProcessPermission::Transition];
532        for permission in &permissions {
533            // DenyAllPermissions denies.
534            assert_eq!(
535                PermissionCheckResult { permit: false, audit: true, todo_bug: None },
536                has_permission(
537                    /*is_enforcing=*/ true,
538                    &deny_all,
539                    &deny_all,
540                    *A_TEST_SID,
541                    *A_TEST_SID,
542                    permission.clone()
543                )
544            );
545            // AllowAllPermissions allows.
546            assert_eq!(
547                PermissionCheckResult { permit: true, audit: false, todo_bug: None },
548                has_permission(
549                    /*is_enforcing=*/ true,
550                    &allow_all,
551                    &allow_all,
552                    *A_TEST_SID,
553                    *A_TEST_SID,
554                    permission.clone()
555                )
556            );
557        }
558    }
559
560    #[test]
561    fn has_ioctl_permission_enforcing() {
562        let deny_all: DenyAllPermissions = Default::default();
563        let allow_all: AllowAllPermissions = Default::default();
564        let deny_perms_allow_xperms: DenyPermissionsAllowXperms = Default::default();
565        let allow_perms_deny_xperms: AllowPermissionsDenyXperms = Default::default();
566        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
567
568        // DenyAllPermissions denies.
569        assert_eq!(
570            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
571            has_ioctl_permission(
572                /*is_enforcing=*/ true,
573                &deny_all,
574                &deny_all,
575                *A_TEST_SID,
576                *A_TEST_SID,
577                permission.clone(),
578                0xabcd
579            )
580        );
581        // AllowAllPermissions allows.
582        assert_eq!(
583            PermissionCheckResult { permit: true, audit: false, todo_bug: None },
584            has_ioctl_permission(
585                /*is_enforcing=*/ true,
586                &allow_all,
587                &allow_all,
588                *A_TEST_SID,
589                *A_TEST_SID,
590                permission.clone(),
591                0xabcd
592            )
593        );
594        // DenyPermissionsAllowXperms denies.
595        assert_eq!(
596            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
597            has_ioctl_permission(
598                /*is_enforcing=*/ true,
599                &deny_perms_allow_xperms,
600                &deny_perms_allow_xperms,
601                *A_TEST_SID,
602                *A_TEST_SID,
603                permission.clone(),
604                0xabcd
605            )
606        );
607        // AllowPermissionsDenyXperms denies.
608        assert_eq!(
609            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
610            has_ioctl_permission(
611                /*is_enforcing=*/ true,
612                &allow_perms_deny_xperms,
613                &allow_perms_deny_xperms,
614                *A_TEST_SID,
615                *A_TEST_SID,
616                permission,
617                0xabcd
618            )
619        );
620    }
621
622    #[test]
623    fn has_ioctl_permission_not_enforcing() {
624        let deny_all: DenyAllPermissions = Default::default();
625        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
626
627        // DenyAllPermissions denies, but the permission is allowed when the security server
628        // is not in enforcing mode. The decision should still be audited.
629        assert_eq!(
630            PermissionCheckResult { permit: true, audit: true, todo_bug: None },
631            has_ioctl_permission(
632                /*is_enforcing=*/ false,
633                &deny_all,
634                &deny_all,
635                *A_TEST_SID,
636                *A_TEST_SID,
637                permission,
638                0xabcd
639            )
640        );
641    }
642}